diff --git a/build.gradle b/build.gradle index b8023ff77..552b5a74d 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,7 @@ plugins { id 'se.patrikerdes.use-latest-versions' version '0.2.18' id 'com.github.ben-manes.versions' version '0.49.0' id 'org.jreleaser' version '1.16.0' + id 'me.champeau.jmh' version '0.7.3' apply false } description = 'A set of libraries and other tools to aid development of blockchain and other decentralized software in Java and other JVM languages' @@ -124,6 +125,39 @@ subprojects { } } + if (file('src/jmh').directory) { + apply plugin: 'me.champeau.jmh' + tasks.named("jmh") { + description = "Usage: gradle jmh -Pincludes=MyBench -PasyncProfiler= -PasyncProfilerOptions=\n" + + "\nRun JMH benchmarks in each of the projects. Allows for controlling JMH execution directly from the command line.\n" + + "\t-Pincludes=\tInclude pattern (regular expression) for benchmarks to be executed. Defaults to including all benchmarks.\n" + + "\t-PasyncProfiler=\tLibrary path to fetch the Async profiler from. Default is to disable profiling.\n" + + "\t-PasyncProfilerOptions=\tOptions to pass on to the Async profiler separated by ';'. Default is to produce a flamegraph with all other default profiler options.\n" + } + + // to pass compilation as the compiler doesn't like what jmh tool is doing + compileJmhJava { + options.compilerArgs << '-Xlint:none' + } + + jmh { + jmhVersion = '1.37' + fork = 3 + includes = _strListCmdArg('includes', ['']) + var asyncProfiler = _strCmdArg('asyncProfiler') + var asyncProfilerOptions = _strCmdArg('asyncProfilerOptions', 'output=flamegraph') + if (asyncProfiler != null) { + profilers = ['async:libPath=' + asyncProfiler + ';' + asyncProfilerOptions] + } + duplicateClassesStrategy = DuplicatesStrategy.WARN + jvmArgs = ['-XX:+EnableDynamicAgentLoading'] + } + + dependencies { + jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.37' + } + } + plugins.withId('java', { _ -> sourceSets { integrationTest { @@ -567,3 +601,18 @@ tasks.register('checkNotice') { } throw new GradleException('NOTICE file is not up-to-date') } + +def _strListCmdArg(name, defaultValue) { + if (!project.hasProperty(name)) + return defaultValue + + return ((String) project.property(name)).tokenize(',') +} + +def _strCmdArg(name) { + return _strCmdArg(name, null) +} + +def _strCmdArg(name, defaultValue) { + return project.hasProperty(name) ? project.property(name) as String : defaultValue +} diff --git a/bytes/build.gradle b/bytes/build.gradle index aa2d575c1..973e68041 100644 --- a/bytes/build.gradle +++ b/bytes/build.gradle @@ -23,6 +23,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.junit.jupiter:junit-jupiter-params' testImplementation 'org.mockito:mockito-junit-jupiter' + testImplementation 'org.assertj:assertj-core' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' } diff --git a/bytes/src/jmh/java/org/benchmark/BytesMegamorphicBenchmarkV1.java b/bytes/src/jmh/java/org/benchmark/BytesMegamorphicBenchmarkV1.java new file mode 100644 index 000000000..c4c0cafc4 --- /dev/null +++ b/bytes/src/jmh/java/org/benchmark/BytesMegamorphicBenchmarkV1.java @@ -0,0 +1,62 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.benchmark; + +import org.apache.tuweni.bytes.Bytes; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(value = Mode.AverageTime) +@State(Scope.Thread) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +public class BytesMegamorphicBenchmarkV1 { + private static final int N = 4; + private static final int FACTOR = 1_000; + private static final Random RANDOM = new Random(23L); + Bytes[] bytesV1; + + @Param({"mono", "mega"}) + private String mode; + + @Setup + public void setup() { + bytesV1 = new Bytes[N * FACTOR]; + for (int i = 0; i < N * FACTOR; i += N) { + bytesV1[i] = Bytes.wrap(getBytes(32)); + bytesV1[i + 1] = "mega".equals(mode) ? Bytes.wrap(getBytes(48)) : Bytes.wrap(getBytes(32)); + bytesV1[i + 2] = + "mega".equals(mode) ? Bytes.repeat((byte) 0x09, 16) : Bytes.wrap(getBytes(32)); + bytesV1[i + 3] = + "mega".equals(mode) ? Bytes.wrap(bytesV1[i], bytesV1[i + 1]) : Bytes.wrap(getBytes(32)); + } + } + + private static byte[] getBytes(final int size) { + byte[] b = new byte[size]; + RANDOM.nextBytes(b); + return b; + } + + @Benchmark + @OperationsPerInvocation(N * FACTOR) + public void test() { + for (Bytes b : bytesV1) { + b.slice(1); + } + } +} diff --git a/bytes/src/jmh/java/org/benchmark/BytesMegamorphicBenchmarkV2.java b/bytes/src/jmh/java/org/benchmark/BytesMegamorphicBenchmarkV2.java new file mode 100644 index 000000000..b8315bc94 --- /dev/null +++ b/bytes/src/jmh/java/org/benchmark/BytesMegamorphicBenchmarkV2.java @@ -0,0 +1,62 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.benchmark; + +import org.apache.tuweni.v2.bytes.Bytes; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(value = Mode.AverageTime) +@State(Scope.Thread) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +public class BytesMegamorphicBenchmarkV2 { + private static final int N = 4; + private static final int FACTOR = 1_000; + private static final Random RANDOM = new Random(23L); + Bytes[] bytesV2; + + @Param({"mono", "mega"}) + private String mode; + + @Setup + public void setup() { + bytesV2 = new Bytes[N * FACTOR]; + for (int i = 0; i < N * FACTOR; i += N) { + bytesV2[i] = Bytes.wrap(getBytes(32)); + bytesV2[i + 1] = "mega".equals(mode) ? Bytes.wrap(getBytes(48)) : Bytes.wrap(getBytes(32)); + bytesV2[i + 2] = + "mega".equals(mode) ? Bytes.repeat((byte) 0x09, 16) : Bytes.wrap(getBytes(32)); + bytesV2[i + 3] = + "mega".equals(mode) ? Bytes.wrap(bytesV2[i], bytesV2[i + 1]) : Bytes.wrap(getBytes(32)); + } + } + + private static byte[] getBytes(final int size) { + byte[] b = new byte[size]; + RANDOM.nextBytes(b); + return b; + } + + @Benchmark + @OperationsPerInvocation(N * FACTOR) + public void test() { + for (Bytes b : bytesV2) { + b.slice(1); + } + } +} diff --git a/bytes/src/main/java/org/apache/tuweni/v2/bytes/ArrayWrappingBytes.java b/bytes/src/main/java/org/apache/tuweni/v2/bytes/ArrayWrappingBytes.java new file mode 100644 index 000000000..5a50c2011 --- /dev/null +++ b/bytes/src/main/java/org/apache/tuweni/v2/bytes/ArrayWrappingBytes.java @@ -0,0 +1,143 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static org.apache.tuweni.v2.bytes.Utils.checkArgument; +import static org.apache.tuweni.v2.bytes.Utils.checkElementIndex; +import static org.apache.tuweni.v2.bytes.Utils.checkLength; + +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.util.Arrays; + +import io.vertx.core.buffer.Buffer; + +class ArrayWrappingBytes extends Bytes { + + protected byte[] bytes; + protected final int offset; + + ArrayWrappingBytes(byte[] bytes) { + this(bytes, 0, bytes.length); + } + + ArrayWrappingBytes(byte[] bytes, int offset, int length) { + super(length); + this.bytes = bytes; + this.offset = offset; + } + + @Override + public byte get(int i) { + // Check bounds because while the array access would throw, the error message would be confusing + // for the caller. + checkElementIndex(offset + i, bytes.length); + checkElementIndex(i, size()); + return bytes[offset + i]; + } + + @Override + public Bytes slice(int i, int length) { + if (i == 0 && length == size()) { + return this; + } + + if (length == 0) { + return EMPTY; + } + + checkArgument(length > 0, "Invalid negative length"); + if (bytes.length > 0) { + checkElementIndex(offset + i, bytes.length); + } + checkLength(bytes.length, offset + i, length); + + return new ArrayWrappingBytes(bytes, offset + i, length); + } + + @Override + public int commonPrefixLength(Bytes other) { + if (!(other instanceof ArrayWrappingBytes o)) { + return super.commonPrefixLength(other); + } + int i = 0; + while (i < size() && i < o.size() && bytes[offset + i] == o.bytes[o.offset + i]) { + i++; + } + return i; + } + + @Override + public void update(MessageDigest digest) { + digest.update(bytes, offset, size()); + } + + @Override + public void appendTo(ByteBuffer byteBuffer) { + byteBuffer.put(bytes, offset, size()); + } + + @Override + public MutableBytes mutableCopy() { + return MutableBytes.fromArray(bytes, offset, size()); + } + + @Override + public void appendTo(Buffer buffer) { + buffer.appendBytes(bytes, offset, size()); + } + + @Override + public byte[] toArrayUnsafe() { + if (offset == 0 && size() == bytes.length) { + return bytes; + } + return Arrays.copyOfRange(bytes, offset, offset + size()); + } + + @Override + protected void and(byte[] bytesArray, int offset, int length) { + Utils.and(this.bytes, this.offset, bytesArray, offset, length); + } + + @Override + protected void or(byte[] bytesArray, int offset, int length) { + Utils.or(this.bytes, this.offset, bytesArray, offset, length); + } + + @Override + protected void xor(byte[] bytesArray, int offset, int length) { + Utils.xor(this.bytes, this.offset, bytesArray, offset, length); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Bytes other)) { + return false; + } + + if (this.size() != other.size()) { + return false; + } + + for (int i = 0; i < size(); i++) { + if (bytes[i + offset] != other.get(i)) { + return false; + } + } + + return true; + } + + @Override + protected int computeHashcode() { + int result = 1; + for (int i = 0; i < size(); i++) { + result = 31 * result + bytes[i + offset]; + } + return result; + } +} diff --git a/bytes/src/main/java/org/apache/tuweni/v2/bytes/BufferWrappingBytes.java b/bytes/src/main/java/org/apache/tuweni/v2/bytes/BufferWrappingBytes.java new file mode 100644 index 000000000..0d166d293 --- /dev/null +++ b/bytes/src/main/java/org/apache/tuweni/v2/bytes/BufferWrappingBytes.java @@ -0,0 +1,124 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static org.apache.tuweni.v2.bytes.Utils.checkElementIndex; +import static org.apache.tuweni.v2.bytes.Utils.checkLength; + +import io.vertx.core.buffer.Buffer; + +class BufferWrappingBytes extends Bytes { + protected final Buffer buffer; + + BufferWrappingBytes(Buffer buffer) { + super(buffer.length()); + this.buffer = buffer; + } + + BufferWrappingBytes(Buffer buffer, int offset, int length) { + super(length); + if (offset == 0 && length == buffer.length()) { + this.buffer = buffer; + } else { + this.buffer = buffer.slice(offset, offset + length); + } + } + + @Override + public byte get(int i) { + return buffer.getByte(i); + } + + @Override + public int getInt(int i) { + return buffer.getInt(i); + } + + @Override + public long getLong(int i) { + return buffer.getLong(i); + } + + @Override + public Bytes slice(int i, int length) { + int size = buffer.length(); + if (i == 0 && length == size) { + return this; + } + if (length == 0) { + return EMPTY; + } + + checkElementIndex(i, size); + checkLength(size, i, length); + + return new BufferWrappingBytes(buffer.slice(i, i + length)); + } + + @Override + public MutableBytes mutableCopy() { + return MutableBytes.fromArray(toArrayUnsafe()); + } + + @Override + public void appendTo(Buffer buffer) { + buffer.appendBuffer(this.buffer); + } + + @Override + public byte[] toArrayUnsafe() { + return buffer.getBytes(); + } + + @Override + protected void and(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + bytesArray[offset + i] = (byte) (buffer.getByte(i) & bytesArray[offset + i]); + } + } + + @Override + protected void or(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + bytesArray[offset + i] = (byte) (buffer.getByte(i) | bytesArray[offset + i]); + } + } + + @Override + protected void xor(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + bytesArray[offset + i] = (byte) (buffer.getByte(i) ^ bytesArray[offset + i]); + } + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Bytes other)) { + return false; + } + + if (this.size() != other.size()) { + return false; + } + + for (int i = 0; i < size(); i++) { + if (buffer.getByte(i) != other.get(i)) { + return false; + } + } + + return true; + } + + @Override + protected int computeHashcode() { + int result = 1; + for (int i = 0; i < size(); i++) { + result = 31 * result + buffer.getByte(i); + } + return result; + } +} diff --git a/bytes/src/main/java/org/apache/tuweni/v2/bytes/ByteBufWrappingBytes.java b/bytes/src/main/java/org/apache/tuweni/v2/bytes/ByteBufWrappingBytes.java new file mode 100644 index 000000000..4d002a196 --- /dev/null +++ b/bytes/src/main/java/org/apache/tuweni/v2/bytes/ByteBufWrappingBytes.java @@ -0,0 +1,128 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static org.apache.tuweni.v2.bytes.Utils.checkElementIndex; +import static org.apache.tuweni.v2.bytes.Utils.checkLength; + +import io.netty.buffer.ByteBuf; +import io.vertx.core.buffer.Buffer; + +class ByteBufWrappingBytes extends Bytes { + + protected final ByteBuf byteBuf; + + ByteBufWrappingBytes(ByteBuf byteBuf) { + super(byteBuf.capacity()); + this.byteBuf = byteBuf; + } + + ByteBufWrappingBytes(ByteBuf byteBuf, int offset, int length) { + super(length); + if (offset == 0 && length == byteBuf.capacity()) { + this.byteBuf = byteBuf; + } else { + this.byteBuf = byteBuf.slice(offset, length); + } + } + + @Override + public byte get(int i) { + return byteBuf.getByte(i); + } + + @Override + public int getInt(int i) { + return byteBuf.getInt(i); + } + + @Override + public long getLong(int i) { + return byteBuf.getLong(i); + } + + @Override + public Bytes slice(int i, int length) { + int size = byteBuf.capacity(); + if (i == 0 && length == size) { + return this; + } + if (length == 0) { + return EMPTY; + } + + checkElementIndex(i, size); + checkLength(size, i, length); + + return new ByteBufWrappingBytes(byteBuf.slice(i, length)); + } + + @Override + public MutableBytes mutableCopy() { + return MutableBytes.fromByteBuf(byteBuf, 0, byteBuf.capacity()); + } + + @Override + public void appendTo(Buffer buffer) { + buffer.appendBuffer(Buffer.buffer(this.byteBuf)); + } + + @Override + public byte[] toArrayUnsafe() { + byte[] array = new byte[byteBuf.capacity()]; + byteBuf.getBytes(0, array); + return array; + } + + @Override + protected void and(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + bytesArray[offset + i] = (byte) (byteBuf.getByte(i) & bytesArray[offset + i]); + } + } + + @Override + protected void or(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + bytesArray[offset + i] = (byte) (byteBuf.getByte(i) | bytesArray[offset + i]); + } + } + + @Override + protected void xor(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + bytesArray[offset + i] = (byte) (byteBuf.getByte(i) ^ bytesArray[offset + i]); + } + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Bytes other)) { + return false; + } + + if (this.size() != other.size()) { + return false; + } + + for (int i = 0; i < size(); i++) { + if (byteBuf.getByte(i) != other.get(i)) { + return false; + } + } + + return true; + } + + @Override + protected int computeHashcode() { + int result = 1; + for (int i = 0; i < size(); i++) { + result = 31 * result + byteBuf.getByte(i); + } + return result; + } +} diff --git a/bytes/src/main/java/org/apache/tuweni/v2/bytes/ByteBufferWrappingBytes.java b/bytes/src/main/java/org/apache/tuweni/v2/bytes/ByteBufferWrappingBytes.java new file mode 100644 index 000000000..530acdfd1 --- /dev/null +++ b/bytes/src/main/java/org/apache/tuweni/v2/bytes/ByteBufferWrappingBytes.java @@ -0,0 +1,144 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static org.apache.tuweni.v2.bytes.Utils.checkArgument; +import static org.apache.tuweni.v2.bytes.Utils.checkElementIndex; +import static org.apache.tuweni.v2.bytes.Utils.checkLength; + +import java.nio.ByteBuffer; + +class ByteBufferWrappingBytes extends Bytes { + + protected final ByteBuffer byteBuffer; + protected final int offset; + + ByteBufferWrappingBytes(ByteBuffer byteBuffer) { + this(byteBuffer, 0, byteBuffer.limit()); + } + + ByteBufferWrappingBytes(ByteBuffer byteBuffer, int offset, int length) { + super(length); + this.byteBuffer = byteBuffer; + this.offset = offset; + } + + @Override + public int getInt(int i) { + return byteBuffer.getInt(offset + i); + } + + @Override + public long getLong(int i) { + return byteBuffer.getLong(offset + i); + } + + @Override + public byte get(int i) { + return byteBuffer.get(offset + i); + } + + @Override + public Bytes slice(int i, int length) { + if (i == 0 && length == size()) { + return this; + } + if (length == 0) { + return Bytes.EMPTY; + } + + checkArgument(length >= 0, "Invalid negative length"); + int bufferLength = byteBuffer.capacity(); + if (bufferLength > 0) { + checkElementIndex(offset + i, bufferLength); + } + checkLength(bufferLength, offset + i, length); + + return new ByteBufferWrappingBytes(byteBuffer, offset + i, length); + } + + @Override + public MutableBytes mutableCopy() { + return MutableBytes.fromByteBuffer(byteBuffer, offset, size()); + } + + @Override + public void appendTo(ByteBuffer byteBuffer) { + byteBuffer.put(this.byteBuffer); + } + + private byte[] toArray() { + byte[] array = new byte[size()]; + for (int i = 0; i < size(); i++) { + array[i] = byteBuffer.get(i + offset); + } + return array; + } + + @Override + public byte[] toArrayUnsafe() { + if (!byteBuffer.hasArray()) { + return toArray(); + } + byte[] array = byteBuffer.array(); + if (byteBuffer.limit() != size() || byteBuffer.arrayOffset() != offset) { + return toArray(); + } + return array; + } + + @Override + protected void and(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + // TODO: There is a chance for implementing with SIMD - see toArrayUnsafe() + bytesArray[offset + i] = (byte) (byteBuffer.get(this.offset + i) & bytesArray[offset + i]); + } + } + + @Override + protected void or(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + // TODO: There is a chance for implementing with SIMD - see toArrayUnsafe() + bytesArray[offset + i] = (byte) (byteBuffer.get(this.offset + i) | bytesArray[offset + i]); + } + } + + @Override + protected void xor(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + // TODO: There is a chance for implementing with SIMD - see toArrayUnsafe() + bytesArray[offset + i] = (byte) (byteBuffer.get(this.offset + i) ^ bytesArray[offset + i]); + } + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Bytes other)) { + return false; + } + + if (this.size() != other.size()) { + return false; + } + + for (int i = 0; i < size(); i++) { + if (byteBuffer.get(offset + i) != other.get(i)) { + return false; + } + } + + return true; + } + + @Override + protected int computeHashcode() { + int result = 1; + for (int i = 0; i < size(); i++) { + result = 31 * result + byteBuffer.get(offset + i); + } + return result; + } +} diff --git a/bytes/src/main/java/org/apache/tuweni/v2/bytes/Bytes.java b/bytes/src/main/java/org/apache/tuweni/v2/bytes/Bytes.java new file mode 100644 index 000000000..9c8eeb7e7 --- /dev/null +++ b/bytes/src/main/java/org/apache/tuweni/v2/bytes/Bytes.java @@ -0,0 +1,1349 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static java.lang.String.format; +import static java.nio.ByteOrder.BIG_ENDIAN; +import static org.apache.tuweni.v2.bytes.Utils.checkArgument; +import static org.apache.tuweni.v2.bytes.Utils.checkElementIndex; +import static org.apache.tuweni.v2.bytes.Utils.checkLength; +import static org.apache.tuweni.v2.bytes.Utils.checkNotNull; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.math.BigInteger; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ReadOnlyBufferException; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.List; +import java.util.Random; + +import io.netty.buffer.ByteBuf; +import io.vertx.core.buffer.Buffer; + +/** + * A value made of bytes. + * + *

This class makes no thread-safety guarantee, and a {@link Bytes} value is generally not thread + * safe. However, specific implementations may be thread-safe, e.g., MutableBytes. + */ +public abstract class Bytes implements Comparable { + + public static final String HEX_CODE_AS_STRING = "0123456789abcdef"; + + /** The empty value (with 0 bytes). */ + public static Bytes EMPTY = wrap(new byte[0]); + + protected Integer hashCode; + protected int size; + + protected Bytes(final int size) { + this.size = size; + } + + /** + * Wrap the provided byte array as a {@link Bytes} value. + * + *

Note that value is not copied and thus any future update to {@code value} will be reflected + * in the returned value. + * + * @param value The value to wrap. + * @return A {@link Bytes} value wrapping {@code value}. + */ + public static Bytes wrap(byte[] value) { + return wrap(value, 0, value.length); + } + + /** + * Wrap a slice of a byte array as a {@link Bytes} value. + * + *

Note that value is not copied and thus any future update to {@code value} within the slice + * will be reflected in the returned value. + * + * @param value The value to wrap. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned + * value. In other words, you will have {@code wrap(value, o, l).get(0) == value[o]}. + * @param length The length of the resulting value. + * @return A {@link Bytes} value that expose the bytes of {@code value} from {@code offset} + * (inclusive) to {@code offset + length} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.length > 0 && offset >= + * value.length)}. + * @throws IllegalArgumentException if {@code length < 0 || offset + length > value.length}. + */ + public static Bytes wrap(byte[] value, int offset, int length) { + checkNotNull(value); + checkArgument(length >= 0, "Invalid negative length"); + if (value.length > 0) { + checkElementIndex(offset, value.length); + } + checkLength(value.length, offset, length); + return new ArrayWrappingBytes(value, offset, length); + } + + /** + * Wrap a list of other values into a concatenated view. + * + *

Note that the values are not copied and thus any future update to the values will be + * reflected in the returned value. + * + * @param values The values to wrap. + * @return A value representing a view over the concatenation of all {@code values}. + * @throws IllegalArgumentException if the result overflows an int. + */ + public static Bytes wrap(Bytes... values) { + return ConcatenatedBytes.create(values); + } + + /** + * Wrap a list of other values into a concatenated view. + * + *

Note that the values are not copied and thus any future update to the values will be + * reflected in the returned value. + * + * @param values The values to wrap. + * @return A value representing a view over the concatenation of all {@code values}. + * @throws IllegalArgumentException if the result overflows an int. + */ + public static Bytes wrap(List values) { + return ConcatenatedBytes.create(values); + } + + /** + * Wrap a full Vert.x {@link Buffer} as a {@link Bytes} value. + * + *

Note that any change to the content of the buffer may be reflected in the returned value. + * + * @param buffer The buffer to wrap. + * @return A {@link Bytes} value. + */ + public static Bytes wrapBuffer(Buffer buffer) { + checkNotNull(buffer); + if (buffer.length() == 0) { + return EMPTY; + } + return new BufferWrappingBytes(buffer); + } + + /** + * Wrap a slice of a Vert.x {@link Buffer} as a {@link Bytes} value. + * + *

Note that any change to the content of the buffer may be reflected in the returned value. + * + * @param buffer The buffer to wrap. + * @param offset The offset in {@code buffer} from which to expose the bytes in the returned + * value. That is, {@code wrapBuffer(buffer, i, 1).get(0) == buffer.getByte(i)}. + * @param size The size of the returned value. + * @return A {@link Bytes} value. + * @throws IndexOutOfBoundsException if {@code offset < 0 || (buffer.length() > 0 && offset >= + * buffer.length())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + length > buffer.length()}. + */ + public static Bytes wrapBuffer(Buffer buffer, int offset, int size) { + checkNotNull(buffer); + if (size == 0) { + return EMPTY; + } + checkArgument(size >= 0, "Invalid negative length"); + int bufferLength = buffer.length(); + checkElementIndex(offset, bufferLength); + checkLength(bufferLength, offset, size); + return new BufferWrappingBytes(buffer, offset, size); + } + + /** + * Wrap a full Netty {@link ByteBuf} as a {@link Bytes} value. + * + *

Note that any change to the content of the byteBuf may be reflected in the returned value. + * + * @param byteBuf The {@link ByteBuf} to wrap. + * @return A {@link Bytes} value. + */ + public static Bytes wrapByteBuf(ByteBuf byteBuf) { + checkNotNull(byteBuf); + if (byteBuf.capacity() == 0) { + return EMPTY; + } + return new ByteBufWrappingBytes(byteBuf); + } + + /** + * Wrap a slice of a Netty {@link ByteBuf} as a {@link Bytes} value. + * + *

Note that any change to the content of the buffer may be reflected in the returned value. + * + * @param byteBuf The {@link ByteBuf} to wrap. + * @param offset The offset in {@code byteBuf} from which to expose the bytes in the returned + * value. That is, {@code wrapByteBuf(byteBuf, i, 1).get(0) == byteBuf.getByte(i)}. + * @param size The size of the returned value. + * @return A {@link Bytes} value. + * @throws IndexOutOfBoundsException if {@code offset < 0 || (byteBuf.capacity() > 0 && offset >= + * byteBuf.capacity())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + length > byteBuf.capacity()}. + */ + public static Bytes wrapByteBuf(ByteBuf byteBuf, int offset, int size) { + checkNotNull(byteBuf); + if (size == 0) { + return EMPTY; + } + checkArgument(size >= 0, "Invalid negative length"); + int bufferLength = byteBuf.capacity(); + checkElementIndex(offset, bufferLength); + checkLength(bufferLength, offset, size); + + return new ByteBufWrappingBytes(byteBuf, offset, size); + } + + /** + * Wrap a full Java NIO {@link ByteBuffer} as a {@link Bytes} value. + * + *

Note that any change to the content of the byteBuf may be reflected in the returned value. + * + * @param byteBuffer The {@link ByteBuffer} to wrap. + * @return A {@link Bytes} value. + */ + public static Bytes wrapByteBuffer(ByteBuffer byteBuffer) { + checkNotNull(byteBuffer); + if (byteBuffer.limit() == 0) { + return EMPTY; + } + return new ByteBufferWrappingBytes(byteBuffer); + } + + /** + * Wrap a slice of a Java NIO {@link ByteBuf} as a {@link Bytes} value. + * + *

Note that any change to the content of the buffer may be reflected in the returned value. + * + * @param byteBuffer The {@link ByteBuffer} to wrap. + * @param offset The offset in {@code byteBuffer} from which to expose the bytes in the returned + * value. That is, {@code wrapByteBuffer(byteBuffer, i, 1).get(0) == byteBuffer.getByte(i)}. + * @param size The size of the returned value. + * @return A {@link Bytes} value. + * @throws IndexOutOfBoundsException if {@code offset < 0 || (byteBuffer.limit() > 0 && offset >= + * byteBuf.limit())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + length > byteBuffer.limit()}. + */ + public static Bytes wrapByteBuffer(ByteBuffer byteBuffer, int offset, int size) { + checkNotNull(byteBuffer); + if (size == 0) { + return EMPTY; + } + checkArgument(size >= 0, "Invalid negative length"); + int bufferLength = byteBuffer.capacity(); + if (bufferLength > 0) { + checkElementIndex(offset, bufferLength); + } + checkLength(bufferLength, offset, size); + return new ByteBufferWrappingBytes(byteBuffer, offset, size); + } + + /** + * Create a value that contains the specified bytes in their specified order. + * + * @param bytes The bytes that must compose the returned value. + * @return A value containing the specified bytes. + */ + public static Bytes of(byte... bytes) { + return wrap(bytes); + } + + /** + * Create a value that contains the specified bytes in their specified order. + * + * @param bytes The bytes. + * @return A value containing bytes are the one from {@code bytes}. + * @throws IllegalArgumentException if any of the specified would be truncated when storing as a + * byte. + */ + public static Bytes of(int... bytes) { + byte[] result = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + int b = bytes[i]; + checkArgument(b == (((byte) b) & 0xff), "%sth value %s does not fit a byte", i + 1, b); + result[i] = (byte) b; + } + return Bytes.wrap(result); + } + + /** + * Return a 2-byte value corresponding to the provided value interpreted as an unsigned short. + * + * @param value The value, which must be no larger than an unsigned short. + * @return A 2 bytes value corresponding to {@code value}. + * @throws IllegalArgumentException if {@code value < 0} or {@code value} is too big to fit an + * unsigned 2-byte short (that is, if {@code value >= (1 << 16)}). + */ + public static Bytes ofUnsignedShort(int value) { + return ofUnsignedShort(value, BIG_ENDIAN); + } + + /** + * Return a 2-byte value corresponding to the provided value interpreted as an unsigned short. + * + * @param value The value, which must be no larger than an unsigned short. + * @param order The byte-order for the integer encoding. + * @return A 2 bytes value corresponding to {@code value}. + * @throws IllegalArgumentException if {@code value < 0} or {@code value} is too big to fit an + * unsigned 2-byte short (that is, if {@code value >= (1 << 16)}). + */ + public static Bytes ofUnsignedShort(int value, ByteOrder order) { + checkArgument( + value >= 0 && value <= BytesValues.MAX_UNSIGNED_SHORT, + "Value %s cannot be represented as an unsigned short (it is negative or too big)", + value); + byte[] res = new byte[2]; + if (order == BIG_ENDIAN) { + res[0] = (byte) ((value >> 8) & 0xFF); + res[1] = (byte) (value & 0xFF); + } else { + res[0] = (byte) (value & 0xFF); + res[1] = (byte) ((value >> 8) & 0xFF); + } + return Bytes.wrap(res); + } + + /** + * Return a 4-byte value corresponding to the provided value interpreted as an unsigned int. + * + * @param value The value, which must be no larger than an unsigned int. + * @return A 4 bytes value corresponding to {@code value}. + * @throws IllegalArgumentException if {@code value < 0} or {@code value} is too big to fit an + * unsigned 4-byte int (that is, if {@code value >= (1L << 32)}). + */ + public static Bytes ofUnsignedInt(long value) { + return ofUnsignedInt(value, BIG_ENDIAN); + } + + /** + * Return a 4-byte value corresponding to the provided value interpreted as an unsigned int. + * + * @param value The value, which must be no larger than an unsigned int. + * @param order The byte-order for the integer encoding. + * @return A 4 bytes value corresponding to the encoded {@code value}. + * @throws IllegalArgumentException if {@code value < 0} or {@code value} is too big to fit an + * unsigned 4-byte int (that is, if {@code value >= (1L << 32)}). + */ + public static Bytes ofUnsignedInt(long value, ByteOrder order) { + checkArgument( + value >= 0 && value <= BytesValues.MAX_UNSIGNED_INT, + "Value %s cannot be represented as an unsigned int (it is negative or too big)", + value); + byte[] res = new byte[4]; + if (order == BIG_ENDIAN) { + res[0] = (byte) ((value >> 24) & 0xFF); + res[1] = (byte) ((value >> 16) & 0xFF); + res[2] = (byte) ((value >> 8) & 0xFF); + res[3] = (byte) ((value) & 0xFF); + } else { + res[0] = (byte) ((value) & 0xFF); + res[1] = (byte) ((value >> 8) & 0xFF); + res[2] = (byte) ((value >> 16) & 0xFF); + res[3] = (byte) ((value >> 24) & 0xFF); + } + return Bytes.wrap(res); + } + + /** + * Return an 8-byte value corresponding to the provided value interpreted as an unsigned long. + * + * @param value The value, which will be interpreted as an unsigned long. + * @return A 8 bytes value corresponding to {@code value}. + * @throws IllegalArgumentException if {@code value < 0} or {@code value} is too big to fit an + * unsigned 8-byte int (that is, if {@code value >= (1L << 64)}). + */ + public static Bytes ofUnsignedLong(long value) { + return ofUnsignedLong(value, BIG_ENDIAN); + } + + /** + * Return an 8-byte value corresponding to the provided value interpreted as an unsigned long. + * + * @param value The value, which will be interpreted as an unsigned long. + * @param order The byte-order for the integer encoding. + * @return A 8 bytes value corresponding to {@code value}. + * @throws IllegalArgumentException if {@code value < 0} or {@code value} is too big to fit an + * unsigned 8-byte int (that is, if {@code value >= (1L << 64)}). + */ + public static Bytes ofUnsignedLong(long value, ByteOrder order) { + byte[] res = new byte[8]; + if (order == BIG_ENDIAN) { + res[0] = (byte) ((value >> 56) & 0xFF); + res[1] = (byte) ((value >> 48) & 0xFF); + res[2] = (byte) ((value >> 40) & 0xFF); + res[3] = (byte) ((value >> 32) & 0xFF); + res[4] = (byte) ((value >> 24) & 0xFF); + res[5] = (byte) ((value >> 16) & 0xFF); + res[6] = (byte) ((value >> 8) & 0xFF); + res[7] = (byte) (value & 0xFF); + } else { + res[0] = (byte) ((value) & 0xFF); + res[1] = (byte) ((value >> 8) & 0xFF); + res[2] = (byte) ((value >> 16) & 0xFF); + res[3] = (byte) ((value >> 24) & 0xFF); + res[4] = (byte) ((value >> 32) & 0xFF); + res[5] = (byte) ((value >> 40) & 0xFF); + res[6] = (byte) ((value >> 48) & 0xFF); + res[7] = (byte) ((value >> 56) & 0xFF); + } + return Bytes.wrap(res); + } + + /** + * Return the smallest bytes value whose bytes correspond to the provided long. That is, the + * returned value may be of size less than 8 if the provided long has leading zero bytes. + * + * @param value The long from which to create the bytes value. + * @return The minimal bytes representation corresponding to {@code l}. + */ + public static Bytes minimalBytes(long value) { + if (value == 0) { + return Bytes.EMPTY; + } + + int zeros = Long.numberOfLeadingZeros(value); + int resultBytes = 8 - (zeros / 8); + + byte[] result = new byte[resultBytes]; + int shift = 0; + for (int i = 0; i < resultBytes; i++) { + result[resultBytes - i - 1] = (byte) ((value >> shift) & 0xFF); + shift += 8; + } + return Bytes.wrap(result); + } + + /** + * Parse a hexadecimal string into a {@link Bytes} value. + * + *

This method is lenient in that {@code str} may of an odd length, in which case it will + * behave exactly as if it had an additional 0 in front. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal + * representation. + */ + public static Bytes fromHexStringLenient(CharSequence str) { + checkNotNull(str); + return BytesValues.fromHexString(str, -1, true); + } + + /** + * Parse a hexadecimal string into a {@link Bytes} value of the provided size. + * + *

This method allows for {@code str} to have an odd length, in which case it will behave + * exactly as if it had an additional 0 in front. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". + * @param destinationSize The size of the returned value, which must be big enough to hold the + * bytes represented by {@code str}. If it is strictly bigger those bytes from {@code str}, + * the returned value will be left padded with zeros. + * @return A value of size {@code destinationSize} corresponding to {@code str} potentially + * left-padded. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal + * representation, represents more bytes than {@code destinationSize} or {@code + * destinationSize < 0}. + */ + public static Bytes fromHexStringLenient(CharSequence str, int destinationSize) { + checkNotNull(str); + checkArgument(destinationSize >= 0, "Invalid negative destination size %s", destinationSize); + return BytesValues.fromHexString(str, destinationSize, true); + } + + /** + * Parse a hexadecimal string into a {@link Bytes} value. + * + *

This method requires that {@code str} have an even length. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal + * representation, or is of an odd length. + */ + public static Bytes fromHexString(CharSequence str) { + checkNotNull(str); + return BytesValues.fromHexString(str, -1, false); + } + + /** + * Parse a hexadecimal string into a {@link Bytes} value. + * + *

This method requires that {@code str} have an even length. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". + * @param destinationSize The size of the returned value, which must be big enough to hold the + * bytes represented by {@code str}. If it is strictly bigger those bytes from {@code str}, + * the returned value will be left padded with zeros. + * @return A value of size {@code destinationSize} corresponding to {@code str} potentially + * left-padded. + * @throws IllegalArgumentException if {@code str} does correspond to a valid hexadecimal + * representation, or is of an odd length. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal + * representation, or is of an odd length, or represents more bytes than {@code + * destinationSize} or {@code destinationSize < 0}. + */ + public static Bytes fromHexString(CharSequence str, int destinationSize) { + checkNotNull(str); + checkArgument(destinationSize >= 0, "Invalid negative destination size %s", destinationSize); + return BytesValues.fromHexString(str, destinationSize, false); + } + + /** + * Parse a base 64 string into a {@link Bytes} value. + * + * @param str The base 64 string to parse. + * @return The value corresponding to {@code str}. + */ + public static Bytes fromBase64String(CharSequence str) { + return Bytes.wrap(Base64.getDecoder().decode(str.toString())); + } + + /** + * Generate random bytes. + * + * @param size The number of bytes to generate. + * @return A value containing the desired number of random bytes. + */ + public static Bytes random(int size) { + return random(size, new SecureRandom()); + } + + /** + * Generate random bytes. + * + * @param size The number of bytes to generate. + * @param generator The generator for random bytes. + * @return A value containing the desired number of random bytes. + */ + public static Bytes random(int size, Random generator) { + byte[] array = new byte[size]; + generator.nextBytes(array); + return Bytes.wrap(array); + } + + /** + * Generate a bytes object filled with the same byte. + * + * @param b the byte to fill the Bytes with + * @param size the size of the object + * @return a value filled with a fixed byte + */ + public static Bytes repeat(byte b, int size) { + return new ConstantBytesValue(b, size); + } + + /** + * Provides the number of bytes this value represents. + * + * @return The number of bytes this value represents. + */ + public int size() { + return size; + } + + /** + * Retrieve a byte in this value. + * + * @param i The index of the byte to fetch within the value (0-indexed). + * @return The byte at index {@code i} in this value. + * @throws IndexOutOfBoundsException if {@code i < 0} or {i >= size()}. + */ + public abstract byte get(int i); + + /** + * Retrieve the 4 bytes starting at the provided index in this value as an integer. + * + * @param i The index from which to get the int, which must less than or equal to {@code size() - + * 4}. + * @return An integer whose value is the 4 bytes from this value starting at index {@code i}. + * @throws IndexOutOfBoundsException if {@code i < 0} or {@code i > size() - 4}. + */ + public int getInt(int i) { + return getInt(i, BIG_ENDIAN); + } + + /** + * Retrieve the 4 bytes starting at the provided index in this value as an integer. + * + * @param i The index from which to get the int, which must less than or equal to {@code size() - + * 4}. + * @param order The byte-order for decoding the integer. + * @return An integer whose value is the 4 bytes from this value starting at index {@code i}. + * @throws IndexOutOfBoundsException if {@code i < 0} or {@code i > size() - 4}. + */ + public int getInt(int i, ByteOrder order) { + int size = size(); + checkElementIndex(i, size); + if (i > (size - 4)) { + throw new IndexOutOfBoundsException( + format( + "Value of size %s has not enough bytes to read a 4 bytes int from index %s", + size, i)); + } + + int value = 0; + if (order == BIG_ENDIAN) { + value |= ((int) get(i) & 0xFF) << 24; + value |= ((int) get(i + 1) & 0xFF) << 16; + value |= ((int) get(i + 2) & 0xFF) << 8; + value |= ((int) get(i + 3) & 0xFF); + } else { + value |= ((int) get(i + 3) & 0xFF) << 24; + value |= ((int) get(i + 2) & 0xFF) << 16; + value |= ((int) get(i + 1) & 0xFF) << 8; + value |= ((int) get(i) & 0xFF); + } + return value; + } + + /** + * The value corresponding to interpreting these bytes as an integer. + * + * @return An value corresponding to this value interpreted as an integer. + * @throws IllegalArgumentException if {@code size() > 4}. + */ + public int toInt() { + return toInt(BIG_ENDIAN); + } + + /** + * The value corresponding to interpreting these bytes as an integer. + * + * @param order The byte-order for decoding the integer. + * @return An value corresponding to this value interpreted as an integer. + * @throws IllegalArgumentException if {@code size() > 4}. + */ + public int toInt(ByteOrder order) { + int size = size(); + checkArgument(size <= 4, "Value of size %s has more than 4 bytes", size()); + if (size == 0) { + return 0; + } + if (order == BIG_ENDIAN) { + int i = size; + int value = ((int) get(--i) & 0xFF); + if (i == 0) { + return value; + } + value |= ((int) get(--i) & 0xFF) << 8; + if (i == 0) { + return value; + } + value |= ((int) get(--i) & 0xFF) << 16; + if (i == 0) { + return value; + } + return value | ((int) get(--i) & 0xFF) << 24; + } else { + int i = 0; + int value = ((int) get(i) & 0xFF); + if (++i == size) { + return value; + } + value |= ((int) get(i++) & 0xFF) << 8; + if (i == size) { + return value; + } + value |= ((int) get(i++) & 0xFF) << 16; + if (i == size) { + return value; + } + return value | ((int) get(i) & 0xFF) << 24; + } + } + + /** + * Whether this value contains no bytes. + * + * @return true if the value contains no bytes + */ + public boolean isEmpty() { + return size() == 0; + } + + /** + * Retrieves the 8 bytes starting at the provided index in this value as a long. + * + * @param i The index from which to get the long, which must less than or equal to {@code size() - + * 8}. + * @return A long whose value is the 8 bytes from this value starting at index {@code i}. + * @throws IndexOutOfBoundsException if {@code i < 0} or {@code i > size() - 8}. + */ + public long getLong(int i) { + return getLong(i, BIG_ENDIAN); + } + + /** + * Retrieves the 8 bytes starting at the provided index in this value as a long. + * + * @param i The index from which to get the long, which must less than or equal to {@code size() - + * 8}. + * @param order The byte-order for decoding the integer. + * @return A long whose value is the 8 bytes from this value starting at index {@code i}. + * @throws IndexOutOfBoundsException if {@code i < 0} or {@code i > size() - 8}. + */ + public long getLong(int i, ByteOrder order) { + int size = size(); + checkElementIndex(i, size); + if (i > (size - 8)) { + throw new IndexOutOfBoundsException( + format( + "Value of size %s has not enough bytes to read a 8 bytes long from index %s", + size, i)); + } + + long value = 0; + if (order == BIG_ENDIAN) { + value |= ((long) get(i) & 0xFF) << 56; + value |= ((long) get(i + 1) & 0xFF) << 48; + value |= ((long) get(i + 2) & 0xFF) << 40; + value |= ((long) get(i + 3) & 0xFF) << 32; + value |= ((long) get(i + 4) & 0xFF) << 24; + value |= ((long) get(i + 5) & 0xFF) << 16; + value |= ((long) get(i + 6) & 0xFF) << 8; + value |= ((long) get(i + 7) & 0xFF); + } else { + value |= ((long) get(i + 7) & 0xFF) << 56; + value |= ((long) get(i + 6) & 0xFF) << 48; + value |= ((long) get(i + 5) & 0xFF) << 40; + value |= ((long) get(i + 4) & 0xFF) << 32; + value |= ((long) get(i + 3) & 0xFF) << 24; + value |= ((long) get(i + 2) & 0xFF) << 16; + value |= ((long) get(i + 1) & 0xFF) << 8; + value |= ((long) get(i) & 0xFF); + } + return value; + } + + /** + * The value corresponding to interpreting these bytes as a long. + * + * @return An value corresponding to this value interpreted as a long. + * @throws IllegalArgumentException if {@code size() > 8}. + */ + public long toLong() { + return toLong(BIG_ENDIAN); + } + + /** + * The value corresponding to interpreting these bytes as a long. + * + * @param order The byte-order for decoding the integer. + * @return An value corresponding to this value interpreted as a long. + * @throws IllegalArgumentException if {@code size() > 8}. + */ + public long toLong(ByteOrder order) { + int size = size(); + checkArgument(size <= 8, "Value of size %s has more than 8 bytes", size()); + if (size == 0) { + return 0; + } + if (order == BIG_ENDIAN) { + int i = size; + long value = ((long) get(--i) & 0xFF); + if (i == 0) { + return value; + } + value |= ((long) get(--i) & 0xFF) << 8; + if (i == 0) { + return value; + } + value |= ((long) get(--i) & 0xFF) << 16; + if (i == 0) { + return value; + } + value |= ((long) get(--i) & 0xFF) << 24; + if (i == 0) { + return value; + } + value |= ((long) get(--i) & 0xFF) << 32; + if (i == 0) { + return value; + } + value |= ((long) get(--i) & 0xFF) << 40; + if (i == 0) { + return value; + } + value |= ((long) get(--i) & 0xFF) << 48; + if (i == 0) { + return value; + } + return value | ((long) get(--i) & 0xFF) << 56; + } else { + int i = 0; + long value = ((long) get(i) & 0xFF); + if (++i == size) { + return value; + } + value |= ((long) get(i) & 0xFF) << 8; + if (++i == size) { + return value; + } + value |= ((long) get(i) & 0xFF) << 16; + if (++i == size) { + return value; + } + value |= ((long) get(i) & 0xFF) << 24; + if (++i == size) { + return value; + } + value |= ((long) get(i) & 0xFF) << 32; + if (++i == size) { + return value; + } + value |= ((long) get(i) & 0xFF) << 40; + if (++i == size) { + return value; + } + value |= ((long) get(i) & 0xFF) << 48; + if (++i == size) { + return value; + } + return value | ((long) get(i) & 0xFF) << 56; + } + } + + /** + * The BigInteger corresponding to interpreting these bytes as a two's-complement signed integer. + * + * @return A {@link BigInteger} corresponding to interpreting these bytes as a two's-complement + * signed integer. + */ + public BigInteger toSignedBigInteger() { + return toSignedBigInteger(BIG_ENDIAN); + } + + /** + * The BigInteger corresponding to interpreting these bytes as a two's-complement signed integer. + * + * @param order The byte-order for decoding the integer. + * @return A {@link BigInteger} corresponding to interpreting these bytes as a two's-complement + * signed integer. + */ + public BigInteger toSignedBigInteger(ByteOrder order) { + if (size() == 0) { + return BigInteger.ZERO; + } + return new BigInteger( + (order == BIG_ENDIAN) ? toArrayUnsafe() : mutableCopy().reverse().toArray()); + } + + /** + * The BigInteger corresponding to interpreting these bytes as a two's-complement signed integer. + * + * @return A {@link BigInteger} corresponding to interpreting these bytes as a two's-complement + * signed integer. + */ + public BigInteger toBigInteger() { + return toBigInteger(BIG_ENDIAN); + } + + /** + * The BigInteger corresponding to interpreting these bytes as a two's-complement signed integer. + * + * @param order The byte-order for decoding the integer. + * @return A {@link BigInteger} corresponding to interpreting these bytes as a two's-complement + * signed integer. + */ + public BigInteger toBigInteger(ByteOrder order) { + if (size() == 0) { + return BigInteger.ZERO; + } + return new BigInteger( + (order == BIG_ENDIAN) ? toArrayUnsafe() : mutableCopy().reverse().toArray()); + } + + /** + * The BigInteger corresponding to interpreting these bytes as an unsigned integer. + * + * @return A positive (or zero) {@link BigInteger} corresponding to interpreting these bytes as an + * unsigned integer. + */ + public BigInteger toUnsignedBigInteger() { + return toUnsignedBigInteger(BIG_ENDIAN); + } + + /** + * The BigInteger corresponding to interpreting these bytes as an unsigned integer. + * + * @param order The byte-order for decoding the integer. + * @return A positive (or zero) {@link BigInteger} corresponding to interpreting these bytes as an + * unsigned integer. + */ + public BigInteger toUnsignedBigInteger(ByteOrder order) { + if (size() == 0) { + return BigInteger.ZERO; + } + return new BigInteger( + 1, (order == BIG_ENDIAN) ? toArrayUnsafe() : mutableCopy().reverse().toArray()); + } + + /** + * Whether this value has only zero bytes. + * + * @return {@code true} if all the bits of this value are zeros. + */ + public boolean isZero() { + for (int i = size() - 1; i >= 0; --i) { + if (get(i) != 0) return false; + } + return true; + } + + /** + * Whether the bytes start with a zero bit value. + * + * @return true if the first bit equals zero + */ + public boolean hasLeadingZero() { + return size() > 0 && (get(0) & 0x80) == 0; + } + + /** + * Provides the number of zero bits preceding the highest-order ("leftmost") one-bit, or {@code + * size() * 8} if all bits * are zero. + * + * @return The number of zero bits preceding the highest-order ("leftmost") one-bit, or {@code + * size() * 8} if all bits are zero. + */ + public int numberOfLeadingZeros() { + int size = size(); + for (int i = 0; i < size; i++) { + byte b = get(i); + if (b == 0) { + continue; + } + + return (i * 8) + Integer.numberOfLeadingZeros(b & 0xFF) - 3 * 8; + } + return size * 8; + } + + /** + * Whether the bytes start with a zero byte value. + * + * @return true if the first byte equals zero + */ + public boolean hasLeadingZeroByte() { + return size() > 0 && get(0) == 0; + } + + /** + * Provides the number of leading zero bytes of the value + * + * @return The number of leading zero bytes of the value. + */ + public int numberOfLeadingZeroBytes() { + int size = size(); + for (int i = 0; i < size; i++) { + if (get(i) != 0) { + return i; + } + } + return size; + } + + /** + * Provides the number of trailing zero bytes of the value. + * + * @return The number of trailing zero bytes of the value. + */ + public int numberOfTrailingZeroBytes() { + int size = size(); + for (int i = size; i >= 1; i--) { + if (get(i - 1) != 0) { + return size - i; + } + } + return size; + } + + /** + * Provides the number of bits following and including the highest-order ("leftmost") one-bit, or + * zero if all bits are zero. + * + * @return The number of bits following and including the highest-order ("leftmost") one-bit, or + * zero if all bits are zero. + */ + public int bitLength() { + int size = size(); + for (int i = 0; i < size; i++) { + byte b = get(i); + if (b == 0) continue; + + return (size * 8) - (i * 8) - (Integer.numberOfLeadingZeros(b & 0xFF) - 3 * 8); + } + return 0; + } + + /** + * Create a new value representing (a view of) a slice of the bytes of this value. + * + *

Please note that the resulting slice is only a view and as such maintains a link to the + * underlying full value. So holding a reference to the returned slice may hold more memory than + * the slice represents. + * + * @param i The start index for the slice. + * @return A new value providing a view over the bytes from index {@code i} (included) to the end. + * @throws IndexOutOfBoundsException if {@code i < 0}. + */ + public Bytes slice(int i) { + if (i == 0) { + return this; + } + int size = size(); + if (i >= size) { + return EMPTY; + } + return slice(i, size - i); + } + + /** + * Create a new value representing (a view of) a slice of the bytes of this value. + * + *

Please note that the resulting slice is only a view and as such maintains a link to the + * underlying full value. So holding a reference to the returned slice may hold more memory than + * the slide represents. + * + * @param i The start index for the slice. + * @param length The length of the resulting value. + * @return A new value providing a view over the bytes from index {@code i} (included) to {@code i + * + length} (excluded). + * @throws IllegalArgumentException if {@code length < 0}. + * @throws IndexOutOfBoundsException if {@code i < 0} or {i >= size()} or {i + length > size()} . + */ + public abstract Bytes slice(int i, int length); + + /** + * Return a new mutable value initialized with the content of this value. + * + * @return A mutable copy of this value. This will copy bytes, modifying the returned value will + * not modify this value. + */ + public abstract MutableBytes mutableCopy(); + + /** + * Append the bytes of this value to the {@link ByteBuffer}. + * + * @param byteBuffer The {@link ByteBuffer} to which to append this value. + * @throws BufferOverflowException If the writer attempts to write more than the provided buffer + * can hold. + * @throws ReadOnlyBufferException If the provided buffer is read-only. + */ + public void appendTo(ByteBuffer byteBuffer) { + checkNotNull(byteBuffer); + for (int i = 0; i < size(); i++) { + byteBuffer.put(get(i)); + } + } + + /** + * Append the bytes of this value to the provided Vert.x {@link Buffer}. + * + *

Note that since a Vert.x {@link Buffer} will grow as necessary, this method never fails. + * + * @param buffer The {@link Buffer} to which to append this value. + */ + public void appendTo(Buffer buffer) { + checkNotNull(buffer); + for (int i = 0; i < size(); i++) { + buffer.appendByte(get(i)); + } + } + + /** + * Append this value as a sequence of hexadecimal characters. + * + * @param appendable The appendable + * @param The appendable type. + * @return The appendable. + */ + public T appendHexTo(T appendable) { + try { + appendable.append(toFastHex(false)); + return appendable; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public String toFastHex(boolean prefix) { + + int offset = prefix ? 2 : 0; + + int resultSize = (size() * 2) + offset; + + char[] result = new char[resultSize]; + + if (prefix) { + result[0] = '0'; + result[1] = 'x'; + } + + for (int i = 0; i < size(); i++) { + byte b = get(i); + int pos = i * 2; + result[pos + offset] = Bytes.HEX_CODE_AS_STRING.charAt(b >> 4 & 15); + result[pos + offset + 1] = Bytes.HEX_CODE_AS_STRING.charAt(b & 15); + } + + return new String(result); + } + + /** + * Return the number of bytes in common between this set of bytes and another. + * + * @param other The bytes to compare to. + * @return The number of common bytes. + */ + public int commonPrefixLength(Bytes other) { + checkNotNull(other); + int ourSize = size(); + int otherSize = other.size(); + int i = 0; + while (i < ourSize && i < otherSize && get(i) == other.get(i)) { + i++; + } + return i; + } + + /** + * Return a slice over the common prefix between this set of bytes and another. + * + * @param other The bytes to compare to. + * @return A slice covering the common prefix. + */ + public Bytes commonPrefix(Bytes other) { + return slice(0, commonPrefixLength(other)); + } + + /** + * Return a slice of representing the same value but without any leading zero bytes. + * + * @return {@code value} if its left-most byte is non zero, or a slice that exclude any leading + * zero bytes. + */ + public Bytes trimLeadingZeros() { + int size = size(); + for (int i = 0; i < size; i++) { + if (get(i) != 0) { + return slice(i); + } + } + return Bytes.EMPTY; + } + + /** + * Return a slice of representing the same value but without any trailing zero bytes. + * + * @return {@code value} if its right-most byte is non zero, or a slice that exclude any trailing + * zero bytes. + */ + public Bytes trimTrailingZeros() { + int size = size(); + for (int i = size - 1; i >= 0; i--) { + if (get(i) != 0) { + return slice(0, i + 1); + } + } + return Bytes.EMPTY; + } + + /** + * Update the provided message digest with the bytes of this value. + * + * @param digest The digest to update. + */ + public void update(MessageDigest digest) { + checkNotNull(digest); + digest.update(toArrayUnsafe()); + } + + /** + * Get the bytes represented by this value as byte array. + * + *

This may avoid allocating a new array and directly return the backing array of this value if + * said value is array backed and doing so is possible. As such, modifications to the returned + * array may or may not impact this value. As such, this method should be used with care and hence + * the "unsafe" moniker. + * + * @return A byte array with the same content than this value, which may or may not be the direct + * backing of this value. + */ + public abstract byte[] toArrayUnsafe(); + + /** + * Provides this value represented as hexadecimal, starting with "0x". + * + * @return This value represented as hexadecimal, starting with "0x". + */ + public String toHexString() { + return toFastHex(true); + } + + /** + * Provides this value represented as hexadecimal, with no prefix + * + * @return This value represented as hexadecimal, with no prefix. + */ + public String toUnprefixedHexString() { + return toFastHex(false); + } + + public String toEllipsisHexString() { + int size = size(); + if (size < 6) { + return toHexString(); + } + char[] result = new char[12]; + result[0] = '0'; + result[1] = 'x'; + for (int i = 0; i < 2; i++) { + byte b = get(i); + int pos = (i * 2) + 2; + result[pos] = Bytes.HEX_CODE_AS_STRING.charAt(b >> 4 & 15); + result[pos + 1] = Bytes.HEX_CODE_AS_STRING.charAt(b & 15); + } + result[6] = '.'; + result[7] = '.'; + for (int i = 0; i < 2; i++) { + byte b = get(i + size - 2); + int pos = (i * 2) + 8; + result[pos] = Bytes.HEX_CODE_AS_STRING.charAt(b >> 4 & 15); + result[pos + 1] = Bytes.HEX_CODE_AS_STRING.charAt(b & 15); + } + return new String(result); + } + + /** + * Provides this value represented as a minimal hexadecimal string (without any leading zero) + * + * @return This value represented as a minimal hexadecimal string (without any leading zero). + */ + public String toShortHexString() { + String hex = toFastHex(false); + + int i = 0; + while (i < hex.length() && hex.charAt(i) == '0') { + i++; + } + return "0x" + hex.substring(i); + } + + /** + * Provides this value represented as a minimal hexadecimal string (without any leading zero, + * except if it's valued zero or empty, in which case it returns 0x0). + * + * @return This value represented as a minimal hexadecimal string (without any leading zero, + * except if it's valued zero or empty, in which case it returns 0x0). + */ + public String toQuantityHexString() { + if (Bytes.EMPTY.equals(this)) { + return "0x0"; + } + String hex = toFastHex(false); + + int i = 0; + while (i < hex.length() - 1 && hex.charAt(i) == '0') { + i++; + } + return "0x" + hex.substring(i); + } + + /** + * Provides this value represented as base 64 + * + * @return This value represented as base 64. + */ + public String toBase64String() { + return Base64.getEncoder().encodeToString(toArrayUnsafe()); + } + + @Override + public int compareTo(Bytes b) { + checkNotNull(b); + + int bitLength = bitLength(); + int sizeCmp = Integer.compare(bitLength, b.bitLength()); + if (sizeCmp != 0) { + return sizeCmp; + } + // same bitlength and is zeroes only, return 0. + if (bitLength == 0) { + return 0; + } + + for (int i = 0; i < size(); i++) { + int cmp = Integer.compare(get(i) & 0xff, b.get(i) & 0xff); + if (cmp != 0) { + return cmp; + } + } + return 0; + } + + /** + * Compare this value and the provided one for equality. + * + *

Two {@link Bytes} values are equal is they have contain the exact same bytes. + * + * @param obj The object to test for equality with. + * @return {@code true} if this value and {@code obj} are equal. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Bytes other)) { + return false; + } + + if (this.size() != other.size()) { + return false; + } + + for (int i = 0; i < size(); i++) { + if (this.get(i) != other.get(i)) { + return false; + } + } + + return true; + } + + protected int computeHashcode() { + int result = 1; + for (int i = 0; i < size(); i++) { + result = 31 * result + get(i); + } + return result; + } + + @Override + public int hashCode() { + if (this.hashCode == null) { + this.hashCode = computeHashcode(); + } + return this.hashCode; + } + + @Override + public String toString() { + return toHexString(); + } + + Bytes getImpl() { + return this; + } + + protected abstract void and(byte[] bytesArray, int offset, int length); + + protected abstract void or(byte[] bytesArray, int offset, int length); + + protected abstract void xor(byte[] bytesArray, int offset, int length); +} diff --git a/bytes/src/main/java/org/apache/tuweni/v2/bytes/Bytes32.java b/bytes/src/main/java/org/apache/tuweni/v2/bytes/Bytes32.java new file mode 100644 index 000000000..6a46f983a --- /dev/null +++ b/bytes/src/main/java/org/apache/tuweni/v2/bytes/Bytes32.java @@ -0,0 +1,235 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static org.apache.tuweni.v2.bytes.Utils.checkArgument; +import static org.apache.tuweni.v2.bytes.Utils.checkElementIndex; +import static org.apache.tuweni.v2.bytes.Utils.checkNotNull; + +import java.security.SecureRandom; +import java.util.Random; + +/** A {@link Bytes} value that is guaranteed to contain exactly 32 bytes. */ +public final class Bytes32 extends DelegatingBytes { + private static final int SIZE = 32; + + /** A {@code Bytes32} containing all zero bytes */ + public static final Bytes ZERO = fromByte((byte) 0); + + private Bytes32(Bytes delegate) { + super(delegate, SIZE); + } + + /** + * Generate a bytes object filled with the same byte. + * + * @param b the byte to fill the Bytes with + * @return a value filled with a fixed byte + */ + public static Bytes32 wrap(byte b) { + return new Bytes32(fromByte(b)); + } + + public static Bytes fromByte(byte b) { + return repeat(b, SIZE); + } + + /** + * Wrap the provided byte array, which must be of length 32, as a {@link Bytes32}. + * + *

Note that value is not copied, only wrapped, and thus any future update to {@code value} + * will be reflected in the returned value. + * + * @param bytes The bytes to wrap. + * @return A {@link Bytes32} wrapping {@code value}. + * @throws IllegalArgumentException if {@code value.length != 32}. + */ + public static Bytes32 wrap(byte[] bytes) { + return wrap(bytes, 0); + } + + public static Bytes fromArray(byte[] bytes) { + return fromArray(bytes, 0); + } + + /** + * Wrap a slice/sub-part of the provided array as a {@link Bytes32}. + * + *

Note that value is not copied, only wrapped, and thus any future update to {@code value} + * within the wrapped parts will be reflected in the returned value. + * + * @param bytes The bytes to wrap. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned + * value. In other words, you will have {@code wrap(value, i).get(0) == value[i]}. + * @return A {@link Bytes32} that exposes the bytes of {@code value} from {@code offset} + * (inclusive) to {@code offset + 32} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.length > 0 && offset >= + * value.length)}. + * @throws IllegalArgumentException if {@code value.length - offset != 32}. + */ + public static Bytes32 wrap(byte[] bytes, int offset) { + return new Bytes32(fromArray(bytes, offset)); + } + + public static Bytes fromArray(byte[] bytes, int offset) { + checkNotNull(bytes); + if (bytes.length == 0) { + return EMPTY; + } + checkElementIndex(offset, bytes.length); + checkLength(bytes, offset); + return new ArrayWrappingBytes(bytes, offset, SIZE); + } + + /** + * Wrap a the provided value, which must be of size 32, as a {@link Bytes32}. + * + *

Note that value is not copied, only wrapped, and thus any future update to {@code value} + * will be reflected in the returned value. + * + * @param value The bytes to wrap. + * @return A {@link Bytes32} that exposes the bytes of {@code value}. + * @throws IllegalArgumentException if {@code value.size() != 32}. + */ + public static Bytes32 wrap(Bytes value) { + checkNotNull(value); + checkArgument(value.size() == SIZE, "Expected %s bytes but got %s", SIZE, value.size()); + if (value instanceof Bytes32 bytes32) { + return bytes32; + } + return new Bytes32(value.getImpl()); + } + + /** + * Wrap a slice/sub-part of the provided value as a {@link Bytes32}. + * + *

Note that value is not copied, only wrapped, and thus any future update to {@code value} + * within the wrapped parts will be reflected in the returned value. + * + * @param value The bytes to wrap. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned + * value. In other words, you will have {@code wrap(value, i).get(0) == value.get(i)}. + * @return A {@link Bytes32} that exposes the bytes of {@code value} from {@code offset} + * (inclusive) to {@code offset + 32} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.size() > 0 && offset >= + * value.size())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + 32 > value.size()}. + */ + public static Bytes32 wrap(Bytes value, int offset) { + checkNotNull(value); + Bytes slice = value.slice(offset, SIZE); + if (slice instanceof Bytes32 bytes32) { + return bytes32; + } + checkArgument(slice.size() == SIZE, "Expected %s bytes but got %s", SIZE, slice.size()); + return new Bytes32(slice.getImpl()); + } + + public static Bytes fromBytes(Bytes value, int offset) { + checkNotNull(value); + Bytes slice = value.slice(offset, SIZE); + checkArgument(slice.size() == SIZE, "Expected %s bytes but got %s", SIZE, slice.size()); + return slice.getImpl(); + } + + /** + * Parse a hexadecimal string into a {@link Bytes32}. + * + *

This method is lenient in that {@code str} may of an odd length, in which case it will + * behave exactly as if it had an additional 0 in front. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". That + * representation may contain less than 32 bytes, in which case the result is left padded with + * zeros (see {@link #fromHexStringStrict} if this is not what you want). + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal + * representation or contains more than 32 bytes. + */ + public static Bytes32 wrapHexStringLenient(CharSequence str) { + return new Bytes32(fromHexStringLenient(str)); + } + + public static Bytes fromHexStringLenient(CharSequence str) { + checkNotNull(str); + return fromArray(BytesValues.fromRawHexString(str, SIZE, true)); + } + + /** + * Parse a hexadecimal string into a {@link Bytes32}. + * + *

This method is strict in that {@code str} must of an even length. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". That + * representation may contain less than 32 bytes, in which case the result is left padded with + * zeros (see {@link #fromHexStringStrict} if this is not what you want). + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal + * representation, is of an odd length, or contains more than 32 bytes. + */ + public static Bytes32 wrapHexString(CharSequence str) { + return new Bytes32(fromHexString(str)); + } + + public static Bytes fromHexString(CharSequence str) { + checkNotNull(str); + return fromArray(BytesValues.fromRawHexString(str, SIZE, false)); + } + + /** + * Generate random bytes. + * + * @return A value containing random bytes. + */ + public static Bytes32 wrapRandom() { + return new Bytes32(fromRandom()); + } + + public static Bytes fromRandom() { + return fromRandom(new SecureRandom()); + } + + /** + * Generate random bytes. + * + * @param generator The generator for random bytes. + * @return A value containing random bytes. + */ + public static Bytes32 wrapRandom(Random generator) { + return new Bytes32(fromRandom(generator)); + } + + public static Bytes fromRandom(Random generator) { + byte[] array = new byte[32]; + generator.nextBytes(array); + return fromArray(array); + } + + /** + * Parse a hexadecimal string into a {@link Bytes32}. + * + *

This method is extra strict in that {@code str} must of an even length and the provided + * representation must have exactly 32 bytes. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal + * representation, is of an odd length or does not contain exactly 32 bytes. + */ + public static Bytes32 wrapHexStringStrict(CharSequence str) { + return new Bytes32(fromHexStringStrict(str)); + } + + public static Bytes fromHexStringStrict(CharSequence str) { + checkNotNull(str); + return fromArray(BytesValues.fromRawHexString(str, -1, false)); + } + + private static void checkLength(byte[] bytes, int offset) { + Utils.checkArgument( + bytes.length - offset == SIZE, + "Expected %s bytes from offset %s but got %s", + SIZE, + offset, + bytes.length - offset); + } +} diff --git a/bytes/src/main/java/org/apache/tuweni/v2/bytes/Bytes48.java b/bytes/src/main/java/org/apache/tuweni/v2/bytes/Bytes48.java new file mode 100644 index 000000000..2a7453c6e --- /dev/null +++ b/bytes/src/main/java/org/apache/tuweni/v2/bytes/Bytes48.java @@ -0,0 +1,236 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static org.apache.tuweni.v2.bytes.Utils.checkArgument; +import static org.apache.tuweni.v2.bytes.Utils.checkElementIndex; +import static org.apache.tuweni.v2.bytes.Utils.checkNotNull; + +import java.security.SecureRandom; +import java.util.Random; + +/** A {@link Bytes} value that is guaranteed to contain exactly 48 bytes. */ +public final class Bytes48 extends DelegatingBytes { + /** The number of bytes in this value - i.e. 48 */ + public static final int SIZE = 48; + + /** A {@code Bytes48} containing all zero bytes */ + public static final Bytes ZERO = fromByte((byte) 0); + + private Bytes48(Bytes delegate) { + super(delegate, SIZE); + } + + /** + * Generate a bytes object filled with the same byte. + * + * @param b the byte to fill the Bytes with + * @return a value filled with a fixed byte + */ + public static Bytes48 wrap(byte b) { + return new Bytes48(fromByte(b)); + } + + public static Bytes fromByte(byte b) { + return repeat(b, SIZE); + } + + /** + * Wrap the provided byte array, which must be of length 48, as a {@link Bytes48}. + * + *

Note that value is not copied, only wrapped, and thus any future update to {@code value} + * will be reflected in the returned value. + * + * @param bytes The bytes to wrap. + * @return A {@link Bytes48} wrapping {@code value}. + * @throws IllegalArgumentException if {@code value.length != 48}. + */ + public static Bytes48 wrap(byte[] bytes) { + return wrap(bytes, 0); + } + + public static Bytes fromArray(byte[] bytes) { + return fromArray(bytes, 0); + } + + /** + * Wrap a slice/sub-part of the provided array as a {@link Bytes48}. + * + *

Note that value is not copied, only wrapped, and thus any future update to {@code value} + * within the wrapped parts will be reflected in the returned value. + * + * @param bytes The bytes to wrap. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned + * value. In other words, you will have {@code wrap(value, i).get(0) == value[i]}. + * @return A {@link Bytes48} that exposes the bytes of {@code value} from {@code offset} + * (inclusive) to {@code offset + 48} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.length > 0 && offset >= + * value.length)}. + * @throws IllegalArgumentException if {@code value.length - offset != 48}. + */ + public static Bytes48 wrap(byte[] bytes, int offset) { + return new Bytes48(fromArray(bytes, offset)); + } + + public static Bytes fromArray(byte[] bytes, int offset) { + checkNotNull(bytes); + if (bytes.length == 0) { + return EMPTY; + } + checkElementIndex(offset, bytes.length); + checkLength(bytes, offset); + return new ArrayWrappingBytes(bytes, offset, SIZE); + } + + /** + * Wrap a the provided value, which must be of size 48, as a {@link Bytes48}. + * + *

Note that value is not copied, only wrapped, and thus any future update to {@code value} + * will be reflected in the returned value. + * + * @param value The bytes to wrap. + * @return A {@link Bytes48} that exposes the bytes of {@code value}. + * @throws IllegalArgumentException if {@code value.size() != 48}. + */ + public static Bytes48 wrap(Bytes value) { + checkNotNull(value); + checkArgument(value.size() == SIZE, "Expected %s bytes but got %s", SIZE, value.size()); + if (value instanceof Bytes48 bytes48) { + return bytes48; + } + return new Bytes48(value.getImpl()); + } + + /** + * Wrap a slice/sub-part of the provided value as a {@link Bytes48}. + * + *

Note that value is not copied, only wrapped, and thus any future update to {@code value} + * within the wrapped parts will be reflected in the returned value. + * + * @param value The bytes to wrap. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned + * value. In other words, you will have {@code wrap(value, i).get(0) == value.get(i)}. + * @return A {@link Bytes48} that exposes the bytes of {@code value} from {@code offset} + * (inclusive) to {@code offset + 48} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.size() > 0 && offset >= + * value.size())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + 48 > value.size()}. + */ + public static Bytes48 wrap(Bytes value, int offset) { + checkNotNull(value); + Bytes slice = value.slice(offset, SIZE); + if (slice instanceof Bytes48 bytes48) { + return bytes48; + } + checkArgument(slice.size() == SIZE, "Expected %s bytes but got %s", SIZE, slice.size()); + return new Bytes48(slice.getImpl()); + } + + public static Bytes fromBytes(Bytes value, int offset) { + checkNotNull(value); + Bytes slice = value.slice(offset, SIZE); + checkArgument(slice.size() == SIZE, "Expected %s bytes but got %s", SIZE, slice.size()); + return slice.getImpl(); + } + + /** + * Parse a hexadecimal string into a {@link Bytes48}. + * + *

This method is lenient in that {@code str} may of an odd length, in which case it will + * behave exactly as if it had an additional 0 in front. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". That + * representation may contain less than 48 bytes, in which case the result is left padded with + * zeros (see {@link #fromHexStringStrict} if this is not what you want). + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal + * representation or contains more than 48 bytes. + */ + public static Bytes48 wrapHexStringLenient(CharSequence str) { + return new Bytes48(fromHexStringLenient(str)); + } + + public static Bytes fromHexStringLenient(CharSequence str) { + checkNotNull(str); + return fromArray(BytesValues.fromRawHexString(str, SIZE, true)); + } + + /** + * Parse a hexadecimal string into a {@link Bytes48}. + * + *

This method is strict in that {@code str} must of an even length. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". That + * representation may contain less than 48 bytes, in which case the result is left padded with + * zeros (see {@link #fromHexStringStrict} if this is not what you want). + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal + * representation, is of an odd length, or contains more than 48 bytes. + */ + public static Bytes48 wrapHexString(CharSequence str) { + return new Bytes48(fromHexString(str)); + } + + public static Bytes fromHexString(CharSequence str) { + checkNotNull(str); + return fromArray(BytesValues.fromRawHexString(str, SIZE, false)); + } + + /** + * Generate random bytes. + * + * @return A value containing random bytes. + */ + public static Bytes48 wrapRandom() { + return new Bytes48(fromRandom()); + } + + public static Bytes fromRandom() { + return fromRandom(new SecureRandom()); + } + + /** + * Generate random bytes. + * + * @param generator The generator for random bytes. + * @return A value containing random bytes. + */ + public static Bytes48 wrapRandom(Random generator) { + return new Bytes48(fromRandom(generator)); + } + + public static Bytes fromRandom(Random generator) { + byte[] array = new byte[48]; + generator.nextBytes(array); + return fromArray(array); + } + + /** + * Parse a hexadecimal string into a {@link Bytes48}. + * + *

This method is extra strict in that {@code str} must of an even length and the provided + * representation must have exactly 48 bytes. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal + * representation, is of an odd length or does not contain exactly 48 bytes. + */ + public static Bytes48 wrapHexStringStrict(CharSequence str) { + return new Bytes48(fromHexStringStrict(str)); + } + + public static Bytes fromHexStringStrict(CharSequence str) { + checkNotNull(str); + return fromArray(BytesValues.fromRawHexString(str, -1, false)); + } + + private static void checkLength(byte[] bytes, int offset) { + checkArgument( + bytes.length - offset == SIZE, + "Expected %s bytes from offset %s but got %s", + SIZE, + offset, + bytes.length - offset); + } +} diff --git a/bytes/src/main/java/org/apache/tuweni/v2/bytes/BytesValues.java b/bytes/src/main/java/org/apache/tuweni/v2/bytes/BytesValues.java new file mode 100644 index 000000000..4049110dd --- /dev/null +++ b/bytes/src/main/java/org/apache/tuweni/v2/bytes/BytesValues.java @@ -0,0 +1,74 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static org.apache.tuweni.v2.bytes.Utils.checkArgument; +import static org.apache.tuweni.v2.bytes.Utils.checkNotNull; + +final class BytesValues { + private BytesValues() {} + + static final int MAX_UNSIGNED_SHORT = (1 << 16) - 1; + static final long MAX_UNSIGNED_INT = (1L << 32) - 1; + static final long MAX_UNSIGNED_LONG = Long.MAX_VALUE; + + static Bytes fromHexString(CharSequence str, int destSize, boolean lenient) { + return Bytes.wrap(fromRawHexString(str, destSize, lenient)); + } + + static byte[] fromRawHexString(CharSequence str, int destSize, boolean lenient) { + checkNotNull(str); + int len = str.length(); + CharSequence hex = str; + if (len >= 2 && str.charAt(0) == '0' && str.charAt(1) == 'x') { + hex = str.subSequence(2, len); + len -= 2; + } + + int idxShift = 0; + if ((len & 0x01) != 0) { + if (!lenient) { + throw new IllegalArgumentException("Invalid odd-length hex binary representation"); + } + + hex = "0" + hex; + len += 1; + idxShift = 1; + } + + int size = len >> 1; + if (destSize < 0) { + destSize = size; + } else { + checkArgument( + size <= destSize, + "Hex value is too large: expected at most %s bytes but got %s", + destSize, + size); + } + + byte[] out = new byte[destSize]; + + int destOffset = (destSize - size); + for (int i = destOffset, j = 0; j < len; i++) { + int h = Character.digit(hex.charAt(j), 16); + if (h == -1) { + throw new IllegalArgumentException( + String.format( + "Illegal character '%c' found at index %d in hex binary representation", + hex.charAt(j), j - idxShift)); + } + j++; + int l = Character.digit(hex.charAt(j), 16); + if (l == -1) { + throw new IllegalArgumentException( + String.format( + "Illegal character '%c' found at index %d in hex binary representation", + hex.charAt(j), j - idxShift)); + } + j++; + out[i] = (byte) ((h << 4) + l); + } + return out; + } +} diff --git a/bytes/src/main/java/org/apache/tuweni/v2/bytes/ConcatenatedBytes.java b/bytes/src/main/java/org/apache/tuweni/v2/bytes/ConcatenatedBytes.java new file mode 100644 index 000000000..3ece172c5 --- /dev/null +++ b/bytes/src/main/java/org/apache/tuweni/v2/bytes/ConcatenatedBytes.java @@ -0,0 +1,273 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static org.apache.tuweni.v2.bytes.Utils.checkElementIndex; +import static org.apache.tuweni.v2.bytes.Utils.checkLength; +import static org.apache.tuweni.v2.bytes.Utils.checkNotNull; + +import java.security.MessageDigest; +import java.util.List; + +final class ConcatenatedBytes extends Bytes { + + private final Bytes[] values; + + private ConcatenatedBytes(Bytes[] values, int totalSize) { + super(totalSize); + this.values = values; + } + + static Bytes create(Bytes... values) { + checkNotNull(values); + if (values.length == 0) { + return EMPTY; + } + if (values.length == 1) { + return values[0]; + } + + int count = 0; + int totalSize = 0; + + for (Bytes value : values) { + if (value == null) { + continue; + } + try { + totalSize = Math.addExact(totalSize, value.size()); + } catch (ArithmeticException e) { + throw new IllegalArgumentException( + "Combined length of values is too long (> Integer.MAX_VALUE)"); + } + if (value instanceof ConcatenatedBytes concatenatedBytes) { + count += concatenatedBytes.values.length; + } else if (!value.isEmpty()) { + count += 1; + } + } + + if (count == 0) { + return EMPTY; + } + if (count == values.length) { + return new ConcatenatedBytes(values, totalSize); + } + + Bytes[] concatenated = new Bytes[count]; + int i = 0; + for (Bytes value : values) { + if (value instanceof ConcatenatedBytes concatenatedBytes) { + Bytes[] subvalues = concatenatedBytes.values; + System.arraycopy(subvalues, 0, concatenated, i, subvalues.length); + i += subvalues.length; + } else if (value != null && !value.isEmpty()) { + concatenated[i] = value; + i++; + } + } + return new ConcatenatedBytes(concatenated, totalSize); + } + + static Bytes create(List values) { + if (values.isEmpty()) { + return EMPTY; + } + if (values.size() == 1) { + return values.getFirst(); + } + + int count = 0; + int totalSize = 0; + + for (Bytes value : values) { + int size = value.size(); + try { + totalSize = Math.addExact(totalSize, size); + } catch (ArithmeticException e) { + throw new IllegalArgumentException( + "Combined length of values is too long (> Integer.MAX_VALUE)"); + } + if (value instanceof ConcatenatedBytes) { + count += ((ConcatenatedBytes) value).values.length; + } else if (size != 0) { + count += 1; + } + } + + if (count == 0) { + return EMPTY; + } + if (count == values.size()) { + return new ConcatenatedBytes(values.toArray(new Bytes[0]), totalSize); + } + + Bytes[] concatenated = new Bytes[count]; + int i = 0; + for (Bytes value : values) { + if (value instanceof ConcatenatedBytes) { + Bytes[] subvalues = ((ConcatenatedBytes) value).values; + System.arraycopy(subvalues, 0, concatenated, i, subvalues.length); + i += subvalues.length; + } else if (!value.isEmpty()) { + concatenated[i++] = value; + } + } + return new ConcatenatedBytes(concatenated, totalSize); + } + + @Override + public byte get(int i) { + checkElementIndex(i, size()); + return getUnsafe(i); + } + + private byte getUnsafe(int i) { + for (Bytes value : values) { + int vSize = value.size(); + if (i < vSize) { + return value.get(i); + } + i -= vSize; + } + throw new IllegalStateException("element sizes do not match total size"); + } + + @Override + public Bytes slice(int offset, int length) { + if (offset == 0 && length == size()) { + return this; + } + if (length == 0) { + return EMPTY; + } + + checkElementIndex(offset, size()); + checkLength(size(), offset, length); + + int startIndex = 0; + for (int i = 0; i < values.length; i++) { + int currentValueSize = values[i].size(); + if (offset < currentValueSize) { + startIndex = i; + break; + } + offset -= currentValueSize; + } + + int endIndex = values.length - 1; + int lastElementLength = offset + length; + for (int i = startIndex; i < values.length; i++) { + int currentValueSize = values[i].size(); + if (lastElementLength <= currentValueSize) { + endIndex = i; + break; + } + lastElementLength -= currentValueSize; + } + + if (startIndex == endIndex) { + return values[startIndex].slice(offset, lastElementLength - offset); + } + + Bytes[] combined = new Bytes[endIndex - startIndex + 1]; + combined[0] = values[startIndex].slice(offset); + combined[combined.length - 1] = values[endIndex].slice(0, lastElementLength); + + if (endIndex >= startIndex + 2) { + System.arraycopy(values, startIndex + 1, combined, 1, endIndex - startIndex - 1); + } + + return new ConcatenatedBytes(combined, length); + } + + @Override + public MutableBytes mutableCopy() { + return MutableBytes.fromArray(toArrayUnsafe()); + } + + @Override + public void update(MessageDigest digest) { + for (Bytes value : values) { + value.update(digest); + } + } + + @Override + public byte[] toArrayUnsafe() { + byte[] bytesArray = new byte[size()]; + int offset = 0; + for (Bytes value : values) { + System.arraycopy(value.toArrayUnsafe(), 0, bytesArray, offset, value.size()); + offset += value.size(); + } + return bytesArray; + } + + @Override + protected void and(byte[] bytesArray, int offset, int length) { + int resultOffset = offset; + for (Bytes value : values) { + value.and(bytesArray, resultOffset, value.size()); + resultOffset += value.size(); + if (resultOffset >= offset + length) { + return; + } + } + } + + @Override + protected void or(byte[] bytesArray, int offset, int length) { + int resultOffset = offset; + for (Bytes value : values) { + value.or(bytesArray, resultOffset, value.size()); + resultOffset += value.size(); + if (resultOffset >= offset + length) { + return; + } + } + } + + @Override + protected void xor(byte[] bytesArray, int offset, int length) { + int resultOffset = offset; + for (Bytes value : values) { + value.xor(bytesArray, resultOffset, value.size()); + resultOffset += value.size(); + if (resultOffset >= offset + length) { + return; + } + } + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Bytes other)) { + return false; + } + + if (this.size() != other.size()) { + return false; + } + + for (int i = 0; i < size(); i++) { + if (getUnsafe(i) != other.get(i)) { + return false; + } + } + + return true; + } + + @Override + protected int computeHashcode() { + int result = 1; + for (int i = 0; i < size(); i++) { + result = 31 * result + getUnsafe(i); + } + return result; + } +} diff --git a/bytes/src/main/java/org/apache/tuweni/v2/bytes/ConstantBytesValue.java b/bytes/src/main/java/org/apache/tuweni/v2/bytes/ConstantBytesValue.java new file mode 100644 index 000000000..945ebe6dc --- /dev/null +++ b/bytes/src/main/java/org/apache/tuweni/v2/bytes/ConstantBytesValue.java @@ -0,0 +1,110 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static org.apache.tuweni.v2.bytes.Utils.checkArgument; +import static org.apache.tuweni.v2.bytes.Utils.checkElementIndex; +import static org.apache.tuweni.v2.bytes.Utils.checkLength; + +import java.util.Arrays; + +/** + * A Bytes value with just one constant value throughout. Ideal to avoid allocating large byte + * arrays filled with the same byte. + */ +class ConstantBytesValue extends Bytes { + + private final byte value; + + ConstantBytesValue(byte b, int size) { + super(size); + this.value = b; + } + + @Override + public byte get(int i) { + return this.value; + } + + @Override + public Bytes slice(int offset, int length) { + checkArgument(length >= 0, "Invalid negative length"); + if (size() > 0) { + checkElementIndex(offset, size()); + } + checkLength(size(), offset, length); + if (length == size()) { + return this; + } + return new ConstantBytesValue(this.value, length); + } + + @Override + protected void and(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + // TODO: Speed this up with SIMD + bytesArray[offset + i] = (byte) (value & bytesArray[offset + i]); + } + } + + @Override + protected void or(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + // TODO: Speed this up with SIMD + bytesArray[offset + i] = (byte) (value | bytesArray[offset + i]); + } + } + + @Override + protected void xor(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + // TODO: Speed this up with SIMD + bytesArray[offset + i] = (byte) (value ^ bytesArray[offset + i]); + } + } + + @Override + public MutableBytes mutableCopy() { + MutableBytes mutableBytes = MutableBytes.create(size()); + mutableBytes.fill(value); + return mutableBytes; + } + + @Override + public byte[] toArrayUnsafe() { + byte[] array = new byte[size()]; + Arrays.fill(array, value); + return array; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Bytes other)) { + return false; + } + + if (this.size() != other.size()) { + return false; + } + + for (int i = 0; i < size(); i++) { + if (value != other.get(i)) { + return false; + } + } + + return true; + } + + @Override + protected int computeHashcode() { + int result = 1; + for (int i = 0; i < size(); i++) { + result = 31 * result + value; + } + return result; + } +} diff --git a/bytes/src/main/java/org/apache/tuweni/v2/bytes/DelegatingBytes.java b/bytes/src/main/java/org/apache/tuweni/v2/bytes/DelegatingBytes.java new file mode 100644 index 000000000..2cbd8581c --- /dev/null +++ b/bytes/src/main/java/org/apache/tuweni/v2/bytes/DelegatingBytes.java @@ -0,0 +1,260 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static org.apache.tuweni.v2.bytes.Utils.checkArgument; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.MessageDigest; + +import io.vertx.core.buffer.Buffer; + +/** + * A class that holds and delegates all operations to its inner bytes field. + * + *

This class may be used to create more types that represent bytes, but need a different name + * for business logic. + */ +public class DelegatingBytes extends Bytes { + + final Bytes delegate; + + protected DelegatingBytes(Bytes delegate, int size) { + super(size); + this.delegate = delegate; + checkArgument(delegate.size() == size, "Expected %s bytes but got %s", size, delegate.size()); + } + + @Override + public byte get(int i) { + return delegate.get(i); + } + + @Override + public Bytes slice(int index, int length) { + return delegate.slice(index, length); + } + + @Override + public byte[] toArrayUnsafe() { + return delegate.toArrayUnsafe(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + @Override + public Bytes getImpl() { + return delegate.getImpl(); + } + + @Override + protected void and(byte[] bytesArray, int offset, int length) { + delegate.and(bytesArray, offset, length); + } + + @Override + protected void or(byte[] bytesArray, int offset, int length) { + delegate.or(bytesArray, offset, length); + } + + @Override + protected void xor(byte[] bytesArray, int offset, int length) { + delegate.xor(bytesArray, offset, length); + } + + @Override + public int getInt(int i) { + return delegate.getInt(i); + } + + @Override + public int getInt(int i, ByteOrder order) { + return delegate.getInt(i, order); + } + + @Override + public int toInt() { + return delegate.toInt(); + } + + @Override + public int toInt(ByteOrder order) { + return delegate.toInt(order); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public long getLong(int i) { + return delegate.getLong(i); + } + + @Override + public long getLong(int i, ByteOrder order) { + return delegate.getLong(i, order); + } + + @Override + public long toLong() { + return delegate.toLong(); + } + + @Override + public long toLong(ByteOrder order) { + return delegate.toLong(order); + } + + @Override + public BigInteger toBigInteger() { + return delegate.toBigInteger(); + } + + @Override + public BigInteger toBigInteger(ByteOrder order) { + return delegate.toBigInteger(order); + } + + @Override + public BigInteger toUnsignedBigInteger() { + return delegate.toUnsignedBigInteger(); + } + + @Override + public BigInteger toUnsignedBigInteger(ByteOrder order) { + return delegate.toUnsignedBigInteger(order); + } + + @Override + public boolean isZero() { + return delegate.isZero(); + } + + @Override + public boolean hasLeadingZero() { + return delegate.hasLeadingZero(); + } + + @Override + public int numberOfLeadingZeros() { + return delegate.numberOfLeadingZeros(); + } + + @Override + public boolean hasLeadingZeroByte() { + return delegate.hasLeadingZeroByte(); + } + + @Override + public int numberOfLeadingZeroBytes() { + return delegate.numberOfLeadingZeroBytes(); + } + + @Override + public int numberOfTrailingZeroBytes() { + return delegate.numberOfTrailingZeroBytes(); + } + + @Override + public int bitLength() { + return delegate.bitLength(); + } + + @Override + public Bytes slice(int i) { + return delegate.slice(i); + } + + @Override + public void appendTo(ByteBuffer byteBuffer) { + delegate.appendTo(byteBuffer); + } + + @Override + public MutableBytes mutableCopy() { + return delegate.mutableCopy(); + } + + @Override + public void appendTo(Buffer buffer) { + delegate.appendTo(buffer); + } + + @Override + public T appendHexTo(T appendable) { + return delegate.appendHexTo(appendable); + } + + @Override + public int commonPrefixLength(Bytes other) { + return delegate.commonPrefixLength(other); + } + + @Override + public Bytes commonPrefix(Bytes other) { + return delegate.commonPrefix(other); + } + + @Override + public Bytes trimLeadingZeros() { + return delegate.trimLeadingZeros(); + } + + @Override + public void update(MessageDigest digest) { + delegate.update(digest); + } + + @Override + public String toHexString() { + return delegate.toHexString(); + } + + @Override + public String toUnprefixedHexString() { + return delegate.toUnprefixedHexString(); + } + + @Override + public String toEllipsisHexString() { + return delegate.toEllipsisHexString(); + } + + @Override + public String toShortHexString() { + return delegate.toShortHexString(); + } + + @Override + public String toQuantityHexString() { + return delegate.toQuantityHexString(); + } + + @Override + public String toBase64String() { + return delegate.toBase64String(); + } + + @Override + public int compareTo(Bytes b) { + return delegate.compareTo(b); + } + + @Override + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + public boolean equals(final Object o) { + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } +} diff --git a/bytes/src/main/java/org/apache/tuweni/v2/bytes/MutableBytes.java b/bytes/src/main/java/org/apache/tuweni/v2/bytes/MutableBytes.java new file mode 100644 index 000000000..685aabf16 --- /dev/null +++ b/bytes/src/main/java/org/apache/tuweni/v2/bytes/MutableBytes.java @@ -0,0 +1,675 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static java.lang.String.format; +import static org.apache.tuweni.v2.bytes.Utils.checkArgument; +import static org.apache.tuweni.v2.bytes.Utils.checkElementIndex; +import static org.apache.tuweni.v2.bytes.Utils.checkLength; +import static org.apache.tuweni.v2.bytes.Utils.checkNotNull; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import io.netty.buffer.ByteBuf; +import io.vertx.core.buffer.Buffer; + +/** A class for doing modifications on a {@link Bytes} value without modifying the original. */ +public class MutableBytes extends ArrayWrappingBytes { + + MutableBytes(int size) { + super(new byte[size]); + } + + MutableBytes(byte[] bytesArray) { + super(new byte[bytesArray.length]); + System.arraycopy(bytesArray, 0, bytes, 0, bytesArray.length); + } + + MutableBytes(byte[] bytesArray, int offset, int length) { + super(new byte[length]); + System.arraycopy(bytesArray, offset, bytes, 0, length); + } + + /** + * Create a new mutable bytes value. + * + * @param size The size of the returned value. + * @return A {@link MutableBytes} value. + */ + public static MutableBytes create(int size) { + return new MutableBytes(size); + } + + /** + * Create a {@link MutableBytes} value from a byte array. + * + * @param value The value to wrap. + * @return A {@link MutableBytes} value wrapping {@code value}. + */ + public static MutableBytes fromArray(byte[] value) { + checkNotNull(value); + if (value.length == 0) { + return MutableBytes.create(0); + } + return new MutableBytes(value); + } + + /** + * Wrap a slice of a byte array as a {@link MutableBytes} value. + * + *

Note that value is not copied and thus any future update to {@code value} within the slice + * will be reflected in the returned value. + * + * @param value The value to wrap. + * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned + * value. In other words, you will have {@code wrap(value, o, l).get(0) == value[o]}. + * @param length The length of the resulting value. + * @return A {@link Bytes} value that expose the bytes of {@code value} from {@code offset} + * (inclusive) to {@code offset + length} (exclusive). + * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.length > 0 && offset >= + * value.length)}. + * @throws IllegalArgumentException if {@code length < 0 || offset + length > value.length}. + */ + public static MutableBytes fromArray(byte[] value, int offset, int length) { + checkNotNull(value); + checkArgument(length >= 0, "Invalid negative length"); + if (value.length > 0) { + checkElementIndex(offset, value.length); + } + checkLength(value.length, offset, length); + if (length == 0) { + return MutableBytes.create(0); + } + return new MutableBytes(value, offset, length); + } + + /** + * Wrap a full Vert.x {@link Buffer} as a {@link MutableBytes} value. + * + *

Note that any change to the content of the buffer may be reflected in the returned value. + * + * @param buffer The buffer to wrap. + * @return A {@link MutableBytes} value. + */ + public static MutableBytes fromBuffer(Buffer buffer) { + checkNotNull(buffer); + if (buffer.length() == 0) { + return MutableBytes.create(0); + } + return new MutableBytes(buffer.getBytes()); + } + + /** + * Wrap a slice of a Vert.x {@link Buffer} as a {@link MutableBytes} value. + * + *

Note that any change to the content of the buffer may be reflected in the returned value, + * and any change to the returned value will be reflected in the buffer. + * + * @param buffer The buffer to wrap. + * @param offset The offset in {@code buffer} from which to expose the bytes in the returned + * value. That is, {@code wrapBuffer(buffer, i, 1).get(0) == buffer.getByte(i)}. + * @param length The size of the returned value. + * @return A {@link MutableBytes} value. + * @throws IndexOutOfBoundsException if {@code offset < 0 || (buffer.length() > 0 && offset >= + * buffer.length())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + length > buffer.length()}. + */ + public static MutableBytes fromBuffer(Buffer buffer, int offset, int length) { + checkNotNull(buffer); + checkArgument(length >= 0, "Invalid negative length"); + if (buffer.length() > 0) { + checkElementIndex(offset, buffer.length()); + } + checkLength(buffer.length(), offset, length); + if (length == 0) { + return MutableBytes.create(0); + } + return new MutableBytes(buffer.getBytes(), offset, length); + } + + /** + * Wrap a full Netty {@link ByteBuf} as a {@link MutableBytes} value. + * + *

Note that any change to the content of the buffer may be reflected in the returned value. + * + * @param byteBuf The {@link ByteBuf} to wrap. + * @return A {@link MutableBytes} value. + */ + public static MutableBytes fromByteBuf(ByteBuf byteBuf) { + checkNotNull(byteBuf); + if (byteBuf.capacity() == 0) { + return MutableBytes.create(0); + } + MutableBytes mutableBytes = MutableBytes.create(byteBuf.capacity()); + byteBuf.getBytes(0, mutableBytes.bytes); + return mutableBytes; + } + + /** + * Wrap a slice of a Netty {@link ByteBuf} as a {@link MutableBytes} value. + * + *

Note that any change to the content of the buffer may be reflected in the returned value, + * and any change to the returned value will be reflected in the buffer. + * + * @param byteBuf The {@link ByteBuf} to wrap. + * @param offset The offset in {@code byteBuf} from which to expose the bytes in the returned + * value. That is, {@code wrapByteBuf(byteBuf, i, 1).get(0) == byteBuf.getByte(i)}. + * @param length The size of the returned value. + * @return A {@link MutableBytes} value. + * @throws IndexOutOfBoundsException if {@code offset < 0 || (byteBuf.capacity() > 0 && offset >= + * byteBuf.capacity())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + length > byteBuf.capacity()}. + */ + public static MutableBytes fromByteBuf(ByteBuf byteBuf, int offset, int length) { + checkNotNull(byteBuf); + checkArgument(length >= 0, "Invalid negative length"); + final int byteBufLength = byteBuf.capacity(); + if (byteBufLength > 0) { + checkElementIndex(offset, byteBuf.capacity()); + } + checkLength(byteBufLength, offset, length); + if (length == 0) { + return MutableBytes.create(0); + } + MutableBytes mutableBytes = MutableBytes.create(length); + byteBuf.getBytes(offset, mutableBytes.bytes); + return mutableBytes; + } + + /** + * Wrap a full Java NIO {@link ByteBuffer} as a {@link MutableBytes} value. + * + *

Note that any change to the content of the buffer may be reflected in the returned value. + * + * @param byteBuffer The {@link ByteBuffer} to wrap. + * @return A {@link MutableBytes} value. + */ + public static MutableBytes fromByteBuffer(ByteBuffer byteBuffer) { + checkNotNull(byteBuffer); + if (byteBuffer.limit() == 0) { + return MutableBytes.create(0); + } + MutableBytes mutableBytes = MutableBytes.create(byteBuffer.limit()); + byteBuffer.get(0, mutableBytes.bytes); + return mutableBytes; + } + + /** + * Wrap a slice of a Java NIO {@link ByteBuffer} as a {@link MutableBytes} value. + * + *

Note that any change to the content of the buffer may be reflected in the returned value, + * and any change to the returned value will be reflected in the buffer. + * + * @param byteBuffer The {@link ByteBuffer} to wrap. + * @param offset The offset in {@code byteBuffer} from which to expose the bytes in the returned + * value. That is, {@code wrapByteBuffer(byteBuffer, i, 1).get(0) == byteBuffer.getByte(i)}. + * @param length The size of the returned value. + * @return A {@link MutableBytes} value. + * @throws IndexOutOfBoundsException if {@code offset < 0 || (byteBuffer.limit() > 0 && offset >= + * byteBuffer.limit())}. + * @throws IllegalArgumentException if {@code length < 0 || offset + length > byteBuffer.limit()}. + */ + public static MutableBytes fromByteBuffer(ByteBuffer byteBuffer, int offset, int length) { + checkNotNull(byteBuffer); + checkArgument(length >= 0, "Invalid negative length"); + final int byteBufferLength = byteBuffer.limit(); + if (byteBufferLength > 0) { + checkElementIndex(offset, byteBuffer.limit()); + } + checkLength(byteBufferLength, offset, length); + if (length == 0) { + return MutableBytes.create(0); + } + MutableBytes mutableBytes = MutableBytes.create(length); + byteBuffer.get(offset, mutableBytes.bytes); + return mutableBytes; + } + + /** + * Create a value that contains the specified bytes in their specified order. + * + * @param bytes The bytes that must compose the returned value. + * @return A value containing the specified bytes. + */ + public static MutableBytes of(byte... bytes) { + return fromArray(bytes); + } + + /** + * Create a value that contains the specified bytes in their specified order. + * + * @param bytes The bytes. + * @return A value containing bytes are the one from {@code bytes}. + * @throws IllegalArgumentException if any of the specified would be truncated when storing as a + * byte. + */ + public static MutableBytes of(int... bytes) { + checkNotNull(bytes); + byte[] result = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + int b = bytes[i]; + checkArgument(b == (((byte) b) & 0xff), "%sth value %s does not fit a byte", i + 1, b); + result[i] = (byte) b; + } + return fromArray(result); + } + + /** + * Set a byte in this value. + * + * @param index The offset of the bytes to set. + * @param bytes The value to set bytes to. + * @throws IndexOutOfBoundsException if {@code offset < 0} or {offset >= size()}. + * @throws IllegalArgumentException if {@code offset + bytes.size() > this.length}. + */ + public void set(int index, Bytes bytes) { + checkNotNull(bytes); + if (bytes.isEmpty()) { + return; + } + checkElementIndex(index, size); + checkLength(this.bytes.length, index, bytes.size()); + for (int i = 0; i < bytes.size(); i++) { + set(i + index, bytes.get(i)); + } + } + + /** + * Set a byte array in this value. + * + * @param index The offset of the bytes to set. + * @param bytes The value to set bytes to. + * @throws IndexOutOfBoundsException if {@code offset < 0} or {offset >= bytes.length}. + * @throws IllegalArgumentException if {@code offset + bytes.length > this.length}. + */ + public void set(int index, byte[] bytes) { + checkNotNull(bytes); + if (bytes.length == 0) { + return; + } + checkElementIndex(index, size); + checkLength(this.bytes.length, index, bytes.length); + for (int i = 0; i < bytes.length; i++) { + set(i + index, bytes[i]); + } + } + + /** + * Set the 4 bytes starting at the specified index to the specified integer value. + * + * @param index The index, which must less than or equal to {@code size() - 4}. + * @param value The integer value. + * @throws IndexOutOfBoundsException if {@code index < 0} or {@code index > size() - 4}. + */ + public void setInt(int index, int value) { + checkElementIndex(index, size); + if (index > (size - 4)) { + throw new IndexOutOfBoundsException( + format( + "Value of size %s has not enough bytes to write a 4 bytes int from index %s", + size, index)); + } + + set(index++, (byte) (value >>> 24)); + set(index++, (byte) ((value >>> 16) & 0xFF)); + set(index++, (byte) ((value >>> 8) & 0xFF)); + set(index, (byte) (value & 0xFF)); + } + + /** + * Set the 8 bytes starting at the specified index to the specified long value. + * + * @param index The index, which must less than or equal to {@code size() - 8}. + * @param value The long value. + * @throws IndexOutOfBoundsException if {@code index < 0} or {@code index > size() - 8}. + */ + public void setLong(int index, long value) { + checkElementIndex(index, size); + if (index > (size - 8)) { + throw new IndexOutOfBoundsException( + format( + "Value of length %s has not enough bytes to write a 8 bytes long from index %s", + size, index)); + } + + set(index++, (byte) (value >>> 56)); + set(index++, (byte) ((value >>> 48) & 0xFF)); + set(index++, (byte) ((value >>> 40) & 0xFF)); + set(index++, (byte) ((value >>> 32) & 0xFF)); + set(index++, (byte) ((value >>> 24) & 0xFF)); + set(index++, (byte) ((value >>> 16) & 0xFF)); + set(index++, (byte) ((value >>> 8) & 0xFF)); + set(index, (byte) (value & 0xFF)); + } + + /** + * Set a byte in this value. + * + * @param index The index of the byte to set. + * @param b The value to set that byte to. + * @throws IndexOutOfBoundsException if {@code i < 0} or {i >= size()}. + */ + public void set(int index, byte b) { + checkElementIndex(index, size); + bytes[index] = b; + hashCode = null; + } + + /** + * Increments the value of the bytes by 1, treating the value as big endian. + * + *

If incrementing overflows the value then all bits flip, i.e. incrementing 0xFFFF will return + * 0x0000. + * + * @return This mutable bytes instance. + */ + public MutableBytes increment() { + for (int i = size - 1; i >= 0; --i) { + if (bytes[i] == (byte) 0xFF) { + bytes[i] = (byte) 0x00; + } else { + byte currentValue = bytes[i]; + bytes[i] = ++currentValue; + break; + } + } + hashCode = null; + return this; + } + + /** + * Decrements the value of the bytes by 1, treating the value as big endian. + * + *

If decrementing underflows the value then all bits flip, i.e. decrementing 0x0000 will + * return 0xFFFF. + * + * @return This mutable bytes instance. + */ + public MutableBytes decrement() { + for (int i = size - 1; i >= 0; --i) { + if (bytes[i] == (byte) 0x00) { + bytes[i] = (byte) 0xFF; + } else { + byte currentValue = bytes[i]; + bytes[i] = --currentValue; + break; + } + } + hashCode = null; + return this; + } + + /** + * Fill all the bytes of this value with the specified byte. + * + * @param b The byte to use to fill the value. + * @return This mutable bytes instance. + */ + public MutableBytes fill(byte b) { + for (int i = 0; i < size; i++) { + bytes[i] = b; + } + hashCode = null; + return this; + } + + /** + * Set all bytes in this value to 0. + * + * @return This mutable bytes instance. + */ + public MutableBytes clear() { + fill((byte) 0); + return this; + } + + /** + * Computes the reverse array of bytes of the current bytes. + * + * @return This mutable bytes instance. + */ + public MutableBytes reverse() { + byte[] reverse = new byte[size]; + for (int i = 0; i < size; i++) { + reverse[size - 1 - i] = bytes[i]; + } + bytes = reverse; + hashCode = null; + return this; + } + + /** + * Calculate a bit-wise AND of these bytes and the supplied bytes. + * + * @param other The bytes to perform the operation with. + * @return This mutable bytes instance. + */ + public MutableBytes and(Bytes other) { + checkNotNull(other); + int otherSize = other.size(); + if (size == otherSize) { + other.and(bytes, 0, size); + hashCode = null; + return this; + } + + int otherOffset = 0; + if (size < otherSize) { + byte[] newBytesArray = new byte[otherSize]; + System.arraycopy(bytes, 0, newBytesArray, otherSize - size, size); + bytes = newBytesArray; + size = otherSize; + } else { + Arrays.fill(bytes, 0, size - otherSize, (byte) 0); + otherOffset = size - otherSize; + } + other.and(bytes, otherOffset, otherSize); + hashCode = null; + return this; + } + + /** + * Calculate a bit-wise OR of these bytes and the supplied bytes. + * + * @param other The bytes to perform the operation with. + * @return This mutable bytes instance. + */ + public MutableBytes or(Bytes other) { + checkNotNull(other); + int otherSize = other.size(); + if (size == otherSize) { + other.or(bytes, 0, size); + hashCode = null; + return this; + } + + int otherOffset = 0; + if (size < otherSize) { + byte[] newBytesArray = new byte[otherSize]; + System.arraycopy(bytes, 0, newBytesArray, otherSize - size, size); + bytes = newBytesArray; + size = otherSize; + } else { + otherOffset = size - otherSize; + } + other.or(bytes, otherOffset, otherSize); + hashCode = null; + return this; + } + + /** + * Calculate a bit-wise XOR of these bytes and the supplied bytes. + * + * @param other The bytes to perform the operation with. + * @return This mutable bytes instance. + */ + public MutableBytes xor(Bytes other) { + checkNotNull(other); + int otherSize = other.size(); + if (size == otherSize) { + other.xor(bytes, 0, size); + hashCode = null; + return this; + } + + int otherOffset = 0; + if (size < otherSize) { + byte[] newBytesArray = new byte[otherSize]; + System.arraycopy(bytes, 0, newBytesArray, otherSize - size, size); + bytes = newBytesArray; + size = otherSize; + } else { + otherOffset = size - otherSize; + } + other.xor(bytes, otherOffset, otherSize); + hashCode = null; + return this; + } + + /** + * Calculate a bit-wise NOT of these bytes. + * + * @return This mutable bytes instance. + */ + public MutableBytes not() { + for (int i = 0; i < size; i++) { + bytes[i] = (byte) ~bytes[i]; + } + hashCode = null; + return this; + } + + /** + * Shift all bits in this value to the right. + * + * @param distance The number of bits to shift by. + * @return This mutable bytes instance. + */ + public MutableBytes shiftRight(int distance) { + checkArgument(distance >= 0, "Invalid negative distance"); + if (distance == 0 || size() == 0) { + return this; + } + distance = Math.min(distance, size * 8); + int byteShift = distance / 8; + int bitShift = distance % 8; + + if (byteShift > 0) { + for (int i = size - 1; i >= 0; i--) { + byte previousByte = (i < byteShift) ? 0 : bytes[i - byteShift]; + bytes[i] = previousByte; + } + } + + if (bitShift > 0) { + for (int i = size - 1; i >= 0; i--) { + byte currentByte = bytes[i]; + byte previousByte = (i == 0) ? 0 : bytes[i - 1]; + int rightSide = (currentByte & 0XFF) >>> bitShift; + int leftSide = previousByte << (8 - bitShift); + bytes[i] = (byte) (leftSide | rightSide); + } + } + hashCode = null; + return this; + } + + /** + * Shift all bits in this value to the left. + * + * @param distance The number of bits to shift by. + * @return This mutable bytes instance. + */ + public MutableBytes shiftLeft(int distance) { + checkArgument(distance >= 0, "Invalid negative distance"); + if (distance == 0 || size() == 0) { + return this; + } + distance = Math.min(distance, size * 8); + int byteShift = distance / 8; + int bitShift = distance % 8; + + if (byteShift > 0) { + for (int i = 0; i < size; i++) { + byte nextByte = (i + byteShift < size) ? bytes[i + byteShift] : 0; + bytes[i] = nextByte; + } + } + + if (bitShift > 0) { + for (int i = 0; i < size; i++) { + byte currentByte = bytes[i]; + byte nextByte = (i == size - 1) ? 0 : bytes[i + 1]; + int leftSide = currentByte << bitShift; + int rightSide = (nextByte & 0XFF) >>> (8 - bitShift); + bytes[i] = (byte) (leftSide | rightSide); + } + } + hashCode = null; + return this; + } + + /** + * Left pad these mutable values with zero bytes up to the specified length. Resulting bytes are + * guaranteed to have at least {@code length} bytes in length but not necessarily that exact + * amount. If length already exceeds {@code length} then bytes are not modified. + * + * @param length The new length of the bytes. + * @throws IllegalArgumentException if {@code length} is negative. + * @return This mutable bytes instance. + */ + public MutableBytes leftPad(int length) { + checkArgument(length >= 0, "Invalid negative length"); + if (length <= size) { + return this; + } + byte[] newBytesArray = new byte[length]; + System.arraycopy(bytes, 0, newBytesArray, length - size, size); + bytes = newBytesArray; + size = length; + hashCode = null; + return this; + } + + /** + * Right pad these mutable values with zero bytes up to the specified length. Resulting bytes are + * guaranteed to have at least {@code length} bytes in length but not necessarily that exact + * amount. If length already exceeds {@code length} then bytes are not modified. + * + * @param length The new length of the bytes. + * @throws IllegalArgumentException if {@code length} is negative. + * @return This mutable bytes instance. + */ + public MutableBytes rightPad(int length) { + checkArgument(length >= 0, "Invalid negative length"); + if (length <= size) { + return this; + } + byte[] newBytesArray = new byte[length]; + System.arraycopy(bytes, 0, newBytesArray, 0, size); + bytes = newBytesArray; + size = length; + hashCode = null; + return this; + } + + public byte[] toArray() { + return toArrayUnsafe(); + } + + /** + * Parse a hexadecimal string into a {@link MutableBytes} value. + * + *

This method requires that {@code str} have an even length. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal + * representation, or is of an odd length. + */ + public static MutableBytes fromHexString(CharSequence str) { + checkNotNull(str); + return MutableBytes.fromArray(BytesValues.fromRawHexString(str, -1, false)); + } +} diff --git a/bytes/src/main/java/org/apache/tuweni/v2/bytes/Utils.java b/bytes/src/main/java/org/apache/tuweni/v2/bytes/Utils.java new file mode 100644 index 000000000..7fe049660 --- /dev/null +++ b/bytes/src/main/java/org/apache/tuweni/v2/bytes/Utils.java @@ -0,0 +1,113 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import javax.annotation.Nullable; + +import com.google.errorprone.annotations.FormatMethod; + +public final class Utils { + + static void checkNotNull(@Nullable Object object) { + if (object == null) { + throw new NullPointerException("argument cannot be null"); + } + } + + public static void checkElementIndex(int index, int size) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException("index is out of bounds"); + } + } + + @FormatMethod + static void checkArgument(boolean condition, String message) { + if (!condition) { + throw new IllegalArgumentException(message); + } + } + + @FormatMethod + static void checkArgument(boolean condition, String message, int arg1) { + if (!condition) { + throw new IllegalArgumentException(String.format(message, arg1)); + } + } + + @FormatMethod + static void checkArgument(boolean condition, String message, int arg1, int arg2) { + if (!condition) { + throw new IllegalArgumentException(String.format(message, arg1, arg2)); + } + } + + @FormatMethod + static void checkArgument(boolean condition, String message, int arg1, int arg2, int arg3) { + if (!condition) { + throw new IllegalArgumentException(String.format(message, arg1, arg2, arg3)); + } + } + + @FormatMethod + static void checkArgument( + boolean condition, String message, int arg1, int arg2, int arg3, int arg4) { + if (!condition) { + throw new IllegalArgumentException(String.format(message, arg1, arg2, arg3, arg4)); + } + } + + @FormatMethod + static void checkArgument(boolean condition, String message, long arg1) { + if (!condition) { + throw new IllegalArgumentException(String.format(message, arg1)); + } + } + + static void checkLength(int arrayLength, int offset, int length) { + checkArgument( + offset + length <= arrayLength, + "Provided length %s is too big: the value has only %s bytes from offset %s", + length, + arrayLength - offset, + offset); + } + + static void and( + byte[] sourceBytesArray, + int sourceOffset, + byte[] destBytesArray, + int destOffset, + int length) { + for (int i = 0; i < length; i++) { + // TODO: Speed this up with SIMD + destBytesArray[destOffset + i] = + (byte) (sourceBytesArray[sourceOffset + i] & destBytesArray[destOffset + i]); + } + } + + static void or( + byte[] sourceBytesArray, + int sourceOffset, + byte[] destBytesArray, + int destOffset, + int length) { + for (int i = 0; i < length; i++) { + // TODO: Speed this up with SIMD + destBytesArray[destOffset + i] = + (byte) (sourceBytesArray[sourceOffset + i] | destBytesArray[destOffset + i]); + } + } + + static void xor( + byte[] sourceBytesArray, + int sourceOffset, + byte[] destBytesArray, + int destOffset, + int length) { + for (int i = 0; i < length; i++) { + // TODO: Speed this up with SIMD + destBytesArray[destOffset + i] = + (byte) (sourceBytesArray[sourceOffset + i] ^ destBytesArray[destOffset + i]); + } + } +} diff --git a/bytes/src/main/java/org/apache/tuweni/v2/bytes/package-info.java b/bytes/src/main/java/org/apache/tuweni/v2/bytes/package-info.java new file mode 100644 index 000000000..35259f8db --- /dev/null +++ b/bytes/src/main/java/org/apache/tuweni/v2/bytes/package-info.java @@ -0,0 +1,23 @@ +/** + * 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. + * + *

Classes and utilities for working with byte arrays. + * + *

These classes are included in the standard Tuweni distribution, or separately when using the + * gradle dependency 'org.apache.tuweni:tuweni-bytes' (tuweni-bytes.jar). + */ +@ParametersAreNonnullByDefault +package org.apache.tuweni.v2.bytes; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/bytes/src/test/java/org/apache/tuweni/bytes/ConcatenatedBytesTest.java b/bytes/src/test/java/org/apache/tuweni/bytes/ConcatenatedBytesTest.java index 03056df39..0450efa55 100644 --- a/bytes/src/test/java/org/apache/tuweni/bytes/ConcatenatedBytesTest.java +++ b/bytes/src/test/java/org/apache/tuweni/bytes/ConcatenatedBytesTest.java @@ -72,6 +72,7 @@ void shouldSliceConcatenatedValue() { fromHexString("0x89ABCDEF")); assertEquals("0x", bytes.slice(4, 0).toHexString()); assertEquals("0x0123456789abcdef0123456789abcdef", bytes.slice(0, 16).toHexString()); + assertEquals("0x0123456789abcdef0123456789ab", bytes.slice(0, 14).toHexString()); assertEquals("0x01234567", bytes.slice(0, 4).toHexString()); assertEquals("0x0123", bytes.slice(0, 2).toHexString()); assertEquals("0x6789", bytes.slice(3, 2).toHexString()); diff --git a/bytes/src/test/java/org/apache/tuweni/v2/bytes/BufferBytesTest.java b/bytes/src/test/java/org/apache/tuweni/v2/bytes/BufferBytesTest.java new file mode 100644 index 000000000..d8eae113c --- /dev/null +++ b/bytes/src/test/java/org/apache/tuweni/v2/bytes/BufferBytesTest.java @@ -0,0 +1,28 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import io.vertx.core.buffer.Buffer; + +class BufferBytesTest extends CommonBytesTests { + + @Override + Bytes h(String hex) { + return Bytes.wrapBuffer(Buffer.buffer(Bytes.fromHexString(hex).toArrayUnsafe())); + } + + @Override + MutableBytes m(int size) { + return MutableBytes.fromBuffer(Buffer.buffer(new byte[size])); + } + + @Override + Bytes w(byte[] bytes) { + return Bytes.wrapBuffer(Buffer.buffer(Bytes.of(bytes).toArrayUnsafe())); + } + + @Override + Bytes of(int... bytes) { + return Bytes.wrapBuffer(Buffer.buffer(Bytes.of(bytes).toArrayUnsafe())); + } +} diff --git a/bytes/src/test/java/org/apache/tuweni/v2/bytes/ByteBufBytesTest.java b/bytes/src/test/java/org/apache/tuweni/v2/bytes/ByteBufBytesTest.java new file mode 100644 index 000000000..b5e10b011 --- /dev/null +++ b/bytes/src/test/java/org/apache/tuweni/v2/bytes/ByteBufBytesTest.java @@ -0,0 +1,28 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import io.netty.buffer.Unpooled; + +class ByteBufBytesTest extends CommonBytesTests { + + @Override + Bytes h(String hex) { + return Bytes.wrapByteBuf(Unpooled.copiedBuffer(Bytes.fromHexString(hex).toArrayUnsafe())); + } + + @Override + MutableBytes m(int size) { + return MutableBytes.fromByteBuf(Unpooled.copiedBuffer(new byte[size])); + } + + @Override + Bytes w(byte[] bytes) { + return Bytes.wrapByteBuf(Unpooled.copiedBuffer(Bytes.of(bytes).toArrayUnsafe())); + } + + @Override + Bytes of(int... bytes) { + return Bytes.wrapByteBuf(Unpooled.copiedBuffer(Bytes.of(bytes).toArrayUnsafe())); + } +} diff --git a/bytes/src/test/java/org/apache/tuweni/v2/bytes/ByteBufferBytesTest.java b/bytes/src/test/java/org/apache/tuweni/v2/bytes/ByteBufferBytesTest.java new file mode 100644 index 000000000..b1bcd8cc9 --- /dev/null +++ b/bytes/src/test/java/org/apache/tuweni/v2/bytes/ByteBufferBytesTest.java @@ -0,0 +1,28 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import java.nio.ByteBuffer; + +class ByteBufferBytesTest extends CommonBytesTests { + + @Override + Bytes h(String hex) { + return Bytes.wrapByteBuffer(ByteBuffer.wrap(Bytes.fromHexString(hex).toArrayUnsafe())); + } + + @Override + MutableBytes m(int size) { + return MutableBytes.fromByteBuffer(ByteBuffer.allocate(size)); + } + + @Override + Bytes w(byte[] bytes) { + return Bytes.wrapByteBuffer(ByteBuffer.wrap(Bytes.of(bytes).toArrayUnsafe())); + } + + @Override + Bytes of(int... bytes) { + return Bytes.wrapByteBuffer(ByteBuffer.wrap(Bytes.of(bytes).toArrayUnsafe())); + } +} diff --git a/bytes/src/test/java/org/apache/tuweni/v2/bytes/Bytes32Test.java b/bytes/src/test/java/org/apache/tuweni/v2/bytes/Bytes32Test.java new file mode 100644 index 000000000..0e64e1c3e --- /dev/null +++ b/bytes/src/test/java/org/apache/tuweni/v2/bytes/Bytes32Test.java @@ -0,0 +1,160 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class Bytes32Test { + + @Test + void testConcatenation() { + Bytes wrapped = Bytes.wrap(Bytes32.wrap(new byte[32]), Bytes32.wrap(new byte[32])); + assertEquals(64, wrapped.size()); + wrapped = wrapped.slice(0, 32); + assertEquals(32, wrapped.size()); + wrapped = wrapped.slice(31, 0); + assertEquals(0, wrapped.size()); + } + + @Test + void constantBytes32Slice() { + assertEquals(Bytes32.ZERO.slice(12, 20).size(), 20); + } + + @Test + void constantBytesslice() { + assertEquals(Bytes.repeat((byte) 1, 63).slice(12, 20).size(), 20); + } + + @Test + void testMutableBytes32WrapWithOffset() { + Bytes bytes = + Bytes.fromHexString( + "0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + MutableBytes mutableBytes = bytes.mutableCopy(); + assertEquals( + "0x112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00", + Bytes32.wrap(mutableBytes, 1).toHexString()); + } + + @Test + void testBytes32SliceWithOffset() { + Bytes bytes = + Bytes.fromHexString( + "0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + assertEquals( + "0x112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00", + Bytes32.wrap(bytes.slice(1, 32)).toHexString()); + assertEquals( + "0xaabbccddeeff00112233445566778899aabbccddeeff00112233445566778899", + Bytes32.wrap(bytes.slice(10, 32)).toHexString()); + } + + @Test + void failsWhenWrappingArraySmallerThan32() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> Bytes32.wrap(new byte[31])); + assertEquals("Expected 32 bytes from offset 0 but got 31", exception.getMessage()); + } + + @Test + void failsWhenWrappingArrayLargerThan32() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> Bytes32.wrap(new byte[33])); + assertEquals("Expected 32 bytes from offset 0 but got 33", exception.getMessage()); + } + + @Test + void wrapReturnsInstanceBytes32() { + assertThat(Bytes32.wrapHexString("0x")).isExactlyInstanceOf(Bytes32.class); + } + + @Test + void leftPadAValueToBytes32() { + Bytes32 b32 = Bytes32.wrap(Bytes.of(1, 2, 3).mutableCopy().leftPad(32)); + assertEquals(32, b32.size()); + for (int i = 0; i < 28; ++i) { + assertEquals((byte) 0, b32.get(i)); + } + assertEquals((byte) 1, b32.get(29)); + assertEquals((byte) 2, b32.get(30)); + assertEquals((byte) 3, b32.get(31)); + } + + @Test + void rightPadAValueToBytes32() { + Bytes32 b32 = Bytes32.wrap(Bytes.of(1, 2, 3).mutableCopy().rightPad(32)); + assertEquals(32, b32.size()); + for (int i = 3; i < 32; ++i) { + assertEquals((byte) 0, b32.get(i)); + } + assertEquals((byte) 1, b32.get(0)); + assertEquals((byte) 2, b32.get(1)); + assertEquals((byte) 3, b32.get(2)); + } + + @Test + void failsWhenLeftPaddingValueLargerThan32() { + Throwable exception = + assertThrows( + IllegalArgumentException.class, + () -> Bytes32.wrap(Bytes.EMPTY.mutableCopy().leftPad(33))); + assertEquals("Expected 32 bytes but got 33", exception.getMessage()); + } + + @Test + void failsWhenRightPaddingValueLargerThan32() { + Throwable exception = + assertThrows( + IllegalArgumentException.class, + () -> Bytes32.wrap(Bytes.EMPTY.mutableCopy().rightPad(33))); + assertEquals("Expected 32 bytes but got 33", exception.getMessage()); + } + + @Test + void testWrapSlicesCorrectly() { + Bytes input = + Bytes.fromHexString( + "0xA99A76ED7796F7BE22D5B7E85DEEB7C5677E88E511E0B337618F8C4EB61349B4BF2D153F649F7B53359FE8B94A38E44C00000000000000000000000000000000"); + Bytes32 value = Bytes32.wrap(input, 0); + assertEquals( + Bytes.fromHexString("0xA99A76ED7796F7BE22D5B7E85DEEB7C5677E88E511E0B337618F8C4EB61349B4"), + value); + + Bytes32 secondValue = Bytes32.wrap(input, 32); + assertEquals( + Bytes.fromHexString("0xBF2D153F649F7B53359FE8B94A38E44C00000000000000000000000000000000"), + secondValue); + } + + @Test + void wrap() { + Bytes source = Bytes.random(96); + Bytes32 value = Bytes32.wrap(source, 2); + assertEquals(source.slice(2, 32), value); + } + + @Test + void hexString() { + Bytes initial = Bytes32.fromRandom(); + assertEquals(initial, Bytes32.fromHexStringLenient(initial.toHexString())); + assertEquals(initial, Bytes32.fromHexString(initial.toHexString())); + assertEquals(initial, Bytes32.fromHexStringStrict(initial.toHexString())); + } + + @Test + void size() { + assertEquals(32, Bytes32.fromRandom().size()); + } + + @Test + void padding() { + Bytes source = Bytes32.fromRandom(); + assertEquals(source, Bytes32.wrap(source.mutableCopy().leftPad(32))); + assertEquals(source, Bytes32.wrap(source.mutableCopy().rightPad(32))); + } +} diff --git a/bytes/src/test/java/org/apache/tuweni/v2/bytes/Bytes48Test.java b/bytes/src/test/java/org/apache/tuweni/v2/bytes/Bytes48Test.java new file mode 100644 index 000000000..4cabc71c5 --- /dev/null +++ b/bytes/src/test/java/org/apache/tuweni/v2/bytes/Bytes48Test.java @@ -0,0 +1,100 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class Bytes48Test { + + @Test + void failsWhenWrappingArraySmallerThan48() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> Bytes48.wrap(new byte[47])); + assertEquals("Expected 48 bytes from offset 0 but got 47", exception.getMessage()); + } + + @Test + void failsWhenWrappingArrayLargerThan48() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> Bytes48.wrap(new byte[49])); + assertEquals("Expected 48 bytes from offset 0 but got 49", exception.getMessage()); + } + + @Test + void wrapReturnsInstanceBytes48() { + assertThat(Bytes48.wrapHexString("0x")).isExactlyInstanceOf(Bytes48.class); + } + + @Test + void rightPadAValueToBytes48() { + Bytes48 b48 = Bytes48.wrap(Bytes.of(1, 2, 3).mutableCopy().rightPad(48)); + assertEquals(48, b48.size()); + for (int i = 3; i < 48; ++i) { + assertEquals((byte) 0, b48.get(i)); + } + assertEquals((byte) 1, b48.get(0)); + assertEquals((byte) 2, b48.get(1)); + assertEquals((byte) 3, b48.get(2)); + } + + @Test + void leftPadAValueToBytes48() { + Bytes48 b48 = Bytes48.wrap(Bytes.of(1, 2, 3).mutableCopy().leftPad(48)); + assertEquals(48, b48.size()); + for (int i = 0; i < 28; ++i) { + assertEquals((byte) 0, b48.get(i)); + } + assertEquals((byte) 1, b48.get(45)); + assertEquals((byte) 2, b48.get(46)); + assertEquals((byte) 3, b48.get(47)); + } + + @Test + void failsWhenLeftPaddingValueLargerThan48() { + Throwable exception = + assertThrows( + IllegalArgumentException.class, + () -> Bytes48.wrap(Bytes.EMPTY.mutableCopy().leftPad(49))); + assertEquals("Expected 48 bytes but got 49", exception.getMessage()); + } + + @Test + void failsWhenRightPaddingValueLargerThan48() { + Throwable exception = + assertThrows( + IllegalArgumentException.class, + () -> Bytes48.wrap(Bytes.EMPTY.mutableCopy().rightPad(49))); + assertEquals("Expected 48 bytes but got 49", exception.getMessage()); + } + + @Test + void hexString() { + Bytes initial = Bytes48.fromRandom(); + assertEquals(initial, Bytes48.fromHexStringLenient(initial.toHexString())); + assertEquals(initial, Bytes48.fromHexString(initial.toHexString())); + assertEquals(initial, Bytes48.fromHexStringStrict(initial.toHexString())); + } + + @Test + void size() { + assertEquals(48, Bytes48.fromRandom().size()); + } + + @Test + void wrap() { + Bytes source = Bytes.random(96); + Bytes48 value = Bytes48.wrap(source, 2); + assertEquals(source.slice(2, 48), value); + } + + @Test + void padding() { + Bytes source = Bytes48.fromRandom(); + assertEquals(source, Bytes48.wrap(source.mutableCopy().leftPad(48))); + assertEquals(source, Bytes48.wrap(source.mutableCopy().rightPad(48))); + } +} diff --git a/bytes/src/test/java/org/apache/tuweni/v2/bytes/BytesTest.java b/bytes/src/test/java/org/apache/tuweni/v2/bytes/BytesTest.java new file mode 100644 index 000000000..ac61ce200 --- /dev/null +++ b/bytes/src/test/java/org/apache/tuweni/v2/bytes/BytesTest.java @@ -0,0 +1,569 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static java.nio.ByteOrder.LITTLE_ENDIAN; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class BytesTest extends CommonBytesTests { + + @Override + Bytes h(String hex) { + return Bytes.fromHexString(hex); + } + + @Override + MutableBytes m(int size) { + return MutableBytes.create(size); + } + + @Override + Bytes w(byte[] bytes) { + return Bytes.wrap(bytes); + } + + @Override + Bytes of(int... bytes) { + return Bytes.of(bytes); + } + + @Test + void wrapEmpty() { + Bytes wrap = Bytes.wrap(new byte[0]); + assertEquals(Bytes.EMPTY, wrap); + } + + @ParameterizedTest + @MethodSource("wrapProvider") + void wrap(List arr) { + final byte[] bytes = new byte[arr.size()]; + arr.forEach(byteValue -> bytes[arr.indexOf(byteValue)] = byteValue); + Bytes value = Bytes.wrap(bytes); + assertEquals(bytes.length, value.size()); + assertArrayEquals(value.toArrayUnsafe(), bytes); + } + + @SuppressWarnings("UnusedMethod") + private static Stream wrapProvider() { + return Stream.of( + Arguments.of(new ArrayList(10)), + Arguments.of(toList(new byte[] {1})), + Arguments.of(toList(new byte[] {1, 2, 3, 4})), + Arguments.of(toList(new byte[] {-1, 127, -128}))); + } + + private static List toList(final byte[] array) { + return IntStream.range(0, array.length).mapToObj(i -> array[i]).toList(); + } + + @Test + void wrapNull() { + assertThrows(NullPointerException.class, () -> Bytes.wrap((byte[]) null)); + } + + /** Checks that modifying a wrapped array modifies the value itself. */ + @Test + void wrapReflectsUpdates() { + byte[] bytes = new byte[] {1, 2, 3, 4, 5}; + Bytes value = Bytes.wrap(bytes); + + assertEquals(bytes.length, value.size()); + assertArrayEquals(value.toArrayUnsafe(), bytes); + + bytes[1] = 127; + bytes[3] = 127; + + assertEquals(bytes.length, value.size()); + assertArrayEquals(value.toArrayUnsafe(), bytes); + } + + @Test + void wrapSliceEmpty() { + assertEquals(Bytes.EMPTY, Bytes.wrap(new byte[0], 0, 0)); + assertEquals(Bytes.EMPTY, Bytes.wrap(new byte[] {1, 2, 3}, 0, 0)); + assertEquals(Bytes.EMPTY, Bytes.wrap(new byte[] {1, 2, 3}, 2, 0)); + } + + @ParameterizedTest + @MethodSource("wrapSliceProvider") + void wrapSlice(Object arr, int offset, int length) { + assertWrapSlice((byte[]) arr, offset, length); + } + + @SuppressWarnings("UnusedMethod") + private static Stream wrapSliceProvider() { + return Stream.of( + Arguments.of(new byte[] {1, 2, 3, 4}, 0, 4), + Arguments.of(new byte[] {1, 2, 3, 4}, 0, 2), + Arguments.of(new byte[] {1, 2, 3, 4}, 2, 1), + Arguments.of(new byte[] {1, 2, 3, 4}, 2, 2)); + } + + private void assertWrapSlice(byte[] bytes, int offset, int length) { + Bytes value = Bytes.wrap(bytes, offset, length); + assertEquals(length, value.size()); + assertArrayEquals(value.toArrayUnsafe(), Arrays.copyOfRange(bytes, offset, offset + length)); + } + + @Test + void wrapSliceNull() { + assertThrows(NullPointerException.class, () -> Bytes.wrap(null, 0, 2)); + } + + @Test + void wrapSliceNegativeOffset() { + assertThrows( + IndexOutOfBoundsException.class, () -> assertWrapSlice(new byte[] {1, 2, 3, 4}, -1, 4)); + } + + @Test + void wrapSliceOutOfBoundOffset() { + assertThrows( + IndexOutOfBoundsException.class, () -> assertWrapSlice(new byte[] {1, 2, 3, 4}, 5, 1)); + } + + @Test + void wrapSliceNegativeLength() { + Throwable exception = + assertThrows( + IllegalArgumentException.class, () -> assertWrapSlice(new byte[] {1, 2, 3, 4}, 0, -2)); + assertEquals("Invalid negative length", exception.getMessage()); + } + + @Test + void wrapSliceTooBig() { + Throwable exception = + assertThrows( + IllegalArgumentException.class, () -> assertWrapSlice(new byte[] {1, 2, 3, 4}, 2, 3)); + assertEquals( + "Provided length 3 is too big: the value has only 2 bytes from offset 2", + exception.getMessage()); + } + + /** + * Checks that modifying a wrapped array modifies the value itself, but only if within the wrapped + * slice. + */ + @Test + void wrapSliceReflectsUpdates() { + byte[] bytes = new byte[] {1, 2, 3, 4, 5}; + assertWrapSlice(bytes, 2, 2); + bytes[2] = 127; + bytes[3] = 127; + assertWrapSlice(bytes, 2, 2); + + Bytes wrapped = Bytes.wrap(bytes, 2, 2); + Bytes copy = wrapped.mutableCopy(); + + // Modify the bytes outside of the wrapped slice and check this doesn't affect the value (that + // it is still equal to the copy from before the updates) + bytes[0] = 127; + assertEquals(copy, wrapped); + + // Sanity check for copy(): modify within the wrapped slice and check the copy differs now. + bytes[2] = 42; + assertEquals("0x2a7f", wrapped.toHexString()); + assertEquals(Bytes.fromHexString("0x7f7f"), copy); + } + + @Test + void ofBytes() { + assertArrayEquals(Bytes.of().toArrayUnsafe(), new byte[] {}); + assertArrayEquals(Bytes.of((byte) 1, (byte) 2).toArrayUnsafe(), new byte[] {1, 2}); + assertArrayEquals( + Bytes.of((byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5).toArrayUnsafe(), + new byte[] {1, 2, 3, 4, 5}); + assertArrayEquals( + Bytes.of((byte) -1, (byte) 2, (byte) -3).toArrayUnsafe(), new byte[] {-1, 2, -3}); + } + + @Test + void ofInts() { + assertArrayEquals(Bytes.of(1, 2).toArrayUnsafe(), new byte[] {1, 2}); + assertArrayEquals(Bytes.of(1, 2, 3, 4, 5).toArrayUnsafe(), new byte[] {1, 2, 3, 4, 5}); + assertArrayEquals(Bytes.of(0xff, 0x7f, 0x80).toArrayUnsafe(), new byte[] {-1, 127, -128}); + } + + @Test + void ofIntsTooBig() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.of(2, 3, 256)); + assertEquals("3th value 256 does not fit a byte", exception.getMessage()); + } + + @Test + void ofIntsTooLow() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.of(2, -1, 3)); + assertEquals("2th value -1 does not fit a byte", exception.getMessage()); + } + + @Test + void minimalBytes() { + assertEquals(h("0x"), Bytes.minimalBytes(0)); + assertEquals(h("0x01"), Bytes.minimalBytes(1)); + assertEquals(h("0x04"), Bytes.minimalBytes(4)); + assertEquals(h("0x10"), Bytes.minimalBytes(16)); + assertEquals(h("0xFF"), Bytes.minimalBytes(255)); + assertEquals(h("0x0100"), Bytes.minimalBytes(256)); + assertEquals(h("0x0200"), Bytes.minimalBytes(512)); + assertEquals(h("0x010000"), Bytes.minimalBytes(1L << 16)); + assertEquals(h("0x01000000"), Bytes.minimalBytes(1L << 24)); + assertEquals(h("0x0100000000"), Bytes.minimalBytes(1L << 32)); + assertEquals(h("0x010000000000"), Bytes.minimalBytes(1L << 40)); + assertEquals(h("0x01000000000000"), Bytes.minimalBytes(1L << 48)); + assertEquals(h("0x0100000000000000"), Bytes.minimalBytes(1L << 56)); + assertEquals(h("0xFFFFFFFFFFFFFFFF"), Bytes.minimalBytes(-1L)); + } + + @Test + void ofUnsignedShort() { + assertEquals(h("0x0000"), Bytes.ofUnsignedShort(0)); + assertEquals(h("0x0001"), Bytes.ofUnsignedShort(1)); + assertEquals(h("0x0100"), Bytes.ofUnsignedShort(256)); + assertEquals(h("0xFFFF"), Bytes.ofUnsignedShort(65535)); + } + + @Test + void ofUnsignedShortLittleEndian() { + assertEquals(h("0x0000"), Bytes.ofUnsignedShort(0, LITTLE_ENDIAN)); + assertEquals(h("0x0100"), Bytes.ofUnsignedShort(1, LITTLE_ENDIAN)); + assertEquals(h("0x0001"), Bytes.ofUnsignedShort(256, LITTLE_ENDIAN)); + assertEquals(h("0xFFFF"), Bytes.ofUnsignedShort(65535, LITTLE_ENDIAN)); + } + + @Test + void ofUnsignedShortNegative() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> Bytes.ofUnsignedShort(-1)); + assertEquals( + "Value -1 cannot be represented as an unsigned short (it is negative or too big)", + exception.getMessage()); + } + + @Test + void ofUnsignedShortTooBig() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> Bytes.ofUnsignedShort(65536)); + assertEquals( + "Value 65536 cannot be represented as an unsigned short (it is negative or too big)", + exception.getMessage()); + } + + @Test + void asUnsignedBigIntegerConstants() { + assertEquals(bi("0"), Bytes.EMPTY.toUnsignedBigInteger()); + assertEquals(bi("1"), Bytes.of(1).toUnsignedBigInteger()); + } + + @Test + void asSignedBigIntegerConstants() { + assertEquals(bi("0"), Bytes.EMPTY.toBigInteger()); + assertEquals(bi("1"), Bytes.of(1).toBigInteger()); + } + + @Test + void fromHexStringLenient() { + assertEquals(Bytes.of(), Bytes.fromHexStringLenient("")); + assertEquals(Bytes.of(), Bytes.fromHexStringLenient("0x")); + assertEquals(Bytes.of(0), Bytes.fromHexStringLenient("0")); + assertEquals(Bytes.of(0), Bytes.fromHexStringLenient("0x0")); + assertEquals(Bytes.of(0), Bytes.fromHexStringLenient("00")); + assertEquals(Bytes.of(0), Bytes.fromHexStringLenient("0x00")); + assertEquals(Bytes.of(1), Bytes.fromHexStringLenient("0x1")); + assertEquals(Bytes.of(1), Bytes.fromHexStringLenient("0x01")); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("1FF2A")); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1FF2A")); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1ff2a")); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1fF2a")); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("01FF2A")); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x01FF2A")); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x01ff2A")); + } + + @Test + void compareTo() { + assertEquals(1, Bytes.of(0x05).compareTo(Bytes.of(0x01))); + assertEquals(1, Bytes.of(0x05).compareTo(Bytes.of(0x01))); + assertEquals(1, Bytes.of(0xef).compareTo(Bytes.of(0x01))); + assertEquals(1, Bytes.of(0xef).compareTo(Bytes.of(0x00, 0x01))); + assertEquals(1, Bytes.of(0x00, 0x00, 0xef).compareTo(Bytes.of(0x00, 0x01))); + assertEquals(1, Bytes.of(0x00, 0xef).compareTo(Bytes.of(0x00, 0x00, 0x01))); + assertEquals(1, Bytes.of(0xef, 0xf0).compareTo(Bytes.of(0xff))); + assertEquals(1, Bytes.of(0xef, 0xf0).compareTo(Bytes.of(0x01))); + assertEquals(1, Bytes.of(0xef, 0xf1).compareTo(Bytes.of(0xef, 0xf0))); + assertEquals(1, Bytes.of(0x00, 0x00, 0x01).compareTo(Bytes.of(0x00, 0x00))); + assertEquals(0, Bytes.of(0xef, 0xf0).compareTo(Bytes.of(0xef, 0xf0))); + assertEquals(-1, Bytes.of(0xef, 0xf0).compareTo(Bytes.of(0xef, 0xf5))); + assertEquals(-1, Bytes.of(0xef).compareTo(Bytes.of(0xff))); + assertEquals(-1, Bytes.of(0x01).compareTo(Bytes.of(0xff))); + assertEquals(-1, Bytes.of(0x01).compareTo(Bytes.of(0x01, 0xff))); + assertEquals(-1, Bytes.of(0x00, 0x00, 0x01).compareTo(Bytes.of(0x00, 0x02))); + assertEquals(-1, Bytes.of(0x00, 0x01).compareTo(Bytes.of(0x00, 0x00, 0x05))); + assertEquals(0, Bytes.fromHexString("0x0000").compareTo(Bytes.fromHexString("0x00"))); + assertEquals(0, Bytes.fromHexString("0x00").compareTo(Bytes.fromHexString("0x0000"))); + assertEquals(0, Bytes.fromHexString("0x000000").compareTo(Bytes.fromHexString("0x000000"))); + assertEquals(-1, Bytes.fromHexString("0x000001").compareTo(Bytes.fromHexString("0x0001"))); + } + + @Test + void fromHexStringLenientInvalidInput() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexStringLenient("foo")); + assertEquals( + "Illegal character 'o' found at index 1 in hex binary representation", + exception.getMessage()); + } + + @Test + void fromHexStringLenientLeftPadding() { + assertEquals(Bytes.of(), Bytes.fromHexStringLenient("", 0)); + assertEquals(Bytes.of(0), Bytes.fromHexStringLenient("", 1)); + assertEquals(Bytes.of(0, 0), Bytes.fromHexStringLenient("", 2)); + assertEquals(Bytes.of(0, 0), Bytes.fromHexStringLenient("0x", 2)); + assertEquals(Bytes.of(0, 0, 0), Bytes.fromHexStringLenient("0", 3)); + assertEquals(Bytes.of(0, 0, 0), Bytes.fromHexStringLenient("0x0", 3)); + assertEquals(Bytes.of(0, 0, 0), Bytes.fromHexStringLenient("00", 3)); + assertEquals(Bytes.of(0, 0, 0), Bytes.fromHexStringLenient("0x00", 3)); + assertEquals(Bytes.of(0, 0, 1), Bytes.fromHexStringLenient("0x1", 3)); + assertEquals(Bytes.of(0, 0, 1), Bytes.fromHexStringLenient("0x01", 3)); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("1FF2A", 3)); + assertEquals(Bytes.of(0x00, 0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1FF2A", 4)); + assertEquals(Bytes.of(0x00, 0x00, 0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1ff2a", 5)); + assertEquals(Bytes.of(0x00, 0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1fF2a", 4)); + assertEquals(Bytes.of(0x00, 0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("01FF2A", 4)); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x01FF2A", 3)); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x01ff2A", 3)); + } + + @Test + void fromHexStringLenientLeftPaddingInvalidInput() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexStringLenient("foo", 10)); + assertEquals( + "Illegal character 'o' found at index 1 in hex binary representation", + exception.getMessage()); + } + + @Test + void fromHexStringLenientLeftPaddingInvalidSize() { + Throwable exception = + assertThrows( + IllegalArgumentException.class, () -> Bytes.fromHexStringLenient("0x001F34", 2)); + assertEquals( + "Hex value is too large: expected at most 2 bytes but got 3", exception.getMessage()); + } + + @Test + void fromHexString() { + assertEquals(Bytes.of(), Bytes.fromHexString("0x")); + assertEquals(Bytes.of(0), Bytes.fromHexString("00")); + assertEquals(Bytes.of(0), Bytes.fromHexString("0x00")); + assertEquals(Bytes.of(1), Bytes.fromHexString("0x01")); + assertEquals(Bytes.of(1, 0xff, 0x2a), Bytes.fromHexString("01FF2A")); + assertEquals(Bytes.of(1, 0xff, 0x2a), Bytes.fromHexString("0x01FF2A")); + assertEquals(Bytes.of(1, 0xff, 0x2a), Bytes.fromHexString("0x01ff2a")); + assertEquals(Bytes.of(1, 0xff, 0x2a), Bytes.fromHexString("0x01fF2a")); + } + + @Test + void fromHexStringInvalidInput() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexString("fooo")); + assertEquals( + "Illegal character 'o' found at index 1 in hex binary representation", + exception.getMessage()); + } + + @Test + void fromHexStringNotLenient() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexString("0x100")); + assertEquals("Invalid odd-length hex binary representation", exception.getMessage()); + } + + @Test + void fromHexStringLeftPadding() { + assertEquals(Bytes.of(), Bytes.fromHexString("0x", 0)); + assertEquals(Bytes.of(0, 0), Bytes.fromHexString("0x", 2)); + assertEquals(Bytes.of(0, 0, 0, 0), Bytes.fromHexString("0x", 4)); + assertEquals(Bytes.of(0, 0), Bytes.fromHexString("00", 2)); + assertEquals(Bytes.of(0, 0), Bytes.fromHexString("0x00", 2)); + assertEquals(Bytes.of(0, 0, 1), Bytes.fromHexString("0x01", 3)); + assertEquals(Bytes.of(0x00, 0x01, 0xff, 0x2a), Bytes.fromHexString("01FF2A", 4)); + assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexString("0x01FF2A", 3)); + assertEquals(Bytes.of(0x00, 0x00, 0x01, 0xff, 0x2a), Bytes.fromHexString("0x01ff2a", 5)); + assertEquals(Bytes.of(0x00, 0x00, 0x01, 0xff, 0x2a), Bytes.fromHexString("0x01fF2a", 5)); + } + + @Test + void fromHexStringLeftPaddingInvalidInput() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexString("fooo", 4)); + assertEquals( + "Illegal character 'o' found at index 1 in hex binary representation", + exception.getMessage()); + } + + @Test + void fromHexStringLeftPaddingNotLenient() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexString("0x100", 4)); + assertEquals("Invalid odd-length hex binary representation", exception.getMessage()); + } + + @Test + void fromHexStringLeftPaddingInvalidSize() { + Throwable exception = + assertThrows( + IllegalArgumentException.class, () -> Bytes.fromHexStringLenient("0x001F34", 2)); + assertEquals( + "Hex value is too large: expected at most 2 bytes but got 3", exception.getMessage()); + } + + @Test + void fromBase64Roundtrip() { + Bytes value = Bytes.fromBase64String("deadbeefISDAbest"); + assertEquals("deadbeefISDAbest", value.toBase64String()); + } + + @Test + void littleEndianRoundtrip() { + int val = Integer.MAX_VALUE - 5; + Bytes littleEndianEncoded = Bytes.ofUnsignedInt(val, LITTLE_ENDIAN); + assertEquals(4, littleEndianEncoded.size()); + Bytes bigEndianEncoded = Bytes.ofUnsignedInt(val); + assertEquals(bigEndianEncoded.get(0), littleEndianEncoded.get(3)); + assertEquals(bigEndianEncoded.get(1), littleEndianEncoded.get(2)); + assertEquals(bigEndianEncoded.get(2), littleEndianEncoded.get(1)); + assertEquals(bigEndianEncoded.get(3), littleEndianEncoded.get(0)); + + int read = littleEndianEncoded.toInt(LITTLE_ENDIAN); + assertEquals(val, read); + } + + @Test + void littleEndianLongRoundtrip() { + long val = 1L << 46; + Bytes littleEndianEncoded = Bytes.ofUnsignedLong(val, LITTLE_ENDIAN); + assertEquals(8, littleEndianEncoded.size()); + Bytes bigEndianEncoded = Bytes.ofUnsignedLong(val); + assertEquals(bigEndianEncoded.get(0), littleEndianEncoded.get(7)); + assertEquals(bigEndianEncoded.get(1), littleEndianEncoded.get(6)); + assertEquals(bigEndianEncoded.get(2), littleEndianEncoded.get(5)); + assertEquals(bigEndianEncoded.get(3), littleEndianEncoded.get(4)); + assertEquals(bigEndianEncoded.get(4), littleEndianEncoded.get(3)); + assertEquals(bigEndianEncoded.get(5), littleEndianEncoded.get(2)); + assertEquals(bigEndianEncoded.get(6), littleEndianEncoded.get(1)); + assertEquals(bigEndianEncoded.get(7), littleEndianEncoded.get(0)); + + long read = littleEndianEncoded.toLong(LITTLE_ENDIAN); + assertEquals(val, read); + } + + @Test + void wrapModifyNoChange() { + Bytes value1 = Bytes.fromHexString("deadbeef"); + Bytes result = Bytes.wrap(value1, value1); + assertEquals(Bytes.fromHexString("deadbeefdeadbeef"), result); + MutableBytes value1Mutable = value1.mutableCopy(); + value1Mutable.set(0, (byte) 0); + assertEquals(Bytes.fromHexString("deadbeefdeadbeef"), result); + } + + @Test + void random() { + Bytes value = Bytes.random(20); + assertNotEquals(value, Bytes.random(20)); + assertEquals(20, value.size()); + } + + @Test + void getInt() { + Bytes value = Bytes.fromHexString("0x00000001"); + assertEquals(1, value.getInt(0)); + assertEquals(16777216, value.getInt(0, LITTLE_ENDIAN)); + assertEquals(1, value.toInt()); + assertEquals(16777216, value.toInt(LITTLE_ENDIAN)); + } + + @Test + void getLong() { + Bytes value = Bytes.fromHexString("0x0000000000000001"); + assertEquals(1, value.getLong(0)); + assertEquals(72057594037927936L, value.getLong(0, LITTLE_ENDIAN)); + assertEquals(1, value.toLong()); + assertEquals(72057594037927936L, value.toLong(LITTLE_ENDIAN)); + } + + @Test + void numberOfLeadingZeros() { + Bytes value = Bytes.fromHexString("0x00000001"); + assertEquals(31, value.numberOfLeadingZeros()); + } + + @Test + void commonPrefix() { + Bytes value = Bytes.fromHexString("0x01234567"); + Bytes value2 = Bytes.fromHexString("0x01236789"); + assertEquals(2, value.commonPrefixLength(value2)); + assertEquals(Bytes.fromHexString("0x0123"), value.commonPrefix(value2)); + } + + @Test + void testWrapByteBufEmpty() { + ByteBuf buffer = Unpooled.buffer(0); + assertSame(Bytes.EMPTY, Bytes.wrapByteBuf(buffer)); + } + + @Test + void testWrapByteBufWithIndexEmpty() { + ByteBuf buffer = Unpooled.buffer(3); + assertSame(Bytes.EMPTY, Bytes.wrapByteBuf(buffer, 3, 0)); + } + + @Test + void testWrapByteBufSizeWithOffset() { + ByteBuf buffer = Unpooled.buffer(10); + assertEquals(1, Bytes.wrapByteBuf(buffer, 1, 1).size()); + } + + @Test + void testWrapByteBufSize() { + ByteBuf buffer = Unpooled.buffer(20); + assertEquals(20, Bytes.wrapByteBuf(buffer).size()); + } + + @Test + void testWrapByteBufReadableBytes() { + ByteBuf buffer = Unpooled.buffer(20).writeByte(3); + assertEquals(1, Bytes.wrapByteBuf(buffer, 0, buffer.readableBytes()).size()); + } + + @Test + void testTrimLeadingZeros() { + Bytes b = Bytes.fromHexString("0x000000f300567800"); + assertEquals(Bytes.fromHexString("0xf300567800"), b.trimLeadingZeros()); + } + + @Test + void testTrimTrailingZeros() { + Bytes b = Bytes.fromHexString("0x000000f300567800"); + assertEquals(Bytes.fromHexString("0x000000f3005678"), b.trimTrailingZeros()); + } +} diff --git a/bytes/src/test/java/org/apache/tuweni/v2/bytes/CommonBytesTest.java b/bytes/src/test/java/org/apache/tuweni/v2/bytes/CommonBytesTest.java new file mode 100644 index 000000000..bfdef1df6 --- /dev/null +++ b/bytes/src/test/java/org/apache/tuweni/v2/bytes/CommonBytesTest.java @@ -0,0 +1,624 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.function.Function; + +import io.netty.buffer.Unpooled; +import io.vertx.core.buffer.Buffer; +import org.junit.jupiter.api.Test; + +abstract class CommonBytesTests { + + abstract Bytes h(String hex); + + abstract MutableBytes m(int size); + + abstract Bytes w(byte[] bytes); + + abstract Bytes of(int... bytes); + + BigInteger bi(String decimal) { + return new BigInteger(decimal); + } + + @Test + void asUnsignedBigInteger() { + // Make sure things are interpreted unsigned. + assertEquals(bi("255"), h("0xFF").toUnsignedBigInteger()); + + // Try 2^100 + Long.MAX_VALUE, as an easy to define a big not too special big integer. + BigInteger expected = BigInteger.valueOf(2).pow(100).add(BigInteger.valueOf(Long.MAX_VALUE)); + + // 2^100 is a one followed by 100 zeros, that's 12 bytes of zeros (=96) plus 4 more zeros (so + // 0x10 == 16). + MutableBytes v = m(13); + v.set(0, (byte) 16); + v.setLong(v.size() - 8, Long.MAX_VALUE); + assertEquals(expected, v.toUnsignedBigInteger()); + } + + @Test + void testAsSignedBigInteger() { + // Make sure things are interpreted signed. + assertEquals(bi("-1"), h("0xFF").toBigInteger()); + + // Try 2^100 + Long.MAX_VALUE, as an easy to define a big but not too special big integer. + BigInteger expected = BigInteger.valueOf(2).pow(100).add(BigInteger.valueOf(Long.MAX_VALUE)); + + // 2^100 is a one followed by 100 zeros, that's 12 bytes of zeros (=96) plus 4 more zeros (so + // 0x10 == 16). + MutableBytes v = m(13); + v.set(0, (byte) 16); + v.setLong(v.size() - 8, Long.MAX_VALUE); + assertEquals(expected, v.toBigInteger()); + + // And for a large negative one, we use -(2^100 + Long.MAX_VALUE), which is: + // 2^100 + Long.MAX_VALUE = 0x10(4 bytes of 0)7F( 7 bytes of 1) + // inverse = 0xEF(4 bytes of 1)80( 7 bytes of 0) + // +1 = 0xEF(4 bytes of 1)80(6 bytes of 0)01 + expected = expected.negate(); + v = m(13); + v.set(0, (byte) 0xEF); + for (int i = 1; i < 5; i++) { + v.set(i, (byte) 0xFF); + } + v.set(5, (byte) 0x80); + // 6 bytes of 0 + v.set(12, (byte) 1); + assertEquals(expected, v.toBigInteger()); + } + + @Test + void testSize() { + assertEquals(0, w(new byte[0]).size()); + assertEquals(1, w(new byte[1]).size()); + assertEquals(10, w(new byte[10]).size()); + } + + @Test + void testGet() { + Bytes v = w(new byte[] {1, 2, 3, 4}); + assertEquals((int) (byte) 1, (int) v.get(0)); + assertEquals((int) (byte) 2, (int) v.get(1)); + assertEquals((int) (byte) 3, (int) v.get(2)); + assertEquals((int) (byte) 4, (int) v.get(3)); + } + + @Test + void testGetNegativeIndex() { + assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).get(-1)); + } + + @Test + void testGetOutOfBound() { + assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).get(4)); + } + + @Test + void testGetInt() { + Bytes value = w(new byte[] {0, 0, 1, 0, -1, -1, -1, -1}); + + // 0x00000100 = 256 + assertEquals(256, value.getInt(0)); + // 0x000100FF = 65536 + 255 = 65791 + assertEquals(65791, value.getInt(1)); + // 0x0100FFFF = 16777216 (2^24) + (65536 - 1) = 16842751 + assertEquals(16842751, value.getInt(2)); + // 0xFFFFFFFF = -1 + assertEquals(-1, value.getInt(4)); + } + + @Test + void testGetIntNegativeIndex() { + assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).getInt(-1)); + } + + @Test + void testGetIntOutOfBound() { + assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).getInt(4)); + } + + @Test + void testGetIntNotEnoughBytes() { + assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).getInt(1)); + } + + @Test + void testAsInt() { + assertEquals(0, Bytes.EMPTY.toInt()); + Bytes value1 = w(new byte[] {0, 0, 1, 0}); + // 0x00000100 = 256 + assertEquals(256, value1.toInt()); + assertEquals(256, value1.slice(2).toInt()); + + Bytes value2 = w(new byte[] {0, 1, 0, -1}); + // 0x000100FF = 65536 + 255 = 65791 + assertEquals(65791, value2.toInt()); + assertEquals(65791, value2.slice(1).toInt()); + + Bytes value3 = w(new byte[] {1, 0, -1, -1}); + // 0x0100FFFF = 16777216 (2^24) + (65536 - 1) = 16842751 + assertEquals(16842751, value3.toInt()); + + Bytes value4 = w(new byte[] {-1, -1, -1, -1}); + // 0xFFFFFFFF = -1 + assertEquals(-1, value4.toInt()); + } + + @Test + void testAsIntTooManyBytes() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> w(new byte[] {1, 2, 3, 4, 5}).toInt()); + assertEquals("Value of size 5 has more than 4 bytes", exception.getMessage()); + } + + @Test + void testGetLong() { + Bytes value1 = w(new byte[] {0, 0, 1, 0, -1, -1, -1, -1, 0, 0}); + // 0x00000100FFFFFFFF = (2^40) + (2^32) - 1 = 1103806595071 + assertEquals(1103806595071L, value1.getLong(0)); + // 0x 000100FFFFFFFF00 = (2^48) + (2^40) - 1 - 255 = 282574488338176 + assertEquals(282574488338176L, value1.getLong(1)); + + Bytes value2 = w(new byte[] {-1, -1, -1, -1, -1, -1, -1, -1}); + assertEquals(-1L, value2.getLong(0)); + } + + @Test + void testGetLongNegativeIndex() { + assertThrows( + IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}).getLong(-1)); + } + + @Test + void testGetLongOutOfBound() { + assertThrows( + IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}).getLong(8)); + } + + @Test + void testGetLongNotEnoughBytes() { + assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).getLong(0)); + } + + @Test + void testAsLong() { + assertEquals(0, Bytes.EMPTY.toLong()); + Bytes value1 = w(new byte[] {0, 0, 1, 0, -1, -1, -1, -1}); + // 0x00000100FFFFFFFF = (2^40) + (2^32) - 1 = 1103806595071 + assertEquals(1103806595071L, value1.toLong()); + assertEquals(1103806595071L, value1.slice(2).toLong()); + Bytes value2 = w(new byte[] {0, 1, 0, -1, -1, -1, -1, 0}); + // 0x000100FFFFFFFF00 = (2^48) + (2^40) - 1 - 255 = 282574488338176 + assertEquals(282574488338176L, value2.toLong()); + assertEquals(282574488338176L, value2.slice(1).toLong()); + + Bytes value3 = w(new byte[] {-1, -1, -1, -1, -1, -1, -1, -1}); + assertEquals(-1L, value3.toLong()); + } + + @Test + void testAsLongTooManyBytes() { + Throwable exception = + assertThrows( + IllegalArgumentException.class, + () -> w(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9}).toLong()); + assertEquals("Value of size 9 has more than 8 bytes", exception.getMessage()); + } + + @Test + void testSlice() { + assertEquals(h("0x"), h("0x0123456789").slice(0, 0)); + assertEquals(h("0x"), h("0x0123456789").slice(2, 0)); + assertEquals(h("0x01"), h("0x0123456789").slice(0, 1)); + assertEquals(h("0x0123"), h("0x0123456789").slice(0, 2)); + + assertEquals(h("0x4567"), h("0x0123456789").slice(2, 2)); + assertEquals(h("0x23456789"), h("0x0123456789").slice(1, 4)); + } + + @Test + void testSliceNegativeOffset() { + assertThrows(IndexOutOfBoundsException.class, () -> h("0x012345").slice(-1, 2)); + } + + @Test + void testSliceOffsetOutOfBound() { + assertThrows(IndexOutOfBoundsException.class, () -> h("0x012345").slice(3, 2)); + } + + @Test + void testSliceTooLong() { + Throwable exception = + assertThrows(IllegalArgumentException.class, () -> h("0x012345").slice(1, 3)); + assertEquals( + "Provided length 3 is too big: the value has only 2 bytes from offset 1", + exception.getMessage()); + } + + @Test + void testMutableCopy() { + Bytes v = h("0x012345"); + + MutableBytes dest = v.mutableCopy(); + + // Initially, copy must be equal. + assertEquals(dest, v); + + // Upon modification, original should not have been modified. + dest.set(0, (byte) -1); + assertNotEquals(dest, v); + assertEquals(h("0x012345"), v); + assertEquals(h("0xFF2345"), dest); + + // The follow does nothing, but simply making sure it doesn't throw. + dest = Bytes.EMPTY.mutableCopy(); + assertEquals(Bytes.EMPTY, dest); + + dest = of(1).mutableCopy(); + assertEquals(h("0x01"), dest); + + dest = of(10).mutableCopy(); + assertEquals(h("0x0A"), dest); + + dest = of(0xff, 0x03).mutableCopy(); + assertEquals(h("0xFF03"), dest); + + dest = of(0xff, 0x03).mutableCopy(); + Bytes destView = dest.slice(1, 1); + assertEquals(h("0x03"), destView); + } + + @Test + void testAppendTo() { + testAppendTo(Bytes.EMPTY, Buffer.buffer(), Bytes.EMPTY); + testAppendTo(Bytes.EMPTY, Buffer.buffer(h("0x1234").toArrayUnsafe()), h("0x1234")); + testAppendTo(h("0x1234"), Buffer.buffer(), h("0x1234")); + testAppendTo(h("0x5678"), Buffer.buffer(h("0x1234").toArrayUnsafe()), h("0x12345678")); + } + + private void testAppendTo(Bytes toAppend, Buffer buffer, Bytes expected) { + toAppend.appendTo(buffer); + assertEquals(expected, Bytes.wrap(buffer.getBytes())); + } + + @Test + void testIsZero() { + assertTrue(Bytes.EMPTY.isZero()); + assertTrue(Bytes.of(0).isZero()); + assertTrue(Bytes.of(0, 0, 0).isZero()); + + assertFalse(Bytes.of(1).isZero()); + assertFalse(Bytes.of(1, 0, 0).isZero()); + assertFalse(Bytes.of(0, 0, 1).isZero()); + assertFalse(Bytes.of(0, 0, 1, 0, 0).isZero()); + } + + @Test + void testIsEmpty() { + assertTrue(Bytes.EMPTY.isEmpty()); + + assertFalse(Bytes.of(0).isEmpty()); + assertFalse(Bytes.of(0, 0, 0).isEmpty()); + assertFalse(Bytes.of(1).isEmpty()); + } + + @Test + void findsCommonPrefix() { + Bytes v = Bytes.of(1, 2, 3, 4, 5, 6, 7); + Bytes o = Bytes.of(1, 2, 3, 4, 4, 3, 2); + assertEquals(4, v.commonPrefixLength(o)); + assertEquals(Bytes.of(1, 2, 3, 4), v.commonPrefix(o)); + } + + @Test + void findsCommonPrefixOfShorter() { + Bytes v = Bytes.of(1, 2, 3, 4, 5, 6, 7); + Bytes o = Bytes.of(1, 2, 3, 4); + assertEquals(4, v.commonPrefixLength(o)); + assertEquals(Bytes.of(1, 2, 3, 4), v.commonPrefix(o)); + } + + @Test + void findsCommonPrefixOfLonger() { + Bytes v = Bytes.of(1, 2, 3, 4); + Bytes o = Bytes.of(1, 2, 3, 4, 4, 3, 2); + assertEquals(4, v.commonPrefixLength(o)); + assertEquals(Bytes.of(1, 2, 3, 4), v.commonPrefix(o)); + } + + @Test + void findsCommonPrefixOfSliced() { + Bytes v = Bytes.of(1, 2, 3, 4).slice(2, 2); + Bytes o = Bytes.of(3, 4, 3, 3, 2).slice(3, 2); + assertEquals(1, v.commonPrefixLength(o)); + assertEquals(Bytes.of(3), v.commonPrefix(o)); + } + + @Test + void testTrimLeadingZeroes() { + assertEquals(h("0x"), h("0x").trimLeadingZeros()); + assertEquals(h("0x"), h("0x00").trimLeadingZeros()); + assertEquals(h("0x"), h("0x00000000").trimLeadingZeros()); + + assertEquals(h("0x01"), h("0x01").trimLeadingZeros()); + assertEquals(h("0x01"), h("0x00000001").trimLeadingZeros()); + + assertEquals(h("0x3010"), h("0x3010").trimLeadingZeros()); + assertEquals(h("0x3010"), h("0x00003010").trimLeadingZeros()); + + assertEquals(h("0xFFFFFFFF"), h("0xFFFFFFFF").trimLeadingZeros()); + assertEquals(h("0xFFFFFFFF"), h("0x000000000000FFFFFFFF").trimLeadingZeros()); + } + + @Test + void testQuantityHexString() { + assertEquals("0x0", h("0x").toQuantityHexString()); + assertEquals("0x0", h("0x0000").toQuantityHexString()); + assertEquals("0x1000001", h("0x01000001").toQuantityHexString()); + } + + @Test + void testHexString() { + assertEquals("0x", h("0x").toShortHexString()); + assertEquals("0x", h("0x0000").toShortHexString()); + assertEquals("0x1000001", h("0x01000001").toShortHexString()); + + assertEquals("0000", h("0x0000").toUnprefixedHexString()); + assertEquals("1234", h("0x1234").toUnprefixedHexString()); + assertEquals("0022", h("0x0022").toUnprefixedHexString()); + } + + @Test + void testEllipsisHexString() { + assertEquals("0x", h("0x").toEllipsisHexString()); + assertEquals("0x0000", h("0x0000").toEllipsisHexString()); + assertEquals("0x01000001", h("0x01000001").toEllipsisHexString()); + assertEquals("0x0100000001", h("0x0100000001").toEllipsisHexString()); + assertEquals("0x0100..0001", h("0x010000000001").toEllipsisHexString()); + assertEquals("0x1234..5678", h("0x123456789abcdef012345678").toEllipsisHexString()); + assertEquals("0x1234..789a", h("0x123456789abcdef0123456789a").toEllipsisHexString()); + assertEquals("0x1234..9abc", h("0x123456789abcdef0123456789abc").toEllipsisHexString()); + assertEquals("0x1234..bcde", h("0x123456789abcdef0123456789abcde").toEllipsisHexString()); + assertEquals("0x1234..def0", h("0x123456789abcdef0123456789abcdef0").toEllipsisHexString()); + assertEquals("0x1234..def0", h("0x123456789abcdef0123456789abcdef0").toEllipsisHexString()); + } + + @Test + void slideToEnd() { + assertEquals(Bytes.of(1, 2, 3, 4), Bytes.of(1, 2, 3, 4).slice(0)); + assertEquals(Bytes.of(2, 3, 4), Bytes.of(1, 2, 3, 4).slice(1)); + assertEquals(Bytes.of(3, 4), Bytes.of(1, 2, 3, 4).slice(2)); + assertEquals(Bytes.of(4), Bytes.of(1, 2, 3, 4).slice(3)); + } + + @Test + void slicePastEndReturnsEmpty() { + assertEquals(Bytes.EMPTY, Bytes.of(1, 2, 3, 4).slice(4)); + assertEquals(Bytes.EMPTY, Bytes.of(1, 2, 3, 4).slice(5)); + } + + @Test + void testUpdate() throws NoSuchAlgorithmException { + // Digest the same byte array in 4 ways: + // 1) directly from the array + // 2) after wrapped using the update() method + // 3) after wrapped and copied using the update() method + // 4) after wrapped but getting the byte manually + // and check all compute the same digest. + MessageDigest md1 = MessageDigest.getInstance("SHA-1"); + MessageDigest md2 = MessageDigest.getInstance("SHA-1"); + MessageDigest md3 = MessageDigest.getInstance("SHA-1"); + MessageDigest md4 = MessageDigest.getInstance("SHA-1"); + + byte[] toDigest = new BigInteger("12324029423415041783577517238472017314").toByteArray(); + Bytes wrapped = w(toDigest); + + byte[] digest1 = md1.digest(toDigest); + + wrapped.update(md2); + byte[] digest2 = md2.digest(); + + wrapped.mutableCopy().update(md3); + byte[] digest3 = md3.digest(); + + for (int i = 0; i < wrapped.size(); i++) md4.update(wrapped.get(i)); + byte[] digest4 = md4.digest(); + + assertArrayEquals(digest2, digest1); + assertArrayEquals(digest3, digest1); + assertArrayEquals(digest4, digest1); + } + + @Test + void testArrayExtraction() { + // extractArray() and getArrayUnsafe() have essentially the same contract... + testArrayExtraction(Bytes::toArrayUnsafe); + + // But on top of the basic, extractArray() guarantees modifying the returned array is safe from + // impacting the original value (not that getArrayUnsafe makes no guarantees here one way or + // another, so there is nothing to test). + byte[] orig = new byte[] {1, 2, 3, 4}; + Bytes value = w(orig); + byte[] extracted = value.mutableCopy().toArray(); + assertArrayEquals(orig, extracted); + Arrays.fill(extracted, (byte) -1); + assertArrayEquals(extracted, new byte[] {-1, -1, -1, -1}); + assertArrayEquals(orig, new byte[] {1, 2, 3, 4}); + assertEquals(of(1, 2, 3, 4), value); + } + + private void testArrayExtraction(Function extractor) { + byte[] bytes = new byte[0]; + assertArrayEquals(extractor.apply(Bytes.EMPTY), bytes); + + byte[][] toTest = + new byte[][] {new byte[] {1}, new byte[] {1, 2, 3, 4, 5, 6}, new byte[] {-1, -1, 0, -1}}; + for (byte[] array : toTest) { + assertArrayEquals(extractor.apply(w(array)), array); + } + + // Test slightly more complex interactions + assertArrayEquals( + extractor.apply(w(new byte[] {1, 2, 3, 4, 5}).slice(2, 2)), new byte[] {3, 4}); + assertArrayEquals(extractor.apply(w(new byte[] {1, 2, 3, 4, 5}).slice(2, 0)), new byte[] {}); + } + + @Test + void testToString() { + assertEquals("0x", Bytes.EMPTY.toString()); + + assertEquals("0x01", of(1).toString()); + assertEquals("0x0aff03", of(0x0a, 0xff, 0x03).toString()); + } + + @Test + void testHasLeadingZeroByte() { + assertFalse(Bytes.fromHexString("0x").hasLeadingZeroByte()); + assertTrue(Bytes.fromHexString("0x0012").hasLeadingZeroByte()); + assertFalse(Bytes.fromHexString("0x120012").hasLeadingZeroByte()); + } + + @Test + void testNumberOfLeadingZeroBytes() { + assertEquals(0, Bytes.fromHexString("0x12").numberOfLeadingZeroBytes()); + assertEquals(1, Bytes.fromHexString("0x0012").numberOfLeadingZeroBytes()); + assertEquals(2, Bytes.fromHexString("0x000012").numberOfLeadingZeroBytes()); + assertEquals(0, Bytes.fromHexString("0x").numberOfLeadingZeroBytes()); + assertEquals(1, Bytes.fromHexString("0x00").numberOfLeadingZeroBytes()); + assertEquals(2, Bytes.fromHexString("0x0000").numberOfLeadingZeroBytes()); + assertEquals(3, Bytes.fromHexString("0x000000").numberOfLeadingZeroBytes()); + } + + @Test + void testNumberOfTrailingZeroBytes() { + assertEquals(0, Bytes.fromHexString("0x12").numberOfTrailingZeroBytes()); + assertEquals(1, Bytes.fromHexString("0x1200").numberOfTrailingZeroBytes()); + assertEquals(2, Bytes.fromHexString("0x120000").numberOfTrailingZeroBytes()); + assertEquals(0, Bytes.fromHexString("0x").numberOfTrailingZeroBytes()); + assertEquals(1, Bytes.fromHexString("0x00").numberOfTrailingZeroBytes()); + assertEquals(2, Bytes.fromHexString("0x0000").numberOfTrailingZeroBytes()); + assertEquals(3, Bytes.fromHexString("0x000000").numberOfTrailingZeroBytes()); + } + + @Test + void testHasLeadingZeroBit() { + assertFalse(Bytes.fromHexString("0x").hasLeadingZero()); + assertTrue(Bytes.fromHexString("0x01").hasLeadingZero()); + assertFalse(Bytes.fromHexString("0xFF0012").hasLeadingZero()); + } + + @Test + void testEquals() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key); + Bytes b2 = w(key); + assertEquals(b.hashCode(), b2.hashCode()); + } + + @Test + void testEqualsWithOffset() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key).slice(16, 4); + Bytes b2 = w(key).slice(16, 8).slice(0, 4); + assertEquals(b, b2); + } + + @Test + void testHashCode() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key); + Bytes b2 = w(key); + assertEquals(b.hashCode(), b2.hashCode()); + } + + @Test + void testHashCodeWithOffset() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key).slice(16, 16); + Bytes b2 = w(key).slice(16, 16); + assertEquals(b.hashCode(), b2.hashCode()); + } + + @Test + void testHashCodeWithByteBufferWrappingBytes() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key); + Bytes other = Bytes.wrapByteBuffer(ByteBuffer.wrap(key)); + assertEquals(b.hashCode(), other.hashCode()); + } + + @Test + void testEqualsWithByteBufferWrappingBytes() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key); + Bytes other = Bytes.wrapByteBuffer(ByteBuffer.wrap(key)); + assertEquals(b, other); + } + + @Test + void testHashCodeWithBufferWrappingBytes() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key); + Bytes other = Bytes.wrapBuffer(Buffer.buffer(key)); + assertEquals(b.hashCode(), other.hashCode()); + } + + @Test + void testEqualsWithBufferWrappingBytes() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key); + Bytes other = Bytes.wrapBuffer(Buffer.buffer(key)); + assertEquals(b, other); + } + + @Test + void testHashCodeWithByteBufWrappingBytes() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key); + Bytes other = Bytes.wrapByteBuf(Unpooled.copiedBuffer(key)); + assertEquals(b.hashCode(), other.hashCode()); + } + + @Test + void testEqualsWithByteBufWrappingBytes() { + SecureRandom random = new SecureRandom(); + byte[] key = new byte[32]; + random.nextBytes(key); + Bytes b = w(key); + Bytes other = Bytes.wrapByteBuf(Unpooled.copiedBuffer(key)); + assertEquals(b, other); + } +} diff --git a/bytes/src/test/java/org/apache/tuweni/v2/bytes/ConcatenatedBytesTest.java b/bytes/src/test/java/org/apache/tuweni/v2/bytes/ConcatenatedBytesTest.java new file mode 100644 index 000000000..bb36b12f7 --- /dev/null +++ b/bytes/src/test/java/org/apache/tuweni/v2/bytes/ConcatenatedBytesTest.java @@ -0,0 +1,181 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; + +import java.security.MessageDigest; +import java.util.Arrays; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InOrder; + +class ConcatenatedBytesTest { + + @ParameterizedTest + @MethodSource("concatenatedWrapProvider") + void concatenatedWrap(Object arr1, Object arr2) { + byte[] first = (byte[]) arr1; + byte[] second = (byte[]) arr2; + byte[] res = Bytes.wrap(Bytes.wrap(first), Bytes.wrap(second)).toArrayUnsafe(); + assertArrayEquals(Arrays.copyOfRange(res, 0, first.length), first); + assertArrayEquals(Arrays.copyOfRange(res, first.length, res.length), second); + } + + @SuppressWarnings("UnusedMethod") + private static Stream concatenatedWrapProvider() { + return Stream.of( + Arguments.of(new byte[] {}, new byte[] {}), + Arguments.of(new byte[] {}, new byte[] {1, 2, 3}), + Arguments.of(new byte[] {1, 2, 3}, new byte[] {}), + Arguments.of(new byte[] {1, 2, 3}, new byte[] {4, 5})); + } + + @Test + void testConcatenatedWrapReflectsUpdates() { + byte[] first = new byte[] {1, 2, 3}; + byte[] second = new byte[] {4, 5}; + byte[] expected1 = new byte[] {1, 2, 3, 4, 5}; + Bytes res = Bytes.wrap(Bytes.wrap(first), Bytes.wrap(second)); + assertArrayEquals(res.toArrayUnsafe(), expected1); + + first[1] = 42; + second[0] = 42; + byte[] expected2 = new byte[] {1, 42, 3, 42, 5}; + assertArrayEquals(res.toArrayUnsafe(), expected2); + } + + @Test + void shouldReadConcatenatedValue() { + Bytes bytes = Bytes.wrap(Bytes.fromHexString("0x01234567"), Bytes.fromHexString("0x89ABCDEF")); + assertEquals(8, bytes.size()); + assertEquals("0x0123456789abcdef", bytes.toHexString()); + } + + static Stream sliceProvider() { + return Stream.of( + Arguments.of("0x", 4, 0), + Arguments.of("0x01234567", 0, 4), + Arguments.of("0x0123", 0, 2), + Arguments.of("0x2345", 1, 2), + Arguments.of("0x6789", 3, 2), + Arguments.of("0x89abcdef", 4, 4), + Arguments.of("0xabcd", 5, 2), + Arguments.of("0xef012345", 7, 4), + Arguments.of("0x01234567", 8, 4), + Arguments.of("0x456789abcdef", 10, 6), + Arguments.of("0x89abcdef", 12, 4), + Arguments.of("0xabcd", 13, 2), + Arguments.of("0x0123456789abcdef0123456789ab", 0, 14), + Arguments.of("0x0123456789abcdef0123456789abcd", 0, 15), + Arguments.of("0x0123456789abcdef0123456789abcdef", 0, 16)); + } + + @ParameterizedTest + @MethodSource("sliceProvider") + void sliceValue(String expectedHex, int sliceOffset, int sliceLength) { + Bytes bytes = + Bytes.wrap( + Bytes.fromHexString("0x01234567"), + Bytes.fromHexString("0x89ABCDEF"), + Bytes.fromHexString("0x01234567"), + Bytes.fromHexString("0x89ABCDEF")); + assertEquals(expectedHex, bytes.slice(sliceOffset, sliceLength).toHexString()); + } + + @ParameterizedTest + @MethodSource("sliceProvider") + void slicedValueToArrayUnsafe(String expectedHex, int sliceOffset, int sliceLength) { + Bytes bytes = + Bytes.wrap( + Bytes.fromHexString("0x01234567"), + Bytes.fromHexString("0x89ABCDEF"), + Bytes.fromHexString("0x01234567"), + Bytes.fromHexString("0x89ABCDEF")); + assertThat(Bytes.fromHexString(expectedHex).toArrayUnsafe()) + .containsExactly(bytes.slice(sliceOffset, sliceLength).toArrayUnsafe()); + } + + @Test + void shouldReadDeepConcatenatedValue() { + Bytes bytes = + Bytes.wrap( + Bytes.wrap(Bytes.fromHexString("0x01234567"), Bytes.fromHexString("0x89ABCDEF")), + Bytes.wrap(Bytes.fromHexString("0x01234567"), Bytes.fromHexString("0x89ABCDEF")), + Bytes.fromHexString("0x01234567"), + Bytes.fromHexString("0x89ABCDEF")); + assertEquals(24, bytes.size()); + assertEquals("0x0123456789abcdef0123456789abcdef0123456789abcdef", bytes.toHexString()); + } + + @Test + void testMutableCopy() { + Bytes bytes = Bytes.wrap(Bytes.fromHexString("0x01234567"), Bytes.fromHexString("0x89ABCDEF")); + assertEquals(bytes, bytes.mutableCopy()); + } + + @Test + void testHashcodeUpdates() { + MutableBytes dest = MutableBytes.create(32); + Bytes bytes = Bytes.wrap(dest, Bytes.fromHexString("0x4567")); + int hashCode = bytes.hashCode(); + dest.set(1, (byte) 123); + assertEquals(hashCode, bytes.hashCode()); + } + + @Test + void shouldUpdateMessageDigest() { + Bytes value1 = Bytes.fromHexString("0x01234567"); + Bytes value2 = Bytes.fromHexString("0x89ABCDEF"); + Bytes value3 = Bytes.fromHexString("0x01234567"); + Bytes bytes = Bytes.wrap(value1, value2, value3); + MessageDigest digest = mock(MessageDigest.class); + bytes.update(digest); + + final InOrder inOrder = inOrder(digest); + inOrder.verify(digest).update(value1.toArrayUnsafe(), 0, 4); + inOrder.verify(digest).update(value2.toArrayUnsafe(), 0, 4); + inOrder.verify(digest).update(value3.toArrayUnsafe(), 0, 4); + } + + @Test + void and() { + Bytes part1 = Bytes.of(0xaa, 0xaa); + Bytes part2 = Bytes.of(0xbb, 0xbb); + Bytes expected = Bytes.fromHexString("0x000000000000aaaabbbb"); + + Bytes concat = ConcatenatedBytes.create(part1, part2); + MutableBytes mutableBytes = MutableBytes.fromHexString("0xffffffffffffffffffff"); + assertThat(mutableBytes.and(concat)).isEqualTo(expected); + } + + @Test + void or() { + Bytes part1 = Bytes.of(0xaa, 0xaa); + Bytes part2 = Bytes.of(0xbb, 0xbb); + Bytes expected = Bytes.fromHexString("0xffffffffffffffffbbbb"); + + Bytes concat = ConcatenatedBytes.create(part1, part2); + MutableBytes mutableBytes = MutableBytes.fromHexString("0xffffffffffffffff0000"); + assertThat(mutableBytes.or(concat)).isEqualTo(expected); + } + + @Test + void xor() { + Bytes part1 = Bytes.of(0xaa, 0xaa); + Bytes part2 = Bytes.of(0xbb, 0xbb); + Bytes expected = Bytes.fromHexString("0xffffffffffff55554444"); + + Bytes concat = ConcatenatedBytes.create(part1, part2); + MutableBytes mutableBytes = MutableBytes.fromHexString("0xffffffffffffffffffff"); + assertThat(mutableBytes.xor(concat)).isEqualTo(expected); + } +} diff --git a/bytes/src/test/java/org/apache/tuweni/v2/bytes/DelegateBytesTest.java b/bytes/src/test/java/org/apache/tuweni/v2/bytes/DelegateBytesTest.java new file mode 100644 index 000000000..34c9b2bef --- /dev/null +++ b/bytes/src/test/java/org/apache/tuweni/v2/bytes/DelegateBytesTest.java @@ -0,0 +1,30 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +class DelegateBytesTest extends CommonBytesTests { + + @Override + Bytes h(String hex) { + Bytes bytes = Bytes.fromHexString(hex); + return new DelegatingBytes(bytes, bytes.size()); + } + + @Override + MutableBytes m(int size) { + // no-op + return MutableBytes.create(size); + } + + @Override + Bytes w(byte[] bytes) { + Bytes bytesValue = Bytes.wrap(bytes); + return new DelegatingBytes(bytesValue, bytesValue.size()); + } + + @Override + Bytes of(int... bytes) { + Bytes bytesValue = Bytes.of(bytes); + return new DelegatingBytes(bytesValue, bytesValue.size()); + } +} diff --git a/bytes/src/test/java/org/apache/tuweni/v2/bytes/MutableBytesTest.java b/bytes/src/test/java/org/apache/tuweni/v2/bytes/MutableBytesTest.java new file mode 100644 index 000000000..afe34b1ce --- /dev/null +++ b/bytes/src/test/java/org/apache/tuweni/v2/bytes/MutableBytesTest.java @@ -0,0 +1,421 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.bytes; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.ByteBuffer; + +import io.netty.buffer.Unpooled; +import io.vertx.core.buffer.Buffer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class MutableBytesTest { + + @Test + void testMutableBytesWrap() { + MutableBytes b = MutableBytes.fromArray(Bytes.fromHexString("deadbeef").toArrayUnsafe(), 1, 3); + assertEquals(Bytes.fromHexString("adbeef"), b); + } + + @Test + void testClear() { + MutableBytes b = MutableBytes.fromHexString("deadbeef"); + assertEquals(Bytes.fromHexString("00000000"), b.clear()); + } + + @Test + void testFill() { + MutableBytes b = MutableBytes.create(2); + assertEquals(Bytes.fromHexString("0x2222"), b.fill((byte) 34)); + } + + @Test + void testDecrementAndIncrement() { + MutableBytes b = MutableBytes.create(2); + assertEquals(Bytes.fromHexString("0x0001"), b.increment()); + assertEquals(Bytes.fromHexString("0x0000"), b.decrement()); + + assertEquals(Bytes.fromHexString("0xFFFE"), b.fill((byte) 0xFF).decrement()); + + b = MutableBytes.fromHexString("0x00FF"); + assertEquals(Bytes.fromHexString("0x0100"), b.increment()); + } + + @Test + void setLong() { + MutableBytes b = MutableBytes.create(8); + b.setLong(0, 256); + assertEquals(Bytes.fromHexString("0x0000000000000100"), b); + } + + @Test + void setInt() { + MutableBytes b = MutableBytes.create(4); + b.setInt(0, 256); + assertEquals(Bytes.fromHexString("0x00000100"), b); + } + + @Test + void setIntOverflow() { + MutableBytes b = MutableBytes.create(2); + assertThrows(IndexOutOfBoundsException.class, () -> b.setInt(0, 18096)); + } + + @Test + void setLongOverflow() { + MutableBytes b = MutableBytes.create(6); + assertThrows(IndexOutOfBoundsException.class, () -> b.setLong(0, Long.MAX_VALUE)); + } + + @Test + void wrapEmpty() { + MutableBytes b = MutableBytes.fromBuffer(Buffer.buffer()); + assertEquals(b.size(), 0); + b = MutableBytes.fromByteBuf(Unpooled.buffer(0)); + assertEquals(b.size(), 0); + } + + @Test + void testHashcodeUpdates() { + MutableBytes dest = MutableBytes.create(32); + int hashCode = dest.hashCode(); + dest.set(1, (byte) 123); + assertNotEquals(hashCode, dest.hashCode()); + } + + @Test + void testHashcodeUpdatesBuffer() { + MutableBytes dest = MutableBytes.fromBuffer(Buffer.buffer(new byte[4])); + int hashCode = dest.hashCode(); + dest.set(1, (byte) 123); + assertNotEquals(hashCode, dest.hashCode()); + } + + @Test + void testHashcodeUpdatesByteBuffer() { + MutableBytes dest = MutableBytes.fromByteBuffer(ByteBuffer.wrap(new byte[4])); + int hashCode = dest.hashCode(); + dest.set(1, (byte) 123); + assertNotEquals(hashCode, dest.hashCode()); + } + + @Test + void testHashcodeUpdatesByteBuf() { + MutableBytes dest = MutableBytes.fromByteBuf(Unpooled.buffer(4)); + int hashCode = dest.hashCode(); + dest.set(1, (byte) 123); + assertNotEquals(hashCode, dest.hashCode()); + } + + @ParameterizedTest + @CsvSource({ + "0x01, 0x00, 1", + "0x02, 0x01, 1", + "0x04, 0x01, 2", + "0x08, 0x01, 3", + "0x10, 0x01, 4", + "0xFF, 0x0F, 4", + "0xFFFF, 0x00FF, 8", + "0x1234, 0x0123, 4", + "0x8000, 0x0001, 15", + "0x321243, 0x000000, 25", + "0x213211AD, 0x00000213, 20", + "0x7FFFFFFF, 0x3FFFFFFF, 1", + "0xFFFFFFFF, 0x0FFFFFFF, 4", + "0xABCDEF, 0x00ABCD, 8", + "0x12345678, 0x01234567, 4", + "0x00, 0x00, 1", + "0x01, 0x01, 0", + "0xAA55, 0x0552, 5", + "0x01000001, 0x00400000, 2" + }) + void shiftRight(String bytesValue, String expected, int shiftBits) { + MutableBytes value = MutableBytes.fromHexString(bytesValue); + assertEquals(Bytes.fromHexString(expected), value.shiftRight(shiftBits)); + } + + @ParameterizedTest + @CsvSource({ + "0x80, 0x00, 1", + "0x40, 0x80, 1", + "0x20, 0x80, 2", + "0x10, 0x80, 3", + "0x08, 0x80, 4", + "0xFF, 0xF0, 4", + "0xFFFF, 0xFF00, 8", + "0x1234, 0x2340, 4", + "0x0001, 0x8000, 15", + "0x321243, 0x000000, 25", + "0x213211AD, 0x1AD00000, 20", + "0xFFFFFFFE, 0xFFFFFFFC, 1", + "0xFFFFFFFF, 0xFFFFFFF0, 4", + "0xABCDEF, 0xCDEF00, 8", + "0x12345678, 0x23456780, 4", + "0x00, 0x00, 1", + "0x80, 0x80, 0", + "0xAA55, 0x4AA0, 5", + "0xAA, 0x40, 5", + "0x01000001, 0x04000004, 2" + }) + void shiftLeft(String bytesValue, String expected, int shiftBits) { + MutableBytes value = MutableBytes.fromHexString(bytesValue); + assertEquals(Bytes.fromHexString(expected), value.shiftLeft(shiftBits)); + } + + @ParameterizedTest + @CsvSource({ + "0x0102, 0x00000102, 4", + "0x0102, 0x0102, 2", + "0x0102, 0x0102, 0", + "0x, 0x000000, 3", + "0x, 0x, 0", + "0x01, 0x000000000001, 6", + "0xFF, 0x00FF, 2", + "0x0000, 0x00000000, 4", + "0x000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000, 29", + "0xE000000000E000000000E000000000, 0x0000000000000000000000000000000000E000000000E000000000E000000000, 32", + "0x123456789ABCDEF0, 0x123456789ABCDEF0, 4" + }) + void leftPad(String bytesValue, String expected, int size) { + MutableBytes value = MutableBytes.fromHexString(bytesValue); + assertEquals(Bytes.fromHexString(expected), value.leftPad(size)); + } + + @ParameterizedTest + @CsvSource({ + "0x0102, 0x01020000, 4", + "0x0102, 0x0102, 2", + "0x0102, 0x0102, 0", + "0x, 0x000000, 3", + "0x, 0x, 0", + "0x01, 0x010000000000, 6", + "0xFF, 0xFF00, 2", + "0x0000, 0x00000000, 4", + "0x000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000, 29", + "0xE000000000E000000000E000000000, 0xE000000000E000000000E0000000000000000000000000000000000000000000, 32", + "0x123456789ABCDEF0, 0x123456789ABCDEF0, 4" + }) + void rightPad(String bytesValue, String expected, int size) { + MutableBytes value = MutableBytes.fromHexString(bytesValue); + assertEquals(Bytes.fromHexString(expected), value.rightPad(size)); + } + + @ParameterizedTest + @CsvSource({"0x000102030405, 0x050403020100", "0x, 0x"}) + void reverse(String bytesValue, String expectedValue) { + MutableBytes bytes = MutableBytes.fromHexString(bytesValue); + assertEquals(MutableBytes.fromHexString(expectedValue), bytes.reverse()); + } + + @ParameterizedTest + @CsvSource({"0x01, 0x02", "0x01FF, 0x0200", "0xFFFFFF, 0x000000"}) + void increment(String bytesValue, String expectedBytes) { + MutableBytes one = MutableBytes.fromHexString(bytesValue); + one.increment(); + assertEquals(MutableBytes.fromHexString(expectedBytes), one); + } + + @ParameterizedTest + @CsvSource({"0x02, 0x01", "0x0100, 0x00FF", "0x000000, 0xFFFFFF"}) + void decrement(String bytesValue, String expectedBytes) { + MutableBytes one = MutableBytes.fromHexString(bytesValue); + one.decrement(); + assertEquals(MutableBytes.fromHexString(expectedBytes), one); + } + + @ParameterizedTest + @CsvSource({ + "0x0F, 0xF0", + "0xA5, 0x5A", + "0xFF, 0x00", + "0xAA, 0x55", + "0x, 0x", + "0x123456, 0xEDCBA9", + "0xABCDEF, 0x543210", + "DEADBEEF, 21524110", + "0x000000, 0xFFFFFF", + "0x0100, 0xFEFF", + "0x01000001, 0xfefffffe" + }) + void not(String bytesValue, String expectedBytes) { + MutableBytes value = MutableBytes.fromHexString(bytesValue).not(); + assertEquals(MutableBytes.fromHexString(expectedBytes), value); + } + + @ParameterizedTest + @CsvSource({ + "0x0F, 0xF0, 0x00", + "0xA5, 0x5A, 0x00", + "0xFF, 0xFF, 0xFF", + "0x00, 0x00, 0x00", + "0xAA, 0x55, 0x00", + "0x, 0x, 0x", + "0x123456, 0x654321, 0x000000", + "0xABCDEF, 0x123456, 0x020446", + "0xDEADBEEF, 0xCAFEBABE, 0xCAACBAAE", + "0x01000001, 0x01000000, 0x01000000", + "0xFF, 0xF012, 0x0012", + "0xF012, 0xFF, 0x0012", + "0xFF, 0x, 0x00", + "0x, 0xFF, 0x00", + "0xDEADBEEF, 0x0000BABE, 0x0000BAAE", + "0x0000BEEF, 0xCAFEBABE, 0x0000BAAE" + }) + void and(String bytes1, String bytes2, String expectedResult) { + MutableBytes value = MutableBytes.fromHexString(bytes1).and(Bytes.fromHexString(bytes2)); + assertEquals(Bytes.fromHexString(expectedResult), value); + } + + @ParameterizedTest + @CsvSource({ + "0, 4, 0xCAACBAAE", + "1, 3, 0x00ACBAAE", + "0, 3, 0x00DEA8BE", + "0, 2, 0x00009AAC", + "2, 1, 0x000000BE", + "2, 2, 0x0000BAAE" + }) + void andOffsetLengthFirstOperand(int offset, int length, String expectedResult) { + Bytes firstOperand = Bytes.fromHexString("0xDEADBEEF"); + Bytes secondOperand = Bytes.fromHexString("0xCAFEBABE"); + firstOperand = firstOperand.slice(offset, length); + assertEquals( + Bytes.fromHexString(expectedResult), firstOperand.mutableCopy().and(secondOperand)); + } + + @ParameterizedTest + @CsvSource({ + "0, 4, 0xCAACBAAE", + "1, 3, 0x00ACBAAE", + "0, 3, 0x0088BEAA", + "0, 2, 0x00008AEE", + "2, 1, 0x000000AA", + "2, 2, 0x0000BAAE" + }) + void andOffsetLengthSecondOperand(int offset, int length, String expectedResult) { + Bytes firstOperand = Bytes.fromHexString("0xDEADBEEF"); + Bytes secondOperand = Bytes.fromHexString("0xCAFEBABE"); + secondOperand = secondOperand.slice(offset, length); + assertEquals( + Bytes.fromHexString(expectedResult), firstOperand.mutableCopy().and(secondOperand)); + } + + @ParameterizedTest + @CsvSource({ + "0x0F, 0xF0, 0xFF", + "0xA5, 0x5A, 0xFF", + "0xFF, 0xFF, 0xFF", + "0x00, 0x00, 0x00", + "0xAA, 0x55, 0xFF", + "0x, 0x, 0x", + "0x123456, 0x654321, 0x777777", + "0xABCDEF, 0x123456, 0xBBFDFF", + "0xDEADBEEF, 0xCAFEBABE, 0xDEFFBEFF", + "0x01000001, 0x01000000, 0x01000001", + "0x0F, 0xF0F0, 0xF0FF", + "0xF0F0, 0x0F, 0xF0FF", + "0xFF, 0x, 0xFF", + "0x, 0xFF, 0xFF", + "0xDEADBEEF, 0x0000BABE, 0xDEADBEFF", + "0x0000BEEF, 0xCAFEBABE, 0xCAFEBEFF" + }) + void or(String bytes1, String bytes2, String expectedResult) { + MutableBytes value = MutableBytes.fromHexString(bytes1).or(Bytes.fromHexString(bytes2)); + assertEquals(Bytes.fromHexString(expectedResult), value); + } + + @ParameterizedTest + @CsvSource({ + "0, 4, 0xDEFFBEFF", + "1, 3, 0xCAFFBEFF", + "0, 3, 0xCAFEBFBE", + "0, 2, 0xCAFEFEBF", + "2, 1, 0xCAFEBABE", + "2, 2, 0xCAFEBEFF" + }) + void orOffsetLengthFirstOperand(int offset, int length, String expectedResult) { + Bytes firstOperand = Bytes.fromHexString("0xDEADBEEF"); + Bytes secondOperand = Bytes.fromHexString("0xCAFEBABE"); + firstOperand = firstOperand.slice(offset, length); + assertEquals(Bytes.fromHexString(expectedResult), firstOperand.mutableCopy().or(secondOperand)); + } + + @ParameterizedTest + @CsvSource({ + "0, 4, 0xDEFFBEFF", + "1, 3, 0xDEFFBEFF", + "0, 3, 0xDEEFFEFF", + "0, 2, 0xDEADFEFF", + "2, 1, 0xDEADBEFF", + "2, 2, 0xDEADBEFF" + }) + void orOffsetLengthSecondOperand(int offset, int length, String expectedResult) { + Bytes firstOperand = Bytes.fromHexString("0xDEADBEEF"); + Bytes secondOperand = Bytes.fromHexString("0xCAFEBABE"); + secondOperand = secondOperand.slice(offset, length); + assertEquals(Bytes.fromHexString(expectedResult), firstOperand.mutableCopy().or(secondOperand)); + } + + @ParameterizedTest + @CsvSource({ + "0x0F, 0xF0, 0xFF", + "0xA5, 0x5A, 0xFF", + "0xFF, 0xFF, 0x00", + "0x00, 0x00, 0x00", + "0xAA, 0x55, 0xFF", + "0x, 0x, 0x", + "0x123456, 0x654321, 0x777777", + "0xABCDEF, 0x123456, 0xB9F9B9", + "0xDEADBEEF, 0xCAFEBABE, 0x14530451", + "0x01000001, 0x01000000, 0x00000001", + "0x0F, 0xF0F0, 0xF0FF", + "0xF0F0, 0x0F, 0xF0FF", + "0xFF, 0x, 0xFF", + "0x, 0xFF, 0xFF", + "0x0000BEEF, 0xCAFEBABE, 0xCAFE0451", + "0xDEADBEEF, 0x0000BABE, 0xDEAD0451" + }) + void xor(String bytes1, String bytes2, String expectedResult) { + MutableBytes value = MutableBytes.fromHexString(bytes1).xor(Bytes.fromHexString(bytes2)); + assertEquals(Bytes.fromHexString(expectedResult), value); + } + + @ParameterizedTest + @CsvSource({ + "0, 4, 0x14530451", + "1, 3, 0xCA530451", + "0, 3, 0xCA201700", + "0, 2, 0xCAFE6413", + "2, 1, 0xCAFEBA00", + "2, 2, 0xCAFE0451" + }) + void xorOffsetLengthFirstOperand(int offset, int length, String expectedResult) { + Bytes firstOperand = Bytes.fromHexString("0xDEADBEEF"); + Bytes secondOperand = Bytes.fromHexString("0xCAFEBABE"); + firstOperand = firstOperand.slice(offset, length); + assertEquals( + Bytes.fromHexString(expectedResult), firstOperand.mutableCopy().xor(secondOperand)); + } + + @ParameterizedTest + @CsvSource({ + "0, 4, 0x14530451", + "1, 3, 0xDE530451", + "0, 3, 0xDE674055", + "0, 2, 0xDEAD7411", + "2, 1, 0xDEADBE55", + "2, 2, 0xDEAD0451" + }) + void xorOffsetLengthSecondOperand(int offset, int length, String expectedResult) { + Bytes firstOperand = Bytes.fromHexString("0xDEADBEEF"); + Bytes secondOperand = Bytes.fromHexString("0xCAFEBABE"); + secondOperand = secondOperand.slice(offset, length); + assertEquals( + Bytes.fromHexString(expectedResult), firstOperand.mutableCopy().xor(secondOperand)); + } +} diff --git a/units/src/main/java/org/apache/tuweni/units/bigints/Utils.java b/units/src/main/java/org/apache/tuweni/units/bigints/Utils.java new file mode 100644 index 000000000..8cf1fd97f --- /dev/null +++ b/units/src/main/java/org/apache/tuweni/units/bigints/Utils.java @@ -0,0 +1,47 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.units.bigints; + +public class Utils { + static void and( + int[] sourceBytesArray, int sourceOffset, byte[] destBytesArray, int destOffset, int length) { + for (int i = 0; i < length; i++) { + // TODO: Speed this up with SIMD + destBytesArray[destOffset + i] = + (byte) (unpackByte(sourceBytesArray, sourceOffset + i) & destBytesArray[destOffset + i]); + } + } + + static void or( + int[] sourceBytesArray, int sourceOffset, byte[] destBytesArray, int destOffset, int length) { + for (int i = 0; i < length; i++) { + // TODO: Speed this up with SIMD + destBytesArray[destOffset + i] = + (byte) (unpackByte(sourceBytesArray, sourceOffset + i) | destBytesArray[destOffset + i]); + } + } + + static void xor( + int[] sourceBytesArray, int sourceOffset, byte[] destBytesArray, int destOffset, int length) { + for (int i = 0; i < length; i++) { + // TODO: Speed this up with SIMD + destBytesArray[destOffset + i] = + (byte) (unpackByte(sourceBytesArray, sourceOffset + i) ^ destBytesArray[destOffset + i]); + } + } + + static byte unpackByte(int[] ints, int index) { + int whichInt = index / 4; + return unpackByte(ints[whichInt], index); + } + + static byte unpackByte(int integer, int index) { + int whichIndex = 3 - index % 4; + return (byte) ((integer >> (8 * whichIndex)) & 0xFF); + } + + static byte unpackByte(long value, int index) { + int whichIndex = 7 - index; + return (byte) ((value >> (8 * whichIndex)) & 0xFF); + } +} diff --git a/units/src/main/java/org/apache/tuweni/v2/units/bigints/UInt256.java b/units/src/main/java/org/apache/tuweni/v2/units/bigints/UInt256.java new file mode 100644 index 000000000..64ead5e58 --- /dev/null +++ b/units/src/main/java/org/apache/tuweni/v2/units/bigints/UInt256.java @@ -0,0 +1,1051 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.units.bigints; + +import static org.apache.tuweni.v2.bytes.Utils.checkElementIndex; + +import org.apache.tuweni.v2.bytes.Bytes; +import org.apache.tuweni.v2.bytes.Bytes32; +import org.apache.tuweni.v2.bytes.MutableBytes; + +import java.math.BigInteger; +import java.util.Arrays; + +import org.jetbrains.annotations.Nullable; + +/** + * An unsigned 256-bit precision number. + * + *

This is a raw 256-bit precision unsigned number of no particular unit. + */ +public final class UInt256 extends Bytes { + private static final int MAX_CONSTANT = 64; + private static final BigInteger BI_MAX_CONSTANT = BigInteger.valueOf(MAX_CONSTANT); + private static UInt256[] CONSTANTS = new UInt256[MAX_CONSTANT + 1]; + + /** The maximum value of a UInt256 */ + public static final UInt256 MAX_VALUE; + + static { + CONSTANTS[0] = new UInt256(Bytes32.ZERO); + for (int i = 1; i <= MAX_CONSTANT; ++i) { + CONSTANTS[i] = new UInt256(i); + } + MAX_VALUE = new UInt256(Bytes32.ZERO.mutableCopy().not()); + } + + /** The minimum value of a UInt256 */ + public static final UInt256 MIN_VALUE = valueOf(0); + + /** The value 0 */ + public static final UInt256 ZERO = valueOf(0); + + /** The value 1 */ + public static final UInt256 ONE = valueOf(1); + + private static final int INTS_SIZE = 32 / 4; + // The mask is used to obtain the value of an int as if it were unsigned. + private static final long LONG_MASK = 0xFFFFFFFFL; + private static final BigInteger P_2_256 = BigInteger.valueOf(2).pow(256); + + // The unsigned int components of the value + private final int[] ints; + + /** + * Return a {@code UInt256} containing the specified value. + * + * @param value The value to create a {@code UInt256} for. + * @return A {@code UInt256} containing the specified value. + * @throws IllegalArgumentException If the value is negative. + */ + public static UInt256 valueOf(long value) { + if (value < 0) { + throw new IllegalArgumentException("Argument must be positive"); + } + if (value <= MAX_CONSTANT) { + return CONSTANTS[(int) value]; + } + return new UInt256(value); + } + + /** + * Return a {@link UInt256} containing the specified value. + * + * @param value the value to create a {@link UInt256} for + * @return a {@link UInt256} containing the specified value + * @throws IllegalArgumentException if the value is negative or too large to be represented as a + * UInt256 + */ + public static UInt256 valueOf(BigInteger value) { + if (value.signum() < 0) { + throw new IllegalArgumentException("Argument must be positive"); + } + if (value.bitLength() > 256) { + throw new IllegalArgumentException("Argument is too large to represent a UInt256"); + } + if (value.compareTo(BI_MAX_CONSTANT) <= 0) { + return CONSTANTS[value.intValue()]; + } + int[] ints = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + ints[i] = value.intValue(); + value = value.shiftRight(32); + } + return new UInt256(ints); + } + + /** + * Return a {@link UInt256} containing the value described by the specified bytes. + * + * @param bytes The bytes containing a {@link UInt256}. + * @return A {@link UInt256} containing the specified value. + * @throws IllegalArgumentException if {@code bytes.size() > 32}. + */ + public static UInt256 fromBytes(final Bytes bytes) { + // TODO: add a fast path for if Bytes.getImpl returns a UInt256 type + if (bytes instanceof UInt256) { + return (UInt256) bytes; + } + if (bytes instanceof Bytes32) { + final byte[] array = bytes.toArrayUnsafe(); + return new UInt256( + new int[] { + (Byte.toUnsignedInt(array[0])) << 24 + | (Byte.toUnsignedInt(array[1]) << 16) + | (Byte.toUnsignedInt(array[2]) << 8) + | (Byte.toUnsignedInt(array[3])), + (Byte.toUnsignedInt(array[4]) << 24) + | (Byte.toUnsignedInt(array[5]) << 16) + | (Byte.toUnsignedInt(array[6]) << 8) + | (Byte.toUnsignedInt(array[7])), + (Byte.toUnsignedInt(array[8]) << 24) + | (Byte.toUnsignedInt(array[9]) << 16) + | (Byte.toUnsignedInt(array[10]) << 8) + | (Byte.toUnsignedInt(array[11])), + (Byte.toUnsignedInt(array[12]) << 24) + | (Byte.toUnsignedInt(array[13]) << 16) + | (Byte.toUnsignedInt(array[14]) << 8) + | (Byte.toUnsignedInt(array[15])), + (Byte.toUnsignedInt(array[16]) << 24) + | (Byte.toUnsignedInt(array[17]) << 16) + | (Byte.toUnsignedInt(array[18]) << 8) + | (Byte.toUnsignedInt(array[19])), + (Byte.toUnsignedInt(array[20]) << 24) + | (Byte.toUnsignedInt(array[21]) << 16) + | (Byte.toUnsignedInt(array[22]) << 8) + | (Byte.toUnsignedInt(array[23])), + (Byte.toUnsignedInt(array[24]) << 24) + | (Byte.toUnsignedInt(array[25]) << 16) + | (Byte.toUnsignedInt(array[26]) << 8) + | (Byte.toUnsignedInt(array[27])), + (Byte.toUnsignedInt(array[28]) << 24) + | (Byte.toUnsignedInt(array[29]) << 16) + | (Byte.toUnsignedInt(array[30]) << 8) + | (Byte.toUnsignedInt(array[31])) + }); + } else { + return new UInt256(bytes.mutableCopy().leftPad(32)); + } + } + + /** + * Parse a hexadecimal string into a {@link UInt256}. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". That + * representation may contain less than 32 bytes, in which case the result is left padded with + * zeros. + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal + * representation or contains more than 32 bytes. + */ + public static UInt256 fromHexString(String str) { + return new UInt256(Bytes32.fromHexStringLenient(str)); + } + + private UInt256(Bytes bytes) { + super(32); + this.ints = new int[INTS_SIZE]; + for (int i = 0, j = 0; i < INTS_SIZE; ++i, j += 4) { + ints[i] = bytes.getInt(j); + } + } + + private UInt256(long value) { + super(32); + this.ints = new int[INTS_SIZE]; + this.ints[INTS_SIZE - 2] = (int) ((value >>> 32) & LONG_MASK); + this.ints[INTS_SIZE - 1] = (int) (value & LONG_MASK); + } + + private UInt256(int[] ints) { + super(32); + this.ints = ints; + } + + @SuppressWarnings("ReferenceEquality") + @Override + public boolean isZero() { + if (this == ZERO) { + return true; + } + for (int i = INTS_SIZE - 1; i >= 0; --i) { + if (this.ints[i] != 0) { + return false; + } + } + return true; + } + + public boolean greaterOrEqualThan(UInt256 other) { + return compareTo(other) >= 0; + } + + public boolean greaterThan(UInt256 other) { + return compareTo(other) > 0; + } + + public boolean lessThan(UInt256 other) { + return compareTo(other) < 0; + } + + public boolean lessOrEqualThan(UInt256 other) { + return compareTo(other) <= 0; + } + + public UInt256 add(UInt256 value) { + if (value.isZero()) { + return this; + } + if (isZero()) { + return value; + } + int[] result = new int[INTS_SIZE]; + boolean constant = true; + long sum = (this.ints[INTS_SIZE - 1] & LONG_MASK) + (value.ints[INTS_SIZE - 1] & LONG_MASK); + result[INTS_SIZE - 1] = (int) (sum & LONG_MASK); + if (result[INTS_SIZE - 1] < 0 || result[INTS_SIZE - 1] > MAX_CONSTANT) { + constant = false; + } + for (int i = INTS_SIZE - 2; i >= 0; --i) { + sum = (this.ints[i] & LONG_MASK) + (value.ints[i] & LONG_MASK) + (sum >>> 32); + result[i] = (int) (sum & LONG_MASK); + constant &= result[i] == 0; + } + if (constant) { + return CONSTANTS[result[INTS_SIZE - 1]]; + } + return new UInt256(result); + } + + public UInt256 add(long value) { + if (value == 0) { + return this; + } + if (value > 0 && isZero()) { + return UInt256.valueOf(value); + } + int[] result = new int[INTS_SIZE]; + boolean constant = true; + long sum = (this.ints[INTS_SIZE - 1] & LONG_MASK) + (value & LONG_MASK); + result[INTS_SIZE - 1] = (int) (sum & LONG_MASK); + if (result[INTS_SIZE - 1] < 0 || result[INTS_SIZE - 1] > MAX_CONSTANT) { + constant = false; + } + sum = (this.ints[INTS_SIZE - 2] & LONG_MASK) + (value >>> 32) + (sum >>> 32); + result[INTS_SIZE - 2] = (int) (sum & LONG_MASK); + constant &= result[INTS_SIZE - 2] == 0; + long signExtent = (value >> 63) & LONG_MASK; + for (int i = INTS_SIZE - 3; i >= 0; --i) { + sum = (this.ints[i] & LONG_MASK) + signExtent + (sum >>> 32); + result[i] = (int) (sum & LONG_MASK); + constant &= result[i] == 0; + } + if (constant) { + return CONSTANTS[result[INTS_SIZE - 1]]; + } + return new UInt256(result); + } + + public UInt256 addMod(UInt256 value, UInt256 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("addMod with zero modulus"); + } + return UInt256.valueOf( + toUnsignedBigInteger() + .add(value.toUnsignedBigInteger()) + .mod(modulus.toUnsignedBigInteger())); + } + + public UInt256 addMod(long value, UInt256 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("addMod with zero modulus"); + } + return UInt256.valueOf( + toUnsignedBigInteger().add(BigInteger.valueOf(value)).mod(modulus.toUnsignedBigInteger())); + } + + public UInt256 addMod(long value, long modulus) { + if (modulus == 0) { + throw new ArithmeticException("addMod with zero modulus"); + } + if (modulus < 0) { + throw new ArithmeticException("addMod unsigned with negative modulus"); + } + return UInt256.valueOf( + toUnsignedBigInteger().add(BigInteger.valueOf(value)).mod(BigInteger.valueOf(modulus))); + } + + public UInt256 subtract(UInt256 value) { + if (value.isZero()) { + return this; + } + + int[] result = new int[INTS_SIZE]; + boolean constant = true; + long sum = + (this.ints[INTS_SIZE - 1] & LONG_MASK) + ((~value.ints[INTS_SIZE - 1]) & LONG_MASK) + 1; + result[INTS_SIZE - 1] = (int) (sum & LONG_MASK); + if (result[INTS_SIZE - 1] < 0 || result[INTS_SIZE - 1] > MAX_CONSTANT) { + constant = false; + } + for (int i = INTS_SIZE - 2; i >= 0; --i) { + sum = (this.ints[i] & LONG_MASK) + ((~value.ints[i]) & LONG_MASK) + (sum >>> 32); + result[i] = (int) (sum & LONG_MASK); + constant &= result[i] == 0; + } + if (constant) { + return CONSTANTS[result[INTS_SIZE - 1]]; + } + return new UInt256(result); + } + + public UInt256 subtract(long value) { + return add(-value); + } + + public UInt256 multiply(UInt256 value) { + if (isZero() || value.isZero()) { + return ZERO; + } + if (value.equals(UInt256.ONE)) { + return this; + } + if (this.equals(UInt256.ONE)) { + return value; + } + return multiply(this.ints, value.ints); + } + + private static UInt256 multiply(int[] x, int[] y) { + int[] result = new int[INTS_SIZE + INTS_SIZE]; + + long carry = 0; + for (int j = INTS_SIZE - 1, k = INTS_SIZE + INTS_SIZE - 1; j >= 0; j--, k--) { + long product = (y[j] & LONG_MASK) * (x[INTS_SIZE - 1] & LONG_MASK) + carry; + result[k] = (int) product; + carry = product >>> 32; + } + result[INTS_SIZE - 1] = (int) carry; + + for (int i = INTS_SIZE - 2; i >= 0; i--) { + carry = 0; + for (int j = INTS_SIZE - 1, k = INTS_SIZE + i; j >= 0; j--, k--) { + long product = (y[j] & LONG_MASK) * (x[i] & LONG_MASK) + (result[k] & LONG_MASK) + carry; + + result[k] = (int) product; + carry = product >>> 32; + } + result[i] = (int) carry; + } + + boolean constant = true; + for (int i = INTS_SIZE; i < (INTS_SIZE + INTS_SIZE) - 1; ++i) { + constant &= (result[i] == 0); + } + if (constant + && result[INTS_SIZE + INTS_SIZE - 1] >= 0 + && result[INTS_SIZE + INTS_SIZE - 1] <= MAX_CONSTANT) { + return CONSTANTS[result[INTS_SIZE + INTS_SIZE - 1]]; + } + return new UInt256(Arrays.copyOfRange(result, INTS_SIZE, INTS_SIZE + INTS_SIZE)); + } + + public UInt256 multiply(long value) { + if (value == 0 || isZero()) { + return ZERO; + } + if (value == 1) { + return this; + } + if (value < 0) { + throw new ArithmeticException("multiply unsigned by negative"); + } + UInt256 other = new UInt256(value); + if (this.equals(UInt256.ONE)) { + return other; + } + return multiply(this.ints, other.ints); + } + + public UInt256 multiplyMod(UInt256 value, UInt256 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (isZero() || value.isZero()) { + return ZERO; + } + if (value.equals(UInt256.ONE)) { + return mod(modulus); + } + return UInt256.valueOf( + toUnsignedBigInteger() + .multiply(value.toUnsignedBigInteger()) + .mod(modulus.toUnsignedBigInteger())); + } + + public UInt256 multiplyMod(long value, UInt256 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (value == 0 || isZero()) { + return ZERO; + } + if (value == 1) { + return mod(modulus); + } + if (value < 0) { + throw new ArithmeticException("multiplyMod unsigned by negative"); + } + return UInt256.valueOf( + toUnsignedBigInteger() + .multiply(BigInteger.valueOf(value)) + .mod(modulus.toUnsignedBigInteger())); + } + + public UInt256 multiplyMod(long value, long modulus) { + if (modulus == 0) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (modulus < 0) { + throw new ArithmeticException("multiplyMod unsigned with negative modulus"); + } + if (value == 0 || isZero()) { + return ZERO; + } + if (value == 1) { + return mod(modulus); + } + if (value < 0) { + throw new ArithmeticException("multiplyMod unsigned by negative"); + } + return UInt256.valueOf( + toUnsignedBigInteger() + .multiply(BigInteger.valueOf(value)) + .mod(BigInteger.valueOf(modulus))); + } + + public UInt256 divide(UInt256 value) { + if (value.isZero()) { + throw new ArithmeticException("divide by zero"); + } + if (value.equals(UInt256.ONE)) { + return this; + } + return UInt256.valueOf(toUnsignedBigInteger().divide(value.toUnsignedBigInteger())); + } + + public UInt256 divide(long value) { + if (value == 0) { + throw new ArithmeticException("divide by zero"); + } + if (value < 0) { + throw new ArithmeticException("divide unsigned by negative"); + } + if (value == 1) { + return this; + } + if (isPowerOf2(value)) { + return shiftRight(log2(value)); + } + return UInt256.valueOf(toUnsignedBigInteger().divide(BigInteger.valueOf(value))); + } + + public UInt256 sdiv0(UInt256 divisor) { + if (divisor.isZero()) { + return UInt256.ZERO; + } else { + BigInteger result = this.toSignedBigInteger().divide(divisor.toSignedBigInteger()); + Bytes resultBytes = Bytes.wrap(result.toByteArray()); + if (resultBytes.size() > 32) { + resultBytes = resultBytes.slice(resultBytes.size() - 32, 32); + } + MutableBytes mutableBytes = resultBytes.mutableCopy().leftPad(32); + if (result.signum() < 0) { + mutableBytes.or(MAX_VALUE.shiftLeft(resultBytes.size() * 8)); + } + return UInt256.fromBytes(mutableBytes); + } + } + + public UInt256 divideCeil(UInt256 value) { + return this.divide(value).add(this.mod(value).isZero() ? 0 : 1); + } + + public UInt256 divideCeil(long value) { + return this.divide(value).add(this.mod(value).isZero() ? 0 : 1); + } + + public UInt256 pow(UInt256 exponent) { + return UInt256.valueOf(toUnsignedBigInteger().modPow(exponent.toUnsignedBigInteger(), P_2_256)); + } + + public UInt256 pow(long exponent) { + return UInt256.valueOf(toUnsignedBigInteger().modPow(BigInteger.valueOf(exponent), P_2_256)); + } + + public UInt256 mod(UInt256 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("mod by zero"); + } + return UInt256.valueOf(toUnsignedBigInteger().mod(modulus.toUnsignedBigInteger())); + } + + public UInt256 mod(long modulus) { + if (modulus == 0) { + throw new ArithmeticException("mod by zero"); + } + if (modulus < 0) { + throw new ArithmeticException("mod by negative"); + } + if (isPowerOf2(modulus)) { + int log2 = log2(modulus); + int d = log2 / 32; + int s = log2 % 32; + assert (d == 0 || d == 1); + + int[] result = new int[INTS_SIZE]; + // Mask the byte at d to only include the s right-most bits + result[INTS_SIZE - 1 - d] = this.ints[INTS_SIZE - 1 - d] & ~(0xFFFFFFFF << s); + if (d != 0) { + result[INTS_SIZE - 1] = this.ints[INTS_SIZE - 1]; + } + return new UInt256(result); + } + return UInt256.valueOf(toUnsignedBigInteger().mod(BigInteger.valueOf(modulus))); + } + + public UInt256 mod0(UInt256 modulus) { + if (modulus.equals(UInt256.ZERO)) { + return UInt256.ZERO; + } + return mod(modulus); + } + + /** + * Returns a value that is the {@code (this signed mod modulus)}, or 0 if modulus is 0. + * + * @param modulus The modulus. + * @return {@code this signed mod modulus}. + */ + public UInt256 smod0(UInt256 modulus) { + if (modulus.equals(UInt256.ZERO)) { + return UInt256.ZERO; + } + + BigInteger bi = this.toSignedBigInteger(); + BigInteger result = bi.abs().mod(modulus.toSignedBigInteger().abs()); + + if (bi.signum() < 0) { + result = result.negate(); + } + + Bytes resultBytes = Bytes.wrap(result.toByteArray()); + if (resultBytes.size() > 32) { + resultBytes = resultBytes.slice(resultBytes.size() - 32, 32); + } + + MutableBytes mutableBytes = resultBytes.mutableCopy().leftPad(32); + if (result.signum() < 0) { + mutableBytes.or(MAX_VALUE.shiftLeft(resultBytes.size() * 8)); + } + return UInt256.fromBytes(mutableBytes); + } + + public UInt256 mod0(long modulus) { + if (modulus == 0) { + return UInt256.ZERO; + } + if (modulus < 0) { + throw new ArithmeticException("mod by negative"); + } + return mod(modulus); + } + + /** + * Return a bit-wise AND of this value and the supplied value. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise AND + */ + public UInt256 and(UInt256 value) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + result[i] = this.ints[i] & value.ints[i]; + } + return new UInt256(result); + } + + /** + * Return a bit-wise AND of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise AND + */ + public UInt256 and(Bytes32 bytes) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1, j = 28; i >= 0; --i, j -= 4) { + int other = ((int) bytes.get(j) & 0xFF) << 24; + other |= ((int) bytes.get(j + 1) & 0xFF) << 16; + other |= ((int) bytes.get(j + 2) & 0xFF) << 8; + other |= ((int) bytes.get(j + 3) & 0xFF); + result[i] = this.ints[i] & other; + } + return new UInt256(result); + } + + /** + * Return a bit-wise OR of this value and the supplied value. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise OR + */ + public UInt256 or(UInt256 value) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + result[i] = this.ints[i] | value.ints[i]; + } + return new UInt256(result); + } + + /** + * Return a bit-wise OR of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise OR + */ + public UInt256 or(Bytes32 bytes) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1, j = 28; i >= 0; --i, j -= 4) { + int other = ((int) bytes.get(j) & 0xFF) << 24; + other |= ((int) bytes.get(j + 1) & 0xFF) << 16; + other |= ((int) bytes.get(j + 2) & 0xFF) << 8; + other |= ((int) bytes.get(j + 3) & 0xFF); + result[i] = this.ints[i] | other; + } + return new UInt256(result); + } + + /** + * Return a bit-wise XOR of this value and the supplied value. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise XOR + */ + public UInt256 xor(UInt256 value) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + result[i] = this.ints[i] ^ value.ints[i]; + } + return new UInt256(result); + } + + /** + * Return a bit-wise XOR of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise XOR + */ + public UInt256 xor(Bytes32 bytes) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1, j = 28; i >= 0; --i, j -= 4) { + int other = ((int) bytes.get(j) & 0xFF) << 24; + other |= ((int) bytes.get(j + 1) & 0xFF) << 16; + other |= ((int) bytes.get(j + 2) & 0xFF) << 8; + other |= ((int) bytes.get(j + 3) & 0xFF); + result[i] = this.ints[i] ^ other; + } + return new UInt256(result); + } + + /** + * Return a bit-wise NOT of this value. + * + * @return the result of a bit-wise NOT + */ + public UInt256 not() { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + result[i] = ~(this.ints[i]); + } + return new UInt256(result); + } + + /** + * Shift all bits in this value to the right. + * + * @param distance The number of bits to shift by. + * @return A value containing the shifted bits. + */ + public UInt256 shiftRight(int distance) { + if (distance == 0) { + return this; + } + if (distance >= 256) { + return ZERO; + } + int[] result = new int[INTS_SIZE]; + int d = distance / 32; + int s = distance % 32; + + int resIdx = INTS_SIZE; + if (s == 0) { + for (int i = INTS_SIZE - d; i > 0; ) { + result[--resIdx] = this.ints[--i]; + } + } else { + for (int i = INTS_SIZE - 1 - d; i >= 0; i--) { + int leftSide = this.ints[i] >>> s; + int rightSide = (i == 0) ? 0 : this.ints[i - 1] << (32 - s); + result[--resIdx] = (leftSide | rightSide); + } + } + return new UInt256(result); + } + + /** + * Shift all bits in this value to the left. + * + * @param distance The number of bits to shift by. + * @return A value containing the shifted bits. + */ + public UInt256 shiftLeft(int distance) { + if (distance == 0) { + return this; + } + if (distance >= 256) { + return ZERO; + } + int[] result = new int[INTS_SIZE]; + int d = distance / 32; + int s = distance % 32; + + int resIdx = 0; + if (s == 0) { + for (int i = d; i < INTS_SIZE; ) { + result[resIdx++] = this.ints[i++]; + } + } else { + for (int i = d; i < INTS_SIZE; ++i) { + int leftSide = this.ints[i] << s; + int rightSide = (i == INTS_SIZE - 1) ? 0 : (this.ints[i + 1] >>> (32 - s)); + result[resIdx++] = (leftSide | rightSide); + } + } + return new UInt256(result); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof UInt256) { + UInt256 other = (UInt256) object; + for (int i = 0; i < INTS_SIZE; ++i) { + if (this.ints[i] != other.ints[i]) { + return false; + } + } + return true; + } + if (object instanceof Bytes) { + Bytes other = (Bytes) object; + if (this.size() != other.size()) { + return false; + } + + for (int i = 0; i < size(); i++) { + if (this.get(i) != other.get(i)) { + return false; + } + } + + return true; + } + return false; + } + + @Override + protected int computeHashcode() { + int result = 1; + for (int i = 0; i < size(); i++) { + result = 31 * result + Utils.unpackByte(ints, i); + } + return result; + } + + public boolean fitsInt() { + for (int i = 0; i < INTS_SIZE - 1; i++) { + if (this.ints[i] != 0) { + return false; + } + } + // Lastly, the left-most byte of the int must not start with a 1. + return this.ints[INTS_SIZE - 1] >= 0; + } + + public int intValue() { + if (!fitsInt()) { + throw new ArithmeticException("Value does not fit a 4 byte int"); + } + return this.ints[INTS_SIZE - 1]; + } + + public boolean fitsLong() { + for (int i = 0; i < INTS_SIZE - 2; i++) { + if (this.ints[i] != 0) { + return false; + } + } + // Lastly, the left-most byte of the int must not start with a 1. + return this.ints[INTS_SIZE - 2] >= 0; + } + + @Override + public byte get(int i) { + checkElementIndex(i, size()); + return Utils.unpackByte(ints, i); + } + + @Override + public long toLong() { + if (!fitsLong()) { + throw new ArithmeticException("Value does not fit a 8 byte long"); + } + return (((long) this.ints[INTS_SIZE - 2]) << 32) + | (((long) (this.ints[INTS_SIZE - 1])) & LONG_MASK); + } + + @Override + public String toString() { + return toHexString(); + } + + @Override + protected void and(byte[] bytesArray, int offset, int length) { + Utils.and(ints, 0, bytesArray, offset, length); + } + + @Override + protected void or(byte[] bytesArray, int offset, int length) { + Utils.or(ints, 0, bytesArray, offset, length); + } + + @Override + protected void xor(byte[] bytesArray, int offset, int length) { + Utils.xor(ints, 0, bytesArray, offset, length); + } + + public UInt256 toUInt256() { + return this; + } + + @Override + public Bytes slice(int i, int length) { + return mutableCopy().slice(i, length); + } + + @Override + public MutableBytes mutableCopy() { + return MutableBytes.fromArray(toArrayUnsafe()); + } + + @Override + public byte[] toArrayUnsafe() { + return new byte[] { + (byte) (ints[0] >> 24), + (byte) (ints[0] >> 16), + (byte) (ints[0] >> 8), + (byte) (ints[0]), + (byte) (ints[1] >> 24), + (byte) (ints[1] >> 16), + (byte) (ints[1] >> 8), + (byte) (ints[1]), + (byte) (ints[2] >> 24), + (byte) (ints[2] >> 16), + (byte) (ints[2] >> 8), + (byte) (ints[2]), + (byte) (ints[3] >> 24), + (byte) (ints[3] >> 16), + (byte) (ints[3] >> 8), + (byte) (ints[3]), + (byte) (ints[4] >> 24), + (byte) (ints[4] >> 16), + (byte) (ints[4] >> 8), + (byte) (ints[4]), + (byte) (ints[5] >> 24), + (byte) (ints[5] >> 16), + (byte) (ints[5] >> 8), + (byte) (ints[5]), + (byte) (ints[6] >> 24), + (byte) (ints[6] >> 16), + (byte) (ints[6] >> 8), + (byte) (ints[6]), + (byte) (ints[7] >> 24), + (byte) (ints[7] >> 16), + (byte) (ints[7] >> 8), + (byte) (ints[7]) + }; + } + + public Bytes toBytes() { + return Bytes.wrap(toArrayUnsafe()); + } + + public Bytes toMinimalBytes() { + int i = 0; + while (i < INTS_SIZE && this.ints[i] == 0) { + ++i; + } + if (i == INTS_SIZE) { + return Bytes.EMPTY; + } + int firstIntBytes = 4 - (Integer.numberOfLeadingZeros(this.ints[i]) / 8); + int totalBytes = firstIntBytes + ((INTS_SIZE - (i + 1)) * 4); + MutableBytes bytes = MutableBytes.create(totalBytes); + int j = 0; + switch (firstIntBytes) { + case 4: + bytes.set(j++, (byte) (this.ints[i] >>> 24)); + // fall through + case 3: + bytes.set(j++, (byte) ((this.ints[i] >>> 16) & 0xFF)); + // fall through + case 2: + bytes.set(j++, (byte) ((this.ints[i] >>> 8) & 0xFF)); + // fall through + case 1: + bytes.set(j++, (byte) (this.ints[i] & 0xFF)); + } + ++i; + for (; i < INTS_SIZE; ++i, j += 4) { + bytes.setInt(j, this.ints[i]); + } + return bytes; + } + + @Override + public int numberOfLeadingZeros() { + for (int i = 0; i < INTS_SIZE; i++) { + if (this.ints[i] == 0) { + continue; + } + return (i * 32) + Integer.numberOfLeadingZeros(this.ints[i]); + } + return 256; + } + + @Override + public int bitLength() { + for (int i = 0; i < INTS_SIZE; i++) { + if (this.ints[i] == 0) { + continue; + } + return (INTS_SIZE * 32) - (i * 32) - Integer.numberOfLeadingZeros(this.ints[i]); + } + return 0; + } + + public UInt256 max() { + return UInt256.MAX_VALUE; + } + + private static boolean isPowerOf2(long n) { + assert n > 0; + return (n & (n - 1)) == 0; + } + + private static int log2(long v) { + assert v > 0; + return 63 - Long.numberOfLeadingZeros(v); + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value the amount to be added to this value + * @return {@code this + value} + * @throws ArithmeticException if the result of the addition overflows + */ + public UInt256 addExact(UInt256 value) { + UInt256 result = add(value); + if (compareTo(result) > 0) { + throw new ArithmeticException("UInt256 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value the amount to be added to this value + * @return {@code this + value} + * @throws ArithmeticException if the result of the addition overflows + */ + public UInt256 addExact(long value) { + UInt256 result = add(value); + if ((value > 0 && compareTo(result) > 0) || (value < 0 && compareTo(result) < 0)) { + throw new ArithmeticException("UInt256 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value the amount to be subtracted to this value + * @return {@code this - value} + * @throws ArithmeticException if the result of the addition overflows + */ + public UInt256 subtractExact(UInt256 value) { + UInt256 result = subtract(value); + if (compareTo(result) < 0) { + throw new ArithmeticException("UInt256 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value the amount to be subtracted to this value + * @return {@code this - value} + * @throws ArithmeticException if the result of the addition overflows + */ + public UInt256 subtractExact(long value) { + UInt256 result = subtract(value); + if ((value > 0 && compareTo(result) < 0) || (value < 0 && compareTo(result) > 0)) { + throw new ArithmeticException("UInt256 overflow"); + } + return result; + } + + /** + * Returns the decimal representation of this value as a String. + * + * @return the decimal representation of this value as a String. + */ + public String toDecimalString() { + return toBigInteger().toString(10); + } +} diff --git a/units/src/main/java/org/apache/tuweni/v2/units/bigints/UInt32.java b/units/src/main/java/org/apache/tuweni/v2/units/bigints/UInt32.java new file mode 100644 index 000000000..0f94334e5 --- /dev/null +++ b/units/src/main/java/org/apache/tuweni/v2/units/bigints/UInt32.java @@ -0,0 +1,483 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.units.bigints; + +import static org.apache.tuweni.v2.bytes.Utils.checkElementIndex; + +import org.apache.tuweni.v2.bytes.Bytes; +import org.apache.tuweni.v2.bytes.MutableBytes; + +import java.math.BigInteger; +import java.nio.ByteOrder; + +/** + * An unsigned 32-bit precision number. + * + *

This is a raw 32-bit precision unsigned number of no particular unit. + */ +public final class UInt32 extends Bytes { + private static final int MAX_CONSTANT = 0xff; + private static UInt32[] CONSTANTS = new UInt32[MAX_CONSTANT + 1]; + + static { + CONSTANTS[0] = new UInt32(0); + for (int i = 1; i <= MAX_CONSTANT; ++i) { + CONSTANTS[i] = new UInt32(i); + } + } + + /** The minimum value of a UInt32 */ + public static final UInt32 MIN_VALUE = valueOf(0); + + /** The maximum value of a UInt32 */ + public static final UInt32 MAX_VALUE = create(~0); + + /** The value 0 */ + public static final UInt32 ZERO = valueOf(0); + + /** The value 1 */ + public static final UInt32 ONE = valueOf(1); + + private static final BigInteger P_2_32 = BigInteger.valueOf(2).pow(32); + + private final int value; + + /** + * Return a {@code UInt32} containing the specified value. + * + * @param value The value to create a {@code UInt32} for. + * @return A {@code UInt32} containing the specified value. + * @throws IllegalArgumentException If the value is negative. + */ + public static UInt32 valueOf(int value) { + if (value < 0) { + throw new IllegalArgumentException("Argument must be positive"); + } + return create(value); + } + + /** + * Return a {@link UInt32} containing the specified value. + * + * @param value the value to create a {@link UInt32} for + * @return a {@link UInt32} containing the specified value + * @throws IllegalArgumentException if the value is negative or too large to be represented as a + * UInt32 + */ + public static UInt32 valueOf(BigInteger value) { + if (value.bitLength() > 32) { + throw new IllegalArgumentException("Argument is too large to represent a UInt32"); + } + if (value.signum() < 0) { + throw new IllegalArgumentException("Argument must be positive"); + } + return create(value.intValue()); + } + + /** + * Return a {@link UInt32} containing the value described by the specified bytes. + * + * @param bytes The bytes containing a {@link UInt32}. \ * @return A {@link UInt32} containing the + * specified value. + * @throws IllegalArgumentException if {@code bytes.size() > 4}. + */ + public static UInt32 fromBytes(Bytes bytes) { + return fromBytes(bytes, ByteOrder.BIG_ENDIAN); + } + + /** + * Return a {@link UInt32} containing the value described by the specified bytes. + * + * @param bytes The bytes containing a {@link UInt32}. + * @param byteOrder the byte order of the value + * @return A {@link UInt32} containing the specified value. + * @throws IllegalArgumentException if {@code bytes.size() > 4}. + */ + public static UInt32 fromBytes(Bytes bytes, ByteOrder byteOrder) { + if (bytes.size() > 4) { + throw new IllegalArgumentException("Argument is greater than 4 bytes"); + } + return create(byteOrder == ByteOrder.LITTLE_ENDIAN ? bytes.mutableCopy().reverse() : bytes); + } + + /** + * Parse a hexadecimal string into a {@link UInt32}. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". That + * representation may contain less than 8 bytes, in which case the result is left padded with + * zeros. + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal + * representation or contains more than 8 bytes. + */ + public static UInt32 fromHexString(String str) { + return fromBytes(Bytes.fromHexStringLenient(str)); + } + + private static UInt32 create(Bytes value) { + return create(value.toInt()); + } + + private static UInt32 create(int value) { + if (value >= 0 && value <= MAX_CONSTANT) { + return CONSTANTS[value]; + } + return new UInt32(value); + } + + private UInt32(int value) { + super(4); + this.value = value; + } + + @Override + public boolean isZero() { + return ZERO.equals(this); + } + + public UInt32 add(UInt32 value) { + if (value.isZero()) { + return this; + } + return create(this.value + value.value); + } + + public UInt32 add(int value) { + if (value == 0) { + return this; + } + return create(this.value + value); + } + + public UInt32 addMod(UInt32 value, UInt32 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("addMod with zero modulus"); + } + return create(toBigInteger().add(value.toBigInteger()).mod(modulus.toBigInteger()).intValue()); + } + + public UInt32 addMod(long value, UInt32 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("addMod with zero modulus"); + } + return create( + toBigInteger().add(BigInteger.valueOf(value)).mod(modulus.toBigInteger()).intValue()); + } + + public UInt32 addMod(long value, long modulus) { + if (modulus == 0) { + throw new ArithmeticException("addMod with zero modulus"); + } + if (modulus < 0) { + throw new ArithmeticException("addMod unsigned with negative modulus"); + } + return create( + toBigInteger().add(BigInteger.valueOf(value)).mod(BigInteger.valueOf(modulus)).intValue()); + } + + public UInt32 subtract(UInt32 value) { + if (value.isZero()) { + return this; + } + + return create(this.value - value.value); + } + + public UInt32 subtract(int value) { + if (value == 0) { + return this; + } + return create(this.value - value); + } + + public UInt32 multiply(UInt32 value) { + return create(this.value * value.value); + } + + public UInt32 multiply(int value) { + if (value < 0) { + throw new ArithmeticException("multiply unsigned by negative"); + } + if (value == 0 || isZero()) { + return ZERO; + } + return multiply(UInt32.valueOf(value)); + } + + public UInt32 multiplyMod(UInt32 value, UInt32 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (isZero() || value.isZero()) { + return ZERO; + } + if (ONE.equals(value)) { + return mod(modulus); + } + return create( + toBigInteger().multiply(value.toBigInteger()).mod(modulus.toBigInteger()).intValue()); + } + + public UInt32 multiplyMod(int value, UInt32 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (value == 0 || this.isZero()) { + return ZERO; + } + if (value == 1) { + return mod(modulus); + } + if (value < 0) { + throw new ArithmeticException("multiplyMod unsigned by negative"); + } + return create( + toBigInteger().multiply(BigInteger.valueOf(value)).mod(modulus.toBigInteger()).intValue()); + } + + public UInt32 multiplyMod(int value, int modulus) { + if (modulus == 0) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (modulus < 0) { + throw new ArithmeticException("multiplyMod unsigned with negative modulus"); + } + if (value == 0 || this.isZero()) { + return ZERO; + } + if (value == 1) { + return mod(modulus); + } + if (value < 0) { + throw new ArithmeticException("multiplyMod unsigned by negative"); + } + return create( + toBigInteger() + .multiply(BigInteger.valueOf(value)) + .mod(BigInteger.valueOf(modulus)) + .intValue()); + } + + public UInt32 divide(UInt32 value) { + if (value.isZero()) { + throw new ArithmeticException("divide by zero"); + } + + if (value.equals(ONE)) { + return this; + } + return create(toBigInteger().divide(value.toBigInteger()).intValue()); + } + + public UInt32 divide(int value) { + if (value == 0) { + throw new ArithmeticException("divide by zero"); + } + if (value < 0) { + throw new ArithmeticException("divide unsigned by negative"); + } + if (value == 1) { + return this; + } + if (isPowerOf2(value)) { + return fromBytes(mutableCopy().shiftRight(log2(value))); + } + return create(toBigInteger().divide(BigInteger.valueOf(value)).intValue()); + } + + public UInt32 pow(UInt32 exponent) { + return create(toBigInteger().modPow(exponent.toBigInteger(), P_2_32).intValue()); + } + + public UInt32 pow(long exponent) { + return create(toBigInteger().modPow(BigInteger.valueOf(exponent), P_2_32).intValue()); + } + + public UInt32 mod(UInt32 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("mod by zero"); + } + return create(Integer.remainderUnsigned(this.value, modulus.value)); + } + + public UInt32 mod(int modulus) { + if (modulus == 0) { + throw new ArithmeticException("mod by zero"); + } + if (modulus < 0) { + throw new ArithmeticException("mod by negative"); + } + return create(Integer.remainderUnsigned(this.value, modulus)); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (!(object instanceof UInt32 other)) { + return false; + } + return this.value == other.value; + } + + @Override + public int computeHashcode() { + return Integer.hashCode(this.value); + } + + public int compareTo(UInt32 other) { + return Integer.compareUnsigned(this.value, other.value); + } + + @Override + protected void and(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + bytesArray[offset + i] = (byte) (get(i) & bytesArray[offset + i]); + } + } + + @Override + protected void or(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + bytesArray[offset + i] = (byte) (get(i) | bytesArray[offset + i]); + } + } + + @Override + protected void xor(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + bytesArray[offset + i] = (byte) (get(i) ^ bytesArray[offset + i]); + } + } + + @Override + public byte get(int i) { + checkElementIndex(i, size()); + return Utils.unpackByte(value, i); + } + + @Override + public BigInteger toBigInteger() { + byte[] mag = new byte[4]; + mag[0] = (byte) (this.value >>> 24); + mag[1] = (byte) (this.value >>> 16); + mag[2] = (byte) (this.value >>> 8); + mag[3] = (byte) this.value; + return new BigInteger(1, mag); + } + + public UInt32 toUInt32() { + return this; + } + + public Bytes toBytes() { + return Bytes.wrap(toArrayUnsafe()); + } + + public Bytes toMinimalBytes() { + int numberOfLeadingZeroBytes = Integer.numberOfLeadingZeros(this.value) / 8; + return slice(numberOfLeadingZeroBytes); + } + + @Override + public int bitLength() { + return 32 - numberOfLeadingZeros(); + } + + @Override + public Bytes slice(int i, int length) { + return toBytes().slice(i, length); + } + + @Override + public MutableBytes mutableCopy() { + return MutableBytes.fromArray(toArrayUnsafe()); + } + + @Override + public byte[] toArrayUnsafe() { + return new byte[] { + (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value + }; + } + + private static boolean isPowerOf2(long n) { + assert n > 0; + return (n & (n - 1)) == 0; + } + + private static int log2(int v) { + assert v > 0; + return 31 - Integer.numberOfLeadingZeros(v); + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value the amount to be added to this value + * @return {@code this + value} + * @throws ArithmeticException if the result of the addition overflows + */ + UInt32 addExact(UInt32 value) { + UInt32 result = add(value); + if (compareTo(result) > 0) { + throw new ArithmeticException("UInt32 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value the amount to be added to this value + * @return {@code this + value} + * @throws ArithmeticException if the result of the addition overflows + */ + UInt32 addExact(int value) { + UInt32 result = add(value); + if ((value > 0 && compareTo(result) > 0) || (value < 0 && compareTo(result) < 0)) { + throw new ArithmeticException("UInt32 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value the amount to be subtracted to this value + * @return {@code this - value} + * @throws ArithmeticException if the result of the subtraction overflows + */ + public UInt32 subtractExact(UInt32 value) { + UInt32 result = subtract(value); + if (compareTo(result) < 0) { + throw new ArithmeticException("UInt32 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value the amount to be subtracted to this value + * @return {@code this - value} + * @throws ArithmeticException if the result of the subtraction overflows + */ + public UInt32 subtractExact(int value) { + UInt32 result = subtract(value); + if ((value > 0 && compareTo(result) < 0) || (value < 0 && compareTo(result) > 0)) { + throw new ArithmeticException("UInt32 overflow"); + } + return result; + } + + /** + * Returns the decimal representation of this value as a String. + * + * @return the decimal representation of this value as a String. + */ + public String toDecimalString() { + return toBigInteger().toString(10); + } +} diff --git a/units/src/main/java/org/apache/tuweni/v2/units/bigints/UInt384.java b/units/src/main/java/org/apache/tuweni/v2/units/bigints/UInt384.java new file mode 100644 index 000000000..3b02eedd8 --- /dev/null +++ b/units/src/main/java/org/apache/tuweni/v2/units/bigints/UInt384.java @@ -0,0 +1,877 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.units.bigints; + +import static org.apache.tuweni.v2.bytes.Utils.checkElementIndex; + +import org.apache.tuweni.v2.bytes.Bytes; +import org.apache.tuweni.v2.bytes.Bytes48; +import org.apache.tuweni.v2.bytes.MutableBytes; + +import java.math.BigInteger; +import java.util.Arrays; + +/** + * An unsigned 384-bit precision number. + * + *

This is a 384-bit precision unsigned number of no particular unit. + */ +public final class UInt384 extends Bytes { + private static final int MAX_CONSTANT = 64; + private static final BigInteger BI_MAX_CONSTANT = BigInteger.valueOf(MAX_CONSTANT); + private static UInt384[] CONSTANTS = new UInt384[MAX_CONSTANT + 1]; + + static { + CONSTANTS[0] = new UInt384(Bytes48.ZERO); + for (int i = 1; i <= MAX_CONSTANT; ++i) { + CONSTANTS[i] = new UInt384(i); + } + } + + /** The minimum value of a UInt384 */ + public static final UInt384 MIN_VALUE = valueOf(0); + + /** The maximum value of a UInt384 */ + public static final UInt384 MAX_VALUE = new UInt384(Bytes48.ZERO.mutableCopy().not()); + + /** The value 0 */ + public static final UInt384 ZERO = valueOf(0); + + /** The value 1 */ + public static final UInt384 ONE = valueOf(1); + + private static final int INTS_SIZE = 48 / 4; + // The mask is used to obtain the value of an int as if it were unsigned. + private static final long LONG_MASK = 0xFFFFFFFFL; + private static final BigInteger P_2_384 = BigInteger.valueOf(2).pow(384); + + // The unsigned int components of the value + private final int[] ints; + + /** + * Return a {@code UInt384} containing the specified value. + * + * @param value The value to create a {@code UInt384} for. + * @return A {@code UInt384} containing the specified value. + * @throws IllegalArgumentException If the value is negative. + */ + public static UInt384 valueOf(long value) { + if (value < 0) { + throw new IllegalArgumentException("Argument must be positive"); + } + if (value <= MAX_CONSTANT) { + return CONSTANTS[(int) value]; + } + return new UInt384(value); + } + + /** + * Return a {@link UInt384} containing the specified value. + * + * @param value the value to create a {@link UInt384} for + * @return a {@link UInt384} containing the specified value + * @throws IllegalArgumentException if the value is negative or too large to be represented as a + * UInt384 + */ + public static UInt384 valueOf(BigInteger value) { + if (value.signum() < 0) { + throw new IllegalArgumentException("Argument must be positive"); + } + if (value.bitLength() > 384) { + throw new IllegalArgumentException("Argument is too large to represent a UInt384"); + } + if (value.compareTo(BI_MAX_CONSTANT) <= 0) { + return CONSTANTS[value.intValue()]; + } + int[] ints = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + ints[i] = value.intValue(); + value = value.shiftRight(32); + } + return new UInt384(ints); + } + + /** + * Return a {@link UInt384} containing the value described by the specified bytes. + * + * @param bytes The bytes containing a {@link UInt384}. + * @return A {@link UInt384} containing the specified value. + * @throws IllegalArgumentException if {@code bytes.size() > 48}. + */ + public static UInt384 fromBytes(Bytes bytes) { + return new UInt384(bytes.mutableCopy().leftPad(48)); + } + + /** + * Parse a hexadecimal string into a {@link UInt384}. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". That + * representation may contain less than 48 bytes, in which case the result is left padded with + * zeros. + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal + * representation or contains more than 48 bytes. + */ + public static UInt384 fromHexString(String str) { + return new UInt384(Bytes48.fromHexStringLenient(str)); + } + + private UInt384(Bytes bytes) { + super(48); + this.ints = new int[INTS_SIZE]; + for (int i = 0, j = 0; i < INTS_SIZE; ++i, j += 4) { + ints[i] = bytes.getInt(j); + } + } + + private UInt384(long value) { + super(48); + this.ints = new int[INTS_SIZE]; + this.ints[INTS_SIZE - 2] = (int) ((value >>> 32) & LONG_MASK); + this.ints[INTS_SIZE - 1] = (int) (value & LONG_MASK); + } + + private UInt384(int[] ints) { + super(48); + this.ints = ints; + } + + @SuppressWarnings("ReferenceEquality") + @Override + public boolean isZero() { + if (this == ZERO) { + return true; + } + for (int i = INTS_SIZE - 1; i >= 0; --i) { + if (this.ints[i] != 0) { + return false; + } + } + return true; + } + + public UInt384 add(UInt384 value) { + if (value.isZero()) { + return this; + } + if (isZero()) { + return value; + } + int[] result = new int[INTS_SIZE]; + boolean constant = true; + long sum = (this.ints[INTS_SIZE - 1] & LONG_MASK) + (value.ints[INTS_SIZE - 1] & LONG_MASK); + result[INTS_SIZE - 1] = (int) (sum & LONG_MASK); + if (result[INTS_SIZE - 1] < 0 || result[INTS_SIZE - 1] > MAX_CONSTANT) { + constant = false; + } + for (int i = INTS_SIZE - 2; i >= 0; --i) { + sum = (this.ints[i] & LONG_MASK) + (value.ints[i] & LONG_MASK) + (sum >>> 32); + result[i] = (int) (sum & LONG_MASK); + constant &= result[i] == 0; + } + if (constant) { + return CONSTANTS[result[INTS_SIZE - 1]]; + } + return new UInt384(result); + } + + public UInt384 add(long value) { + if (value == 0) { + return this; + } + if (value > 0 && isZero()) { + return UInt384.valueOf(value); + } + int[] result = new int[INTS_SIZE]; + boolean constant = true; + long sum = (this.ints[INTS_SIZE - 1] & LONG_MASK) + (value & LONG_MASK); + result[INTS_SIZE - 1] = (int) (sum & LONG_MASK); + if (result[INTS_SIZE - 1] < 0 || result[INTS_SIZE - 1] > MAX_CONSTANT) { + constant = false; + } + sum = (this.ints[INTS_SIZE - 2] & LONG_MASK) + (value >>> 32) + (sum >>> 32); + result[INTS_SIZE - 2] = (int) (sum & LONG_MASK); + constant &= result[INTS_SIZE - 2] == 0; + long signExtent = (value >> 63) & LONG_MASK; + for (int i = INTS_SIZE - 3; i >= 0; --i) { + sum = (this.ints[i] & LONG_MASK) + signExtent + (sum >>> 32); + result[i] = (int) (sum & LONG_MASK); + constant &= result[i] == 0; + } + if (constant) { + return CONSTANTS[result[INTS_SIZE - 1]]; + } + return new UInt384(result); + } + + public UInt384 addMod(UInt384 value, UInt384 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("addMod with zero modulus"); + } + return UInt384.valueOf(toBigInteger().add(value.toBigInteger()).mod(modulus.toBigInteger())); + } + + public UInt384 addMod(long value, UInt384 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("addMod with zero modulus"); + } + return UInt384.valueOf( + toBigInteger().add(BigInteger.valueOf(value)).mod(modulus.toBigInteger())); + } + + public UInt384 addMod(long value, long modulus) { + if (modulus == 0) { + throw new ArithmeticException("addMod with zero modulus"); + } + if (modulus < 0) { + throw new ArithmeticException("addMod unsigned with negative modulus"); + } + return UInt384.valueOf( + toBigInteger().add(BigInteger.valueOf(value)).mod(BigInteger.valueOf(modulus))); + } + + public UInt384 subtract(UInt384 value) { + if (value.isZero()) { + return this; + } + + int[] result = new int[INTS_SIZE]; + boolean constant = true; + long sum = + (this.ints[INTS_SIZE - 1] & LONG_MASK) + ((~value.ints[INTS_SIZE - 1]) & LONG_MASK) + 1; + result[INTS_SIZE - 1] = (int) (sum & LONG_MASK); + if (result[INTS_SIZE - 1] < 0 || result[INTS_SIZE - 1] > MAX_CONSTANT) { + constant = false; + } + for (int i = INTS_SIZE - 2; i >= 0; --i) { + sum = (this.ints[i] & LONG_MASK) + ((~value.ints[i]) & LONG_MASK) + (sum >>> 32); + result[i] = (int) (sum & LONG_MASK); + constant &= result[i] == 0; + } + if (constant) { + return CONSTANTS[result[INTS_SIZE - 1]]; + } + return new UInt384(result); + } + + public UInt384 subtract(long value) { + return add(-value); + } + + public UInt384 multiply(UInt384 value) { + if (isZero() || value.isZero()) { + return ZERO; + } + if (value.equals(UInt384.ONE)) { + return this; + } + return multiply(this.ints, value.ints); + } + + private static UInt384 multiply(int[] x, int[] y) { + int[] result = new int[INTS_SIZE + INTS_SIZE]; + + long carry = 0; + for (int j = INTS_SIZE - 1, k = INTS_SIZE + INTS_SIZE - 1; j >= 0; j--, k--) { + long product = (y[j] & LONG_MASK) * (x[INTS_SIZE - 1] & LONG_MASK) + carry; + result[k] = (int) product; + carry = product >>> 32; + } + result[INTS_SIZE - 1] = (int) carry; + + for (int i = INTS_SIZE - 2; i >= 0; i--) { + carry = 0; + for (int j = INTS_SIZE - 1, k = INTS_SIZE + i; j >= 0; j--, k--) { + long product = (y[j] & LONG_MASK) * (x[i] & LONG_MASK) + (result[k] & LONG_MASK) + carry; + + result[k] = (int) product; + carry = product >>> 32; + } + result[i] = (int) carry; + } + + boolean constant = true; + for (int i = INTS_SIZE; i < (INTS_SIZE + INTS_SIZE) - 2; ++i) { + constant &= (result[i] == 0); + } + if (constant + && result[INTS_SIZE + INTS_SIZE - 1] >= 0 + && result[INTS_SIZE + INTS_SIZE - 1] <= MAX_CONSTANT) { + return CONSTANTS[result[INTS_SIZE + INTS_SIZE - 1]]; + } + return new UInt384(Arrays.copyOfRange(result, INTS_SIZE, INTS_SIZE + INTS_SIZE)); + } + + public UInt384 multiply(long value) { + if (value == 0 || isZero()) { + return ZERO; + } + if (value == 1) { + return this; + } + if (value < 0) { + throw new ArithmeticException("multiply unsigned by negative"); + } + UInt384 other = new UInt384(value); + return multiply(this.ints, other.ints); + } + + public UInt384 multiplyMod(UInt384 value, UInt384 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (isZero() || value.isZero()) { + return ZERO; + } + if (value.equals(UInt384.ONE)) { + return mod(modulus); + } + return UInt384.valueOf( + toBigInteger().multiply(value.toBigInteger()).mod(modulus.toBigInteger())); + } + + public UInt384 multiplyMod(long value, UInt384 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (value == 0 || isZero()) { + return ZERO; + } + if (value == 1) { + return mod(modulus); + } + if (value < 0) { + throw new ArithmeticException("multiplyMod unsigned by negative"); + } + return UInt384.valueOf( + toBigInteger().multiply(BigInteger.valueOf(value)).mod(modulus.toBigInteger())); + } + + public UInt384 multiplyMod(long value, long modulus) { + if (modulus == 0) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (modulus < 0) { + throw new ArithmeticException("multiplyMod unsigned with negative modulus"); + } + if (value == 0 || isZero()) { + return ZERO; + } + if (value == 1) { + return mod(modulus); + } + if (value < 0) { + throw new ArithmeticException("multiplyMod unsigned by negative"); + } + return UInt384.valueOf( + toBigInteger().multiply(BigInteger.valueOf(value)).mod(BigInteger.valueOf(modulus))); + } + + public UInt384 divide(UInt384 value) { + if (value.isZero()) { + throw new ArithmeticException("divide by zero"); + } + if (value.equals(UInt384.ONE)) { + return this; + } + return UInt384.valueOf(toBigInteger().divide(value.toBigInteger())); + } + + public UInt384 divide(long value) { + if (value == 0) { + throw new ArithmeticException("divide by zero"); + } + if (value < 0) { + throw new ArithmeticException("divide unsigned by negative"); + } + if (value == 1) { + return this; + } + if (isPowerOf2(value)) { + return shiftRight(log2(value)); + } + return UInt384.valueOf(toBigInteger().divide(BigInteger.valueOf(value))); + } + + public UInt384 pow(UInt384 exponent) { + return UInt384.valueOf(toBigInteger().modPow(exponent.toBigInteger(), P_2_384)); + } + + public UInt384 pow(long exponent) { + return UInt384.valueOf(toBigInteger().modPow(BigInteger.valueOf(exponent), P_2_384)); + } + + public UInt384 mod(UInt384 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("mod by zero"); + } + return UInt384.valueOf(toBigInteger().mod(modulus.toBigInteger())); + } + + public UInt384 mod(long modulus) { + if (modulus == 0) { + throw new ArithmeticException("mod by zero"); + } + if (modulus < 0) { + throw new ArithmeticException("mod by negative"); + } + if (isPowerOf2(modulus)) { + int log2 = log2(modulus); + int d = log2 / 32; + int s = log2 % 32; + assert (d == 0 || d == 1); + + int[] result = new int[INTS_SIZE]; + // Mask the byte at d to only include the s right-most bits + result[INTS_SIZE - 1 - d] = this.ints[INTS_SIZE - 1 - d] & ~(0xFFFFFFFF << s); + if (d != 0) { + result[INTS_SIZE - 1] = this.ints[INTS_SIZE - 1]; + } + return new UInt384(result); + } + return UInt384.valueOf(toBigInteger().mod(BigInteger.valueOf(modulus))); + } + + /** + * Return a bit-wise AND of this value and the supplied value. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise AND + */ + public UInt384 and(UInt384 value) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + result[i] = this.ints[i] & value.ints[i]; + } + return new UInt384(result); + } + + /** + * Return a bit-wise AND of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise AND + */ + public UInt384 and(Bytes48 bytes) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1, j = 44; i >= 0; --i, j -= 4) { + int other = ((int) bytes.get(j) & 0xFF) << 24; + other |= ((int) bytes.get(j + 1) & 0xFF) << 16; + other |= ((int) bytes.get(j + 2) & 0xFF) << 8; + other |= ((int) bytes.get(j + 3) & 0xFF); + result[i] = this.ints[i] & other; + } + return new UInt384(result); + } + + /** + * Return a bit-wise OR of this value and the supplied value. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise OR + */ + public UInt384 or(UInt384 value) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + result[i] = this.ints[i] | value.ints[i]; + } + return new UInt384(result); + } + + /** + * Return a bit-wise OR of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise OR + */ + public UInt384 or(Bytes48 bytes) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1, j = 44; i >= 0; --i, j -= 4) { + int other = ((int) bytes.get(j) & 0xFF) << 24; + other |= ((int) bytes.get(j + 1) & 0xFF) << 16; + other |= ((int) bytes.get(j + 2) & 0xFF) << 8; + other |= ((int) bytes.get(j + 3) & 0xFF); + result[i] = this.ints[i] | other; + } + return new UInt384(result); + } + + /** + * Return a bit-wise XOR of this value and the supplied value. + * + * @param value the value to perform the operation with + * @return the result of a bit-wise XOR + */ + public UInt384 xor(UInt384 value) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + result[i] = this.ints[i] ^ value.ints[i]; + } + return new UInt384(result); + } + + /** + * Return a bit-wise XOR of this value and the supplied bytes. + * + * @param bytes the bytes to perform the operation with + * @return the result of a bit-wise XOR + */ + public UInt384 xor(Bytes48 bytes) { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1, j = 44; i >= 0; --i, j -= 4) { + int other = ((int) bytes.get(j) & 0xFF) << 24; + other |= ((int) bytes.get(j + 1) & 0xFF) << 16; + other |= ((int) bytes.get(j + 2) & 0xFF) << 8; + other |= ((int) bytes.get(j + 3) & 0xFF); + result[i] = this.ints[i] ^ other; + } + return new UInt384(result); + } + + /** + * Return a bit-wise NOT of this value. + * + * @return the result of a bit-wise NOT + */ + public UInt384 not() { + int[] result = new int[INTS_SIZE]; + for (int i = INTS_SIZE - 1; i >= 0; --i) { + result[i] = ~(this.ints[i]); + } + return new UInt384(result); + } + + /** + * Shift all bits in this value to the right. + * + * @param distance The number of bits to shift by. + * @return A value containing the shifted bits. + */ + public UInt384 shiftRight(int distance) { + if (distance == 0) { + return this; + } + if (distance >= 384) { + return ZERO; + } + int[] result = new int[INTS_SIZE]; + int d = distance / 32; + int s = distance % 32; + + int resIdx = INTS_SIZE; + if (s == 0) { + for (int i = INTS_SIZE - d; i > 0; ) { + result[--resIdx] = this.ints[--i]; + } + } else { + for (int i = INTS_SIZE - 1 - d; i >= 0; i--) { + int leftSide = this.ints[i] >>> s; + int rightSide = (i == 0) ? 0 : this.ints[i - 1] << (32 - s); + result[--resIdx] = (leftSide | rightSide); + } + } + return new UInt384(result); + } + + /** + * Shift all bits in this value to the left. + * + * @param distance The number of bits to shift by. + * @return A value containing the shifted bits. + */ + public UInt384 shiftLeft(int distance) { + if (distance == 0) { + return this; + } + if (distance >= 384) { + return ZERO; + } + int[] result = new int[INTS_SIZE]; + int d = distance / 32; + int s = distance % 32; + + int resIdx = 0; + if (s == 0) { + for (int i = d; i < INTS_SIZE; ) { + result[resIdx++] = this.ints[i++]; + } + } else { + for (int i = d; i < INTS_SIZE; ++i) { + int leftSide = this.ints[i] << s; + int rightSide = (i == INTS_SIZE - 1) ? 0 : (this.ints[i + 1] >>> (32 - s)); + result[resIdx++] = (leftSide | rightSide); + } + } + return new UInt384(result); + } + + @Override + public Bytes slice(int i, int length) { + return mutableCopy().slice(i, length); + } + + @Override + public MutableBytes mutableCopy() { + return MutableBytes.fromArray(toArrayUnsafe()); + } + + @Override + public byte[] toArrayUnsafe() { + byte[] byteArray = new byte[size()]; + int j = 0; + for (int i = 0; i < INTS_SIZE; i++) { + byteArray[j] = (byte) (ints[i] >> 24); + byteArray[j + 1] = (byte) (ints[i] >> 16); + byteArray[j + 2] = (byte) (ints[i] >> 8); + byteArray[j + 3] = (byte) ints[i]; + j += 4; + } + return byteArray; + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (!(object instanceof UInt384 other)) { + return false; + } + for (int i = 0; i < INTS_SIZE; ++i) { + if (this.ints[i] != other.ints[i]) { + return false; + } + } + return true; + } + + @Override + public int computeHashcode() { + int result = 1; + for (int i = 0; i < INTS_SIZE; ++i) { + result = 31 * result + this.ints[i]; + } + return result; + } + + public int compareTo(UInt384 other) { + for (int i = 0; i < INTS_SIZE; ++i) { + int cmp = Long.compare(((long) this.ints[i]) & LONG_MASK, ((long) other.ints[i]) & LONG_MASK); + if (cmp != 0) { + return cmp; + } + } + return 0; + } + + public boolean fitsInt() { + for (int i = 0; i < INTS_SIZE - 1; i++) { + if (this.ints[i] != 0) { + return false; + } + } + // Lastly, the left-most byte of the int must not start with a 1. + return this.ints[INTS_SIZE - 1] >= 0; + } + + public int intValue() { + if (!fitsInt()) { + throw new ArithmeticException("Value does not fit a 4 byte int"); + } + return this.ints[INTS_SIZE - 1]; + } + + public boolean fitsLong() { + for (int i = 0; i < INTS_SIZE - 2; i++) { + if (this.ints[i] != 0) { + return false; + } + } + // Lastly, the left-most byte of the int must not start with a 1. + return this.ints[INTS_SIZE - 2] >= 0; + } + + @Override + public byte get(int i) { + checkElementIndex(i, size()); + return Utils.unpackByte(ints, i); + } + + @Override + public long toLong() { + if (!fitsLong()) { + throw new ArithmeticException("Value does not fit a 8 byte long"); + } + return (((long) this.ints[INTS_SIZE - 2]) << 32) + | (((long) (this.ints[INTS_SIZE - 1])) & LONG_MASK); + } + + @Override + public String toString() { + return toBigInteger().toString(); + } + + @Override + protected void and(byte[] bytesArray, int offset, int length) { + Utils.and(ints, 0, bytesArray, offset, length); + } + + @Override + protected void or(byte[] bytesArray, int offset, int length) { + Utils.or(ints, 0, bytesArray, offset, length); + } + + @Override + protected void xor(byte[] bytesArray, int offset, int length) { + Utils.xor(ints, 0, bytesArray, offset, length); + } + + @Override + public BigInteger toBigInteger() { + byte[] mag = new byte[48]; + for (int i = 0, j = 0; i < INTS_SIZE; ++i) { + mag[j++] = (byte) (this.ints[i] >>> 24); + mag[j++] = (byte) ((this.ints[i] >>> 16) & 0xFF); + mag[j++] = (byte) ((this.ints[i] >>> 8) & 0xFF); + mag[j++] = (byte) (this.ints[i] & 0xFF); + } + return new BigInteger(1, mag); + } + + public UInt384 toUInt384() { + return this; + } + + public Bytes toMinimalBytes() { + int i = 0; + while (i < INTS_SIZE && this.ints[i] == 0) { + ++i; + } + if (i == INTS_SIZE) { + return Bytes.EMPTY; + } + int firstIntBytes = 4 - (Integer.numberOfLeadingZeros(this.ints[i]) / 8); + int totalBytes = firstIntBytes + ((INTS_SIZE - (i + 1)) * 4); + MutableBytes bytes = MutableBytes.create(totalBytes); + int j = 0; + switch (firstIntBytes) { + case 4: + bytes.set(j++, (byte) (this.ints[i] >>> 24)); + // fall through + case 3: + bytes.set(j++, (byte) ((this.ints[i] >>> 16) & 0xFF)); + // fall through + case 2: + bytes.set(j++, (byte) ((this.ints[i] >>> 8) & 0xFF)); + // fall through + case 1: + bytes.set(j++, (byte) (this.ints[i] & 0xFF)); + } + ++i; + for (; i < INTS_SIZE; ++i, j += 4) { + bytes.setInt(j, this.ints[i]); + } + return bytes; + } + + @Override + public int numberOfLeadingZeros() { + for (int i = 0; i < INTS_SIZE; i++) { + if (this.ints[i] == 0) { + continue; + } + return (i * 32) + Integer.numberOfLeadingZeros(this.ints[i]); + } + return 384; + } + + @Override + public int bitLength() { + for (int i = 0; i < INTS_SIZE; i++) { + if (this.ints[i] == 0) { + continue; + } + return (INTS_SIZE * 32) - (i * 32) - Integer.numberOfLeadingZeros(this.ints[i]); + } + return 0; + } + + private static boolean isPowerOf2(long n) { + assert n > 0; + return (n & (n - 1)) == 0; + } + + private static int log2(long v) { + assert v > 0; + return 63 - Long.numberOfLeadingZeros(v); + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value the amount to be added to this value + * @return {@code this + value} + * @throws ArithmeticException if the result of the addition overflows + */ + public UInt384 addExact(UInt384 value) { + UInt384 result = add(value); + if (compareTo(result) > 0) { + throw new ArithmeticException("UInt384 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value the amount to be added to this value + * @return {@code this + value} + * @throws ArithmeticException if the result of the addition overflows + */ + public UInt384 addExact(long value) { + UInt384 result = add(value); + if ((value > 0 && compareTo(result) > 0) || (value < 0 && compareTo(result) < 0)) { + throw new ArithmeticException("UInt384 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value the amount to be subtracted to this value + * @return {@code this - value} + * @throws ArithmeticException if the result of the addition overflows + */ + public UInt384 subtractExact(UInt384 value) { + UInt384 result = subtract(value); + if (compareTo(result) < 0) { + throw new ArithmeticException("UInt384 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value the amount to be subtracted to this value + * @return {@code this - value} + * @throws ArithmeticException if the result of the addition overflows + */ + public UInt384 subtractExact(long value) { + UInt384 result = subtract(value); + if ((value > 0 && compareTo(result) < 0) || (value < 0 && compareTo(result) > 0)) { + throw new ArithmeticException("UInt384 overflow"); + } + return result; + } + + /** + * Returns the decimal representation of this value as a String. + * + * @return the decimal representation of this value as a String. + */ + public String toDecimalString() { + return toBigInteger().toString(10); + } +} diff --git a/units/src/main/java/org/apache/tuweni/v2/units/bigints/UInt64.java b/units/src/main/java/org/apache/tuweni/v2/units/bigints/UInt64.java new file mode 100644 index 000000000..1a581ad3e --- /dev/null +++ b/units/src/main/java/org/apache/tuweni/v2/units/bigints/UInt64.java @@ -0,0 +1,541 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.units.bigints; + +import static org.apache.tuweni.v2.bytes.Utils.checkElementIndex; + +import org.apache.tuweni.v2.bytes.Bytes; +import org.apache.tuweni.v2.bytes.MutableBytes; + +import java.math.BigInteger; + +/** + * An unsigned 64-bit precision number. + * + *

This is a raw 64-bit precision unsigned number of no particular unit. + */ +public final class UInt64 extends Bytes { + private static final int MAX_CONSTANT = 64; + private static UInt64[] CONSTANTS = new UInt64[MAX_CONSTANT + 1]; + + static { + CONSTANTS[0] = new UInt64(0); + for (int i = 1; i <= MAX_CONSTANT; ++i) { + CONSTANTS[i] = new UInt64(i); + } + } + + /** The minimum value of a UInt64 */ + public static final UInt64 MIN_VALUE = valueOf(0); + + /** The maximum value of a UInt64 */ + public static final UInt64 MAX_VALUE = new UInt64(~0L); + + /** The value 0 */ + public static final UInt64 ZERO = valueOf(0); + + /** The value 1 */ + public static final UInt64 ONE = valueOf(1); + + private static final BigInteger P_2_64 = BigInteger.valueOf(2).pow(64); + + private final long value; + + /** + * Return a {@code UInt64} containing the specified value. + * + * @param value The value to create a {@code UInt64} for. + * @return A {@code UInt64} containing the specified value. + * @throws IllegalArgumentException If the value is negative. + */ + public static UInt64 valueOf(long value) { + if (value < 0) { + throw new IllegalArgumentException("Argument must be positive"); + } + return create(value); + } + + /** + * Return a {@link UInt64} containing a random value. + * + * @return a {@link UInt64} containing a random value + */ + public static UInt64 random() { + return UInt64.fromBytes(Bytes.random(8)); + } + + /** + * Return a {@link UInt64} containing the specified value. + * + * @param value the value to create a {@link UInt64} for + * @return a {@link UInt64} containing the specified value + * @throws IllegalArgumentException if the value is negative or too large to be represented as a + * UInt64 + */ + public static UInt64 valueOf(BigInteger value) { + if (value.signum() < 0) { + throw new IllegalArgumentException("Argument must be positive"); + } + if (value.bitLength() > 64) { + throw new IllegalArgumentException("Argument is too large to represent a UInt64"); + } + return create(value.longValue()); + } + + /** + * Return a {@link UInt64} containing the value described by the specified bytes. + * + * @param bytes The bytes containing a {@link UInt64}. + * @return A {@link UInt64} containing the specified value. + * @throws IllegalArgumentException if {@code bytes.size() > 8}. + */ + public static UInt64 fromBytes(Bytes bytes) { + if (bytes.size() > 8) { + throw new IllegalArgumentException("Argument is greater than 8 bytes"); + } + return create(bytes.toLong()); + } + + /** + * Parse a hexadecimal string into a {@link UInt64}. + * + * @param str The hexadecimal string to parse, which may or may not start with "0x". That + * representation may contain less than 8 bytes, in which case the result is left padded with + * zeros. + * @return The value corresponding to {@code str}. + * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal + * representation or contains more than 8 bytes. + */ + public static UInt64 fromHexString(String str) { + return fromBytes(Bytes.fromHexStringLenient(str)); + } + + private static UInt64 create(long value) { + if (value >= 0 && value <= MAX_CONSTANT) { + return CONSTANTS[(int) value]; + } + return new UInt64(value); + } + + private UInt64(long value) { + super(8); + this.value = value; + } + + @Override + public boolean isZero() { + return this.value == 0; + } + + public UInt64 add(UInt64 value) { + if (value.value == 0) { + return this; + } + if (this.value == 0) { + return value; + } + return create(this.value + value.value); + } + + public UInt64 add(long value) { + if (value == 0) { + return this; + } + return create(this.value + value); + } + + public UInt64 addMod(UInt64 value, UInt64 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("addMod with zero modulus"); + } + return create(toBigInteger().add(value.toBigInteger()).mod(modulus.toBigInteger()).longValue()); + } + + public UInt64 addMod(long value, UInt64 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("addMod with zero modulus"); + } + return create( + toBigInteger().add(BigInteger.valueOf(value)).mod(modulus.toBigInteger()).longValue()); + } + + public UInt64 addMod(long value, long modulus) { + if (modulus == 0) { + throw new ArithmeticException("addMod with zero modulus"); + } + if (modulus < 0) { + throw new ArithmeticException("addMod unsigned with negative modulus"); + } + return create( + toBigInteger().add(BigInteger.valueOf(value)).mod(BigInteger.valueOf(modulus)).longValue()); + } + + public UInt64 subtract(UInt64 value) { + if (value.isZero()) { + return this; + } + return create(this.value - value.value); + } + + public UInt64 subtract(long value) { + return add(-value); + } + + public UInt64 multiply(UInt64 value) { + if (this.value == 0 || value.value == 0) { + return ZERO; + } + if (value.value == 1) { + return this; + } + return create(this.value * value.value); + } + + public UInt64 multiply(long value) { + if (value < 0) { + throw new ArithmeticException("multiply unsigned by negative"); + } + if (value == 0 || this.value == 0) { + return ZERO; + } + if (value == 1) { + return this; + } + return create(this.value * value); + } + + public UInt64 multiplyMod(UInt64 value, UInt64 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (this.value == 0 || value.value == 0) { + return ZERO; + } + if (value.value == 1) { + return mod(modulus); + } + return create( + toBigInteger().multiply(value.toBigInteger()).mod(modulus.toBigInteger()).longValue()); + } + + public UInt64 multiplyMod(long value, UInt64 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (value == 0 || this.value == 0) { + return ZERO; + } + if (value == 1) { + return mod(modulus); + } + if (value < 0) { + throw new ArithmeticException("multiplyMod unsigned by negative"); + } + return create( + toBigInteger().multiply(BigInteger.valueOf(value)).mod(modulus.toBigInteger()).longValue()); + } + + public UInt64 multiplyMod(long value, long modulus) { + if (modulus == 0) { + throw new ArithmeticException("multiplyMod with zero modulus"); + } + if (modulus < 0) { + throw new ArithmeticException("multiplyMod unsigned with negative modulus"); + } + if (value == 0 || this.value == 0) { + return ZERO; + } + if (value == 1) { + return mod(modulus); + } + if (value < 0) { + throw new ArithmeticException("multiplyMod unsigned by negative"); + } + return create( + toBigInteger() + .multiply(BigInteger.valueOf(value)) + .mod(BigInteger.valueOf(modulus)) + .longValue()); + } + + public UInt64 divide(UInt64 value) { + if (value.value == 0) { + throw new ArithmeticException("divide by zero"); + } + if (value.value == 1) { + return this; + } + return create(toBigInteger().divide(value.toBigInteger()).longValue()); + } + + public UInt64 divide(long value) { + if (value == 0) { + throw new ArithmeticException("divide by zero"); + } + if (value < 0) { + throw new ArithmeticException("divide unsigned by negative"); + } + if (value == 1) { + return this; + } + if (isPowerOf2(value)) { + return fromBytes(mutableCopy().shiftRight(log2(value))); + } + return create(toBigInteger().divide(BigInteger.valueOf(value)).longValue()); + } + + public UInt64 pow(UInt64 exponent) { + return create(toBigInteger().modPow(exponent.toBigInteger(), P_2_64).longValue()); + } + + public UInt64 pow(long exponent) { + return create(toBigInteger().modPow(BigInteger.valueOf(exponent), P_2_64).longValue()); + } + + public UInt64 mod(UInt64 modulus) { + if (modulus.isZero()) { + throw new ArithmeticException("mod by zero"); + } + return create(toBigInteger().mod(modulus.toBigInteger()).longValue()); + } + + public UInt64 mod(long modulus) { + if (modulus == 0) { + throw new ArithmeticException("mod by zero"); + } + if (modulus < 0) { + throw new ArithmeticException("mod by negative"); + } + return create(this.value % modulus); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (!(object instanceof UInt64 other)) { + return false; + } + return this.value == other.value; + } + + @Override + public int computeHashcode() { + return Long.hashCode(this.value); + } + + public int compareTo(UInt64 other) { + return Long.compareUnsigned(this.value, other.value); + } + + public boolean fitsInt() { + return this.value >= 0 && this.value <= Integer.MAX_VALUE; + } + + public int intValue() { + if (!fitsInt()) { + throw new ArithmeticException("Value does not fit a 4 byte int"); + } + return (int) this.value; + } + + public boolean fitsLong() { + return this.value >= 0; + } + + @Override + public byte get(int i) { + checkElementIndex(i, size()); + return Utils.unpackByte(value, i); + } + + @Override + public long toLong() { + if (!fitsLong()) { + throw new ArithmeticException("Value does not fit a 8 byte long"); + } + return this.value; + } + + @Override + public BigInteger toBigInteger() { + return new BigInteger(1, toArrayUnsafe()); + } + + @Override + protected void and(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + bytesArray[offset + i] = (byte) (get(i) & bytesArray[offset + i]); + } + } + + @Override + protected void or(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + bytesArray[offset + i] = (byte) (get(i) | bytesArray[offset + i]); + } + } + + @Override + protected void xor(byte[] bytesArray, int offset, int length) { + for (int i = 0; i < length; i++) { + bytesArray[offset + i] = (byte) (get(i) ^ bytesArray[offset + i]); + } + } + + public UInt64 toUInt64() { + return this; + } + + public Bytes toBytes() { + MutableBytes bytes = MutableBytes.create(8); + bytes.setLong(0, this.value); + return bytes; + } + + public Bytes toMinimalBytes() { + int requiredBytes = 8 - (Long.numberOfLeadingZeros(this.value) / 8); + MutableBytes bytes = MutableBytes.create(requiredBytes); + int j = 0; + switch (requiredBytes) { + case 8: + bytes.set(j++, (byte) (this.value >>> 56)); + // fall through + case 7: + bytes.set(j++, (byte) ((this.value >>> 48) & 0xFF)); + // fall through + case 6: + bytes.set(j++, (byte) ((this.value >>> 40) & 0xFF)); + // fall through + case 5: + bytes.set(j++, (byte) ((this.value >>> 32) & 0xFF)); + // fall through + case 4: + bytes.set(j++, (byte) ((this.value >>> 24) & 0xFF)); + // fall through + case 3: + bytes.set(j++, (byte) ((this.value >>> 16) & 0xFF)); + // fall through + case 2: + bytes.set(j++, (byte) ((this.value >>> 8) & 0xFF)); + // fall through + case 1: + bytes.set(j, (byte) (this.value & 0xFF)); + } + return bytes; + } + + @Override + public int numberOfLeadingZeros() { + return Long.numberOfLeadingZeros(this.value); + } + + @Override + public int bitLength() { + return 64 - Long.numberOfLeadingZeros(this.value); + } + + @Override + public Bytes slice(int i, int length) { + return toBytes().slice(i, length); + } + + @Override + public MutableBytes mutableCopy() { + return MutableBytes.fromArray(toArrayUnsafe()); + } + + @Override + public byte[] toArrayUnsafe() { + byte[] bytesArray = new byte[8]; + bytesArray[0] = (byte) ((this.value >>> 56) & 0xFF); + bytesArray[1] = (byte) ((this.value >>> 48) & 0xFF); + bytesArray[2] = (byte) ((this.value >>> 40) & 0xFF); + bytesArray[3] = (byte) ((this.value >>> 32) & 0xFF); + bytesArray[4] = (byte) ((this.value >>> 24) & 0xFF); + bytesArray[5] = (byte) ((this.value >>> 16) & 0xFF); + bytesArray[6] = (byte) ((this.value >>> 8) & 0xFF); + bytesArray[7] = (byte) (this.value & 0xFF); + return bytesArray; + } + + private static boolean isPowerOf2(long n) { + assert n > 0; + return (n & (n - 1)) == 0; + } + + private static int log2(long v) { + assert v > 0; + return 63 - Long.numberOfLeadingZeros(v); + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value the amount to be added to this value + * @return {@code this + value} + * @throws ArithmeticException if the result of the addition overflows + */ + public UInt64 addExact(UInt64 value) { + UInt64 result = add(value); + if (compareTo(result) > 0) { + throw new ArithmeticException("UInt64 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this + value)}. + * + * @param value the amount to be added to this value + * @return {@code this + value} + * @throws ArithmeticException if the result of the addition overflows + */ + public UInt64 addExact(long value) { + UInt64 result = add(value); + if ((value > 0 && compareTo(result) > 0) || (value < 0 && compareTo(result) < 0)) { + throw new ArithmeticException("UInt64 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value the amount to be subtracted to this value + * @return {@code this - value} + * @throws ArithmeticException if the result of the subtraction overflows + */ + public UInt64 subtractExact(UInt64 value) { + UInt64 result = subtract(value); + if (compareTo(result) < 0) { + throw new ArithmeticException("UInt64 overflow"); + } + return result; + } + + /** + * Returns a value that is {@code (this - value)}. + * + * @param value the amount to be subtracted to this value + * @return {@code this - value} + * @throws ArithmeticException if the result of the subtraction overflows + */ + public UInt64 subtractExact(long value) { + UInt64 result = subtract(value); + if ((value > 0 && compareTo(result) < 0) || (value < 0 && compareTo(result) > 0)) { + throw new ArithmeticException("UInt64 overflow"); + } + return result; + } + + /** + * Returns the decimal representation of this value as a String. + * + * @return the decimal representation of this value as a String. + */ + public String toDecimalString() { + return toBigInteger().toString(10); + } +} diff --git a/units/src/main/java/org/apache/tuweni/v2/units/bigints/Utils.java b/units/src/main/java/org/apache/tuweni/v2/units/bigints/Utils.java new file mode 100644 index 000000000..cbd062374 --- /dev/null +++ b/units/src/main/java/org/apache/tuweni/v2/units/bigints/Utils.java @@ -0,0 +1,47 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.units.bigints; + +public class Utils { + static void and( + int[] sourceBytesArray, int sourceOffset, byte[] destBytesArray, int destOffset, int length) { + for (int i = 0; i < length; i++) { + // TODO: Speed this up with SIMD + destBytesArray[destOffset + i] = + (byte) (unpackByte(sourceBytesArray, sourceOffset + i) & destBytesArray[destOffset + i]); + } + } + + static void or( + int[] sourceBytesArray, int sourceOffset, byte[] destBytesArray, int destOffset, int length) { + for (int i = 0; i < length; i++) { + // TODO: Speed this up with SIMD + destBytesArray[destOffset + i] = + (byte) (unpackByte(sourceBytesArray, sourceOffset + i) | destBytesArray[destOffset + i]); + } + } + + static void xor( + int[] sourceBytesArray, int sourceOffset, byte[] destBytesArray, int destOffset, int length) { + for (int i = 0; i < length; i++) { + // TODO: Speed this up with SIMD + destBytesArray[destOffset + i] = + (byte) (unpackByte(sourceBytesArray, sourceOffset + i) ^ destBytesArray[destOffset + i]); + } + } + + static byte unpackByte(int[] ints, int index) { + int whichInt = index / 4; + return unpackByte(ints[whichInt], index); + } + + static byte unpackByte(int integer, int index) { + int whichIndex = 3 - index % 4; + return (byte) ((integer >> (8 * whichIndex)) & 0xFF); + } + + static byte unpackByte(long value, int index) { + int whichIndex = 7 - index; + return (byte) ((value >> (8 * whichIndex)) & 0xFF); + } +} diff --git a/units/src/main/java/org/apache/tuweni/v2/units/bigints/package-info.java b/units/src/main/java/org/apache/tuweni/v2/units/bigints/package-info.java new file mode 100644 index 000000000..261b20a94 --- /dev/null +++ b/units/src/main/java/org/apache/tuweni/v2/units/bigints/package-info.java @@ -0,0 +1,20 @@ +/** + * 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. + * + *

Classes and utilities for working with 256 bit integers. + */ +@ParametersAreNonnullByDefault +package org.apache.tuweni.v2.units.bigints; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/units/src/main/java/org/apache/tuweni/v2/units/package-info.java b/units/src/main/java/org/apache/tuweni/v2/units/package-info.java new file mode 100644 index 000000000..61c9fa3b8 --- /dev/null +++ b/units/src/main/java/org/apache/tuweni/v2/units/package-info.java @@ -0,0 +1,20 @@ +/** + * 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. + * + *

Classes and utilities for working with 256 bit integers and Ethereum units. + * + *

These classes are included in the standard Tuweni distribution, or separately when using the + * gradle dependency 'org.apache.tuweni:tuweni-units' (tuweni-units.jar). + */ +package org.apache.tuweni.v2.units; diff --git a/units/src/test/java/org/apache/tuweni/v2/units/bigints/UInt256Test.java b/units/src/test/java/org/apache/tuweni/v2/units/bigints/UInt256Test.java new file mode 100644 index 000000000..af8a526df --- /dev/null +++ b/units/src/test/java/org/apache/tuweni/v2/units/bigints/UInt256Test.java @@ -0,0 +1,1245 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.units.bigints; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.tuweni.v2.bytes.Bytes; +import org.apache.tuweni.v2.bytes.Bytes32; +import org.apache.tuweni.v2.bytes.MutableBytes; + +import java.math.BigInteger; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class UInt256Test { + + private static UInt256 v(long v) { + return UInt256.valueOf(v); + } + + private static UInt256 biv(String s) { + return UInt256.valueOf(new BigInteger(s)); + } + + private static UInt256 hv(String s) { + return UInt256.fromHexString(s); + } + + @Test + void valueOfLong() { + assertThrows(IllegalArgumentException.class, () -> UInt256.valueOf(-1)); + assertThrows(IllegalArgumentException.class, () -> UInt256.valueOf(Long.MIN_VALUE)); + assertThrows(IllegalArgumentException.class, () -> UInt256.valueOf(~0L)); + } + + @Test + void valueOfBigInteger() { + assertThrows(IllegalArgumentException.class, () -> UInt256.valueOf(BigInteger.valueOf(-1))); + assertThrows( + IllegalArgumentException.class, () -> UInt256.valueOf(BigInteger.valueOf(2).pow(256))); + } + + @ParameterizedTest + @MethodSource("addProvider") + void add(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addProvider() { + return Stream.of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(0), v(1), v(1)), + Arguments.of(v(0), v(100), v(100)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(100), v(90), v(190)), + Arguments.of(biv("9223372036854775807"), v(1), biv("9223372036854775808")), + Arguments.of( + biv("13492324908428420834234908342"), v(10), biv("13492324908428420834234908352")), + Arguments.of( + biv("13492324908428420834234908342"), + v(23422141424214L), + biv("13492324908428444256376332556")), + Arguments.of(UInt256.MAX_VALUE, v(1), v(0)), + Arguments.of(UInt256.MAX_VALUE, v(2), v(1)), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), + v(1), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + v(1), + UInt256.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addLongProvider") + void addLong(UInt256 v1, long v2, UInt256 expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addLongProvider() { + return Stream.of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(0), 1L, v(1)), + Arguments.of(v(0), 100L, v(100)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(100), 90L, v(190)), + Arguments.of( + biv("13492324908428420834234908342"), 10L, biv("13492324908428420834234908352")), + Arguments.of( + biv("13492324908428420834234908342"), + 23422141424214L, + biv("13492324908428444256376332556")), + Arguments.of(UInt256.MAX_VALUE, 1L, v(0)), + Arguments.of(UInt256.MAX_VALUE, 2L, v(1)), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), + 1L, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + 1L, + UInt256.MAX_VALUE), + Arguments.of(v(10), -5L, v(5)), + Arguments.of(v(0), -1L, UInt256.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addModProvider") + void addMod(UInt256 v1, UInt256 v2, UInt256 m, UInt256 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModProvider() { + return Stream.of( + Arguments.of(v(0), v(1), UInt256.valueOf(2), v(1)), + Arguments.of(v(1), v(1), UInt256.valueOf(2), v(0)), + Arguments.of( + UInt256.MAX_VALUE.subtract(2), v(1), UInt256.MAX_VALUE, UInt256.MAX_VALUE.subtract(1)), + Arguments.of(UInt256.MAX_VALUE.subtract(1), v(1), UInt256.MAX_VALUE, v(0)), + Arguments.of(v(2), v(1), UInt256.valueOf(2), v(1)), + Arguments.of(v(3), v(2), UInt256.valueOf(6), v(5)), + Arguments.of(v(3), v(4), UInt256.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModOfZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).addMod(v(1), UInt256.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModUInt256UInt256Provider") + void addModUInt256UInt256(UInt256 v1, UInt256 v2, UInt256 m, UInt256 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModUInt256UInt256Provider() { + return Stream.of( + Arguments.of(v(0), UInt256.ONE, UInt256.valueOf(2), v(1)), + Arguments.of(v(1), UInt256.ONE, UInt256.valueOf(2), v(0)), + Arguments.of( + UInt256.MAX_VALUE.subtract(2), + UInt256.ONE, + UInt256.MAX_VALUE, + UInt256.MAX_VALUE.subtract(1)), + Arguments.of(UInt256.MAX_VALUE.subtract(1), UInt256.ONE, UInt256.MAX_VALUE, v(0)), + Arguments.of(v(2), UInt256.ONE, UInt256.valueOf(2), v(1)), + Arguments.of(v(3), UInt256.valueOf(2), UInt256.valueOf(6), v(5)), + Arguments.of(v(3), UInt256.valueOf(4), UInt256.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModLongUInt256OfZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).addMod(1, UInt256.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongUInt256Provider") + void addModLongUInt256(UInt256 v1, long v2, UInt256 m, UInt256 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongUInt256Provider() { + return Stream.of( + Arguments.of(v(0), 1L, UInt256.valueOf(2), v(1)), + Arguments.of(v(1), 1L, UInt256.valueOf(2), v(0)), + Arguments.of( + UInt256.MAX_VALUE.subtract(2), 1L, UInt256.MAX_VALUE, UInt256.MAX_VALUE.subtract(1)), + Arguments.of(UInt256.MAX_VALUE.subtract(1), 1L, UInt256.MAX_VALUE, v(0)), + Arguments.of(v(2), 1L, UInt256.valueOf(2), v(1)), + Arguments.of(v(2), -1L, UInt256.valueOf(2), v(1)), + Arguments.of(v(1), -7L, UInt256.valueOf(5), v(4))); + } + + @Test + void shouldThrowForAddModUInt256UInt256OfZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).addMod(UInt256.ONE, UInt256.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongLongProvider") + void addModLongLong(UInt256 v1, long v2, long m, UInt256 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongLongProvider() { + return Stream.of( + Arguments.of(v(0), 1L, 2L, v(1)), + Arguments.of(v(1), 1L, 2L, v(0)), + Arguments.of(v(2), 1L, 2L, v(1))); + } + + @Test + void shouldThrowForAddModLongLongOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, 0)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForAddModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, -5)); + assertEquals("addMod unsigned with negative modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("subtractProvider") + void subtract(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractProvider() { + return Stream.of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(2), v(1), v(1)), + Arguments.of(v(100), v(100), v(0)), + Arguments.of( + biv("13492324908428420834234908342"), v(10), biv("13492324908428420834234908332")), + Arguments.of( + biv("13492324908428420834234908342"), + v(23422141424214L), + biv("13492324908428397412093484128")), + Arguments.of(v(0), v(1), UInt256.MAX_VALUE), + Arguments.of(v(1), v(2), UInt256.MAX_VALUE), + Arguments.of( + UInt256.MAX_VALUE, + v(1), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); + } + + @ParameterizedTest + @MethodSource("subtractLongProvider") + void subtractLong(UInt256 v1, long v2, UInt256 expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractLongProvider() { + return Stream.of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(2), 1L, v(1)), + Arguments.of(v(100), 100L, v(0)), + Arguments.of( + biv("13492324908428420834234908342"), 10L, biv("13492324908428420834234908332")), + Arguments.of( + biv("13492324908428420834234908342"), + 23422141424214L, + biv("13492324908428397412093484128")), + Arguments.of(v(0), 1L, UInt256.MAX_VALUE), + Arguments.of(v(1), 2L, UInt256.MAX_VALUE), + Arguments.of( + UInt256.MAX_VALUE, + 1L, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), + Arguments.of(v(0), -1L, v(1)), + Arguments.of(v(0), -100L, v(100)), + Arguments.of(v(2), -2L, v(4))); + } + + @ParameterizedTest + @MethodSource("multiplyProvider") + void multiply(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyProvider() { + return Stream.of( + Arguments.of(v(0), v(1), v(0)), + Arguments.of(v(1), v(0), v(0)), + Arguments.of(v(1), v(20), v(20)), + Arguments.of(hv("0x0a0000000000"), v(1), hv("0x0a0000000000")), + Arguments.of(v(1), hv("0x0a0000000000"), hv("0x0a0000000000")), + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(2)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(3), v(2), v(6)), + Arguments.of(v(4), v(2), v(8)), + Arguments.of(v(10), v(18), v(180)), + Arguments.of( + biv("13492324908428420834234908341"), v(2), biv("26984649816856841668469816682")), + Arguments.of( + biv("13492324908428420834234908342"), v(2), biv("26984649816856841668469816684")), + Arguments.of(v(2), v(8), v(16)), + Arguments.of(v(7), v(8), v(56)), + Arguments.of(v(8), v(8), v(64)), + Arguments.of(v(17), v(8), v(136)), + Arguments.of( + biv("13492324908428420834234908342"), v(8), biv("107938599267427366673879266736")), + Arguments.of( + biv("13492324908428420834234908342"), v(2048), biv("27632281412461405868513092284416")), + Arguments.of( + biv("13492324908428420834234908342"), + v(131072), + biv("1768466010397529975584837906202624")), + Arguments.of(v(22), v(0), v(0))); + } + + @ParameterizedTest + @MethodSource("multiplyLongProvider") + void multiplyLong(UInt256 v1, long v2, UInt256 expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyLongProvider() { + return Stream.of( + Arguments.of(v(0), 1L, v(0)), + Arguments.of(v(1), 0L, v(0)), + Arguments.of(v(1), 20L, v(20)), + Arguments.of(v(20), 1L, v(20)), + Arguments.of(hv("0x0a0000000000"), 1L, hv("0x0a0000000000")), + Arguments.of(v(1), 10995116277760L, hv("0x0a0000000000")), + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(2)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(3), 2L, v(6)), + Arguments.of(v(4), 2L, v(8)), + Arguments.of(v(10), 18L, v(180)), + Arguments.of( + biv("13492324908428420834234908341"), 2L, biv("26984649816856841668469816682")), + Arguments.of( + biv("13492324908428420834234908342"), 2L, biv("26984649816856841668469816684")), + Arguments.of(v(2), 8L, v(16)), + Arguments.of(v(7), 8L, v(56)), + Arguments.of(v(8), 8L, v(64)), + Arguments.of(v(17), 8L, v(136)), + Arguments.of( + biv("13492324908428420834234908342"), 8L, biv("107938599267427366673879266736")), + Arguments.of( + biv("13492324908428420834234908342"), 2048L, biv("27632281412461405868513092284416")), + Arguments.of( + biv("13492324908428420834234908342"), + 131072L, + biv("1768466010397529975584837906202624")), + Arguments.of(v(22), 0L, v(0)), + Arguments.of( + hv("0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 2L, + hv("0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 2L, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); + } + + @Test + void shouldThrowForMultiplyLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiply(-5)); + assertEquals("multiply unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModProvider") + void multiplyMod(UInt256 v1, UInt256 v2, UInt256 m, UInt256 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModProvider() { + return Stream.of( + Arguments.of(v(0), v(5), UInt256.valueOf(2), v(0)), + Arguments.of(v(2), v(3), UInt256.valueOf(7), v(6)), + Arguments.of(v(2), v(3), UInt256.valueOf(6), v(0)), + Arguments.of(v(2), v(0), UInt256.valueOf(6), v(0)), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + v(2), + UInt256.MAX_VALUE, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModOfModZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(v(1), UInt256.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongUInt256Provider") + void multiplyModLongUInt256(UInt256 v1, long v2, UInt256 m, UInt256 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongUInt256Provider() { + return Stream.of( + Arguments.of(v(0), 5L, UInt256.valueOf(2), v(0)), + Arguments.of(v(2), 3L, UInt256.valueOf(7), v(6)), + Arguments.of(v(2), 3L, UInt256.valueOf(6), v(0)), + Arguments.of(v(2), 0L, UInt256.valueOf(6), v(0)), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + 2L, + UInt256.MAX_VALUE, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModLongUInt256OfModZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1L, UInt256.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongUInt256OfNegative() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, UInt256.valueOf(2))); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongLongProvider") + void multiplyModLongLong(UInt256 v1, long v2, long m, UInt256 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongLongProvider() { + return Stream.of( + Arguments.of(v(0), 5L, 2L, v(0)), + Arguments.of(v(2), 3L, 7L, v(6)), + Arguments.of(v(2), 3L, 6L, v(0)), + Arguments.of(v(2), 0L, 6L, v(0)), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + 2L, + Long.MAX_VALUE, + hv("0x000000000000000000000000000000000000000000000000000000000000001C"))); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1, 0)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiplyMod(5, -7)); + assertEquals("multiplyMod unsigned with negative modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, 2)); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideProvider") + void divide(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideProvider() { + return Stream.of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(0)), + Arguments.of(v(2), v(2), v(1)), + Arguments.of(v(3), v(2), v(1)), + Arguments.of(v(4), v(2), v(2)), + Arguments.of( + biv("13492324908428420834234908341"), v(2), biv("6746162454214210417117454170")), + Arguments.of( + biv("13492324908428420834234908342"), v(2), biv("6746162454214210417117454171")), + Arguments.of( + biv("13492324908428420834234908343"), v(2), biv("6746162454214210417117454171")), + Arguments.of(v(2), v(8), v(0)), + Arguments.of(v(7), v(8), v(0)), + Arguments.of(v(8), v(8), v(1)), + Arguments.of(v(9), v(8), v(1)), + Arguments.of(v(17), v(8), v(2)), + Arguments.of(v(1024), v(8), v(128)), + Arguments.of(v(1026), v(8), v(128)), + Arguments.of( + biv("13492324908428420834234908342"), v(8), biv("1686540613553552604279363542")), + Arguments.of( + biv("13492324908428420834234908342"), v(2048), biv("6588049271693564860466263")), + Arguments.of( + biv("13492324908428420834234908342"), v(131072), biv("102938269870211950944785"))); + } + + @Test + void shouldThrowForDivideByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(v(0))); + assertEquals("divide by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideCeilProvider") + void divideCeil(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.divideCeil(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideCeilProvider() { + return Stream.of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(1)), + Arguments.of(v(2), v(2), v(1)), + Arguments.of(v(3), v(2), v(2)), + Arguments.of(v(4), v(2), v(2)), + Arguments.of( + biv("13492324908428420834234908341"), v(2), biv("6746162454214210417117454171")), + Arguments.of( + biv("13492324908428420834234908342"), v(2), biv("6746162454214210417117454171")), + Arguments.of( + biv("13492324908428420834234908343"), v(2), biv("6746162454214210417117454172")), + Arguments.of(v(2), v(8), v(1)), + Arguments.of(v(7), v(8), v(1)), + Arguments.of(v(8), v(8), v(1)), + Arguments.of(v(9), v(8), v(2)), + Arguments.of(v(17), v(8), v(3)), + Arguments.of(v(1024), v(8), v(128)), + Arguments.of(v(1026), v(8), v(129)), + Arguments.of( + biv("13492324908428420834234908342"), v(8), biv("1686540613553552604279363543")), + Arguments.of( + biv("13492324908428420834234908342"), v(2048), biv("6588049271693564860466264")), + Arguments.of( + biv("13492324908428420834234908342"), v(131072), biv("102938269870211950944786"))); + } + + @Test + void shouldThrowForDivideCeilByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divideCeil(v(0))); + assertEquals("divide by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideLongProvider") + void divideLong(UInt256 v1, long v2, UInt256 expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideLongProvider() { + return Stream.of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(0)), + Arguments.of(v(2), 2L, v(1)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(v(4), 2L, v(2)), + Arguments.of(biv("13492324908428420834234908341"), 2L, biv("6746162454214210417117454170")), + Arguments.of(biv("13492324908428420834234908342"), 2L, biv("6746162454214210417117454171")), + Arguments.of(biv("13492324908428420834234908343"), 2L, biv("6746162454214210417117454171")), + Arguments.of(v(2), 8L, v(0)), + Arguments.of(v(7), 8L, v(0)), + Arguments.of(v(8), 8L, v(1)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(17), 8L, v(2)), + Arguments.of(v(1024), 8L, v(128)), + Arguments.of(v(1026), 8L, v(128)), + Arguments.of(biv("13492324908428420834234908342"), 8L, biv("1686540613553552604279363542")), + Arguments.of(biv("13492324908428420834234908342"), 2048L, biv("6588049271693564860466263")), + Arguments.of( + biv("13492324908428420834234908342"), 131072L, biv("102938269870211950944785"))); + } + + @Test + void shouldThrowForDivideLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(0)); + assertEquals("divide by zero", exception.getMessage()); + } + + @Test + void shouldThrowForDivideLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(-5)); + assertEquals("divide unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("powUInt256Provider") + void powUInt256(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powUInt256Provider() { + return Stream.of( + Arguments.of(v(0), UInt256.valueOf(2), v(0)), + Arguments.of(v(2), UInt256.valueOf(2), v(4)), + Arguments.of(v(2), UInt256.valueOf(8), v(256)), + Arguments.of(v(3), UInt256.valueOf(3), v(27)), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0F0"), + UInt256.valueOf(3), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A920E119A2F000"))); + } + + @ParameterizedTest + @MethodSource("powLongProvider") + void powLong(UInt256 v1, long v2, UInt256 expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powLongProvider() { + return Stream.of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(2), 8L, v(256)), + Arguments.of(v(3), 3L, v(27)), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0F0"), + 3L, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A920E119A2F000")), + Arguments.of( + v(3), -3L, hv("0x2F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA13"))); + } + + @ParameterizedTest + @MethodSource("modLongProvider") + void modLong(UInt256 v1, long v2, UInt256 expected) { + assertValueEquals(expected, v1.mod(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream modLongProvider() { + return Stream.of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(1)), + Arguments.of(v(2), 2L, v(0)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(biv("13492324908428420834234908342"), 2L, v(0)), + Arguments.of(biv("13492324908428420834234908343"), 2L, v(1)), + Arguments.of(v(0), 8L, v(0)), + Arguments.of(v(1), 8L, v(1)), + Arguments.of(v(2), 8L, v(2)), + Arguments.of(v(3), 8L, v(3)), + Arguments.of(v(7), 8L, v(7)), + Arguments.of(v(8), 8L, v(0)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(1024), 8L, v(0)), + Arguments.of(v(1026), 8L, v(2)), + Arguments.of(biv("13492324908428420834234908342"), 8L, v(6)), + Arguments.of(biv("13492324908428420834234908343"), 8L, v(7)), + Arguments.of(biv("13492324908428420834234908344"), 8L, v(0))); + } + + @Test + void shouldThrowForModLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(0)); + assertEquals("mod by zero", exception.getMessage()); + } + + @Test + void shouldThrowForModLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(-5)); + assertEquals("mod by negative", exception.getMessage()); + } + + @Test + void shouldReturnZeroForMod0LongByZero() { + assertEquals(UInt256.ZERO, v(5).mod0(0)); + } + + @Test + void shouldThrowForMod0LongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod0(-5)); + assertEquals("mod by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("andProvider") + void and(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.and(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream andProvider() { + return Stream.of( + Arguments.of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000")), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0x000000000000000000000000000000FF00000000000000000000000000000000"))); + } + + @ParameterizedTest + @MethodSource("andProviderBytes32") + void andBytes32(UInt256 v1, Bytes32 v2, UInt256 expected) { + assertValueEquals(expected, v1.and(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream andProviderBytes32() { + return Stream.of( + Arguments.of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + Bytes32.wrapHexString( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000")), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + Bytes32.wrapHexString( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0x000000000000000000000000000000FF00000000000000000000000000000000"))); + } + + @ParameterizedTest + @MethodSource("orProvider") + void or(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.or(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream orProvider() { + return Stream.of( + Arguments.of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments.of( + hv("0x0000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))); + } + + @ParameterizedTest + @MethodSource("orProviderBytes32") + void orBytes32(UInt256 v1, Bytes32 v2, UInt256 expected) { + assertValueEquals(expected, v1.or(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream orProviderBytes32() { + return Stream.of( + Arguments.of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + Bytes32.wrapHexString( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + Bytes32.wrapHexString( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments.of( + hv("0x0000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + Bytes32.wrapHexString( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))); + } + + @ParameterizedTest + @MethodSource("xorProvider") + void xor(UInt256 v1, UInt256 v2, UInt256 expected) { + assertValueEquals(expected, v1.xor(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream xorProvider() { + return Stream.of( + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000")), + Arguments.of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))); + } + + @ParameterizedTest + @MethodSource("xorProviderBytes32") + void xorBytes32(UInt256 v1, Bytes32 v2, UInt256 expected) { + assertValueEquals(expected, v1.xor(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream xorProviderBytes32() { + return Stream.of( + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + Bytes32.wrapHexString( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000")), + Arguments.of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + Bytes32.wrapHexString( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + Bytes32.wrapHexString( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))); + } + + @ParameterizedTest + @MethodSource("notProvider") + void not(UInt256 value, UInt256 expected) { + assertValueEquals(expected, value.not()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream notProvider() { + return Stream.of( + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000")), + Arguments.of( + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000"))); + } + + @ParameterizedTest + @MethodSource("shiftLeftProvider") + void shiftLeft(UInt256 value, int distance, UInt256 expected) { + assertValueEquals(expected, value.shiftLeft(distance)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream shiftLeftProvider() { + return Stream.of( + Arguments.of(hv("0x01"), 1, hv("0x02")), + Arguments.of(hv("0x01"), 2, hv("0x04")), + Arguments.of(hv("0x01"), 8, hv("0x0100")), + Arguments.of(hv("0x01"), 9, hv("0x0200")), + Arguments.of(hv("0x01"), 16, hv("0x10000")), + Arguments.of(hv("0x00FF00"), 4, hv("0x0FF000")), + Arguments.of(hv("0x00FF00"), 8, hv("0xFF0000")), + Arguments.of(hv("0x00FF00"), 1, hv("0x01FE00")), + Arguments.of( + hv("0x0000000000000000000000000000000000000000000000000000000000000001"), + 16, + hv("0x0000000000000000000000000000000000000000000000000000000000010000")), + Arguments.of( + hv("0x0000000000000000000000000000000000000000000000000000000000000001"), + 15, + hv("0x0000000000000000000000000000000000000000000000000000000000008000")), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 55, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000000000000")), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 202, + hv("0xFFFFFFFFFFFFFC00000000000000000000000000000000000000000000000000"))); + } + + @ParameterizedTest + @MethodSource("shiftRightProvider") + void shiftRight(UInt256 value, int distance, UInt256 expected) { + assertValueEquals(expected, value.shiftRight(distance)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream shiftRightProvider() { + return Stream.of( + Arguments.of(hv("0x01"), 1, hv("0x00")), + Arguments.of(hv("0x10"), 1, hv("0x08")), + Arguments.of(hv("0x10"), 2, hv("0x04")), + Arguments.of(hv("0x10"), 8, hv("0x00")), + Arguments.of(hv("0x1000"), 4, hv("0x0100")), + Arguments.of(hv("0x1000"), 5, hv("0x0080")), + Arguments.of(hv("0x1000"), 8, hv("0x0010")), + Arguments.of(hv("0x1000"), 9, hv("0x0008")), + Arguments.of(hv("0x1000"), 16, hv("0x0000")), + Arguments.of(hv("0x00FF00"), 4, hv("0x000FF0")), + Arguments.of(hv("0x00FF00"), 8, hv("0x0000FF")), + Arguments.of(hv("0x00FF00"), 1, hv("0x007F80")), + Arguments.of( + hv("0x1000000000000000000000000000000000000000000000000000000000000000"), + 16, + hv("0x0000100000000000000000000000000000000000000000000000000000000000")), + Arguments.of( + hv("0x1000000000000000000000000000000000000000000000000000000000000000"), + 15, + hv("0x0000200000000000000000000000000000000000000000000000000000000000")), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 55, + hv("0x00000000000001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000000000"), + 202, + hv("0x000000000000000000000000000000000000000000000000003FFFFFFFFFFFFF"))); + } + + @ParameterizedTest + @MethodSource("intValueProvider") + void intValue(UInt256 value, int expected) { + assertEquals(expected, value.intValue()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream intValueProvider() { + return Stream.of( + Arguments.of(hv("0x"), 0), + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x00000000"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x0001"), 1), + Arguments.of(hv("0x000001"), 1), + Arguments.of(hv("0x00000001"), 1), + Arguments.of(hv("0x0100"), 256), + Arguments.of(hv("0x000100"), 256), + Arguments.of(hv("0x00000100"), 256)); + } + + @Test + void shouldThrowForIntValueOfOversizeValue() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> hv("0x0100000000").intValue()); + assertEquals("Value does not fit a 4 byte int", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("longValueProvider") + void longValue(UInt256 value, long expected) { + assertEquals(expected, value.toLong()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream longValueProvider() { + return Stream.of( + Arguments.of(hv("0x"), 0L), + Arguments.of(hv("0x00"), 0L), + Arguments.of(hv("0x00000000"), 0L), + Arguments.of(hv("0x01"), 1L), + Arguments.of(hv("0x0001"), 1L), + Arguments.of(hv("0x000001"), 1L), + Arguments.of(hv("0x00000001"), 1L), + Arguments.of(hv("0x0000000001"), 1L), + Arguments.of(hv("0x000000000001"), 1L), + Arguments.of(hv("0x0100"), 256L), + Arguments.of(hv("0x000100"), 256L), + Arguments.of(hv("0x00000100"), 256L), + Arguments.of(hv("0x00000100"), 256L), + Arguments.of(hv("0x000000000100"), 256L), + Arguments.of(hv("0x00000000000100"), 256L), + Arguments.of(hv("0x0000000000000100"), 256L), + Arguments.of(hv("0xFFFFFFFF"), (1L << 32) - 1)); + } + + @Test + void shouldThrowForLongValueOfOversizeValue() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> hv("0x010000000000000000").toLong()); + assertEquals("Value does not fit a 8 byte long", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("compareToProvider") + void compareTo(UInt256 v1, UInt256 v2, int expected) { + assertEquals(expected, v1.compareTo(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream compareToProvider() { + return Stream.of( + Arguments.of(v(5), v(5), 0), + Arguments.of(v(5), v(3), 1), + Arguments.of(v(5), v(6), -1), + Arguments.of( + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + 0), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 0), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 0), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + 1), + Arguments.of( + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + -1), + Arguments.of( + hv("0x000000000000000000000000000001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 1), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + -1)); + } + + @ParameterizedTest + @MethodSource("toBytesProvider") + void toBytesTest(UInt256 value, Bytes expected) { + assertEquals(expected, value); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toBytesProvider() { + return Stream.of( + Arguments.of( + hv("0x00"), + Bytes.fromHexString( + "0x0000000000000000000000000000000000000000000000000000000000000000")), + Arguments.of( + hv("0x01000000"), + Bytes.fromHexString( + "0x0000000000000000000000000000000000000000000000000000000001000000")), + Arguments.of( + hv("0x0100000000"), + Bytes.fromHexString( + "0x0000000000000000000000000000000000000000000000000000000100000000")), + Arguments.of( + hv("0xf100000000ab"), + Bytes.fromHexString( + "0x0000000000000000000000000000000000000000000000000000f100000000ab")), + Arguments.of( + hv("0x0400000000000000000000000000000000000000000000000000f100000000ab"), + Bytes.fromHexString( + "0x0400000000000000000000000000000000000000000000000000f100000000ab"))); + } + + @ParameterizedTest + @MethodSource("fromBytesProvider") + void fromBytesTest(Bytes value, UInt256 expected, boolean isBytes32) { + assertEquals(expected, UInt256.fromBytes(value)); + assertEquals(isBytes32, value instanceof Bytes32); + } + + @SuppressWarnings("UnusedMethod") + private static Stream fromBytesProvider() { + String onesString = "11111111111111111111111111111111"; + String twosString = "22222222222222222222222222222222"; + String eString = "e000000000e000000000e000000000e0"; + Bytes onesBytes = Bytes.fromHexString(onesString); + Bytes twosBytes = Bytes.fromHexString(twosString); + Bytes eBytes = Bytes.fromHexString(eString); + Bytes onetwoBytes = Bytes.fromHexString(onesString + twosString); + Bytes oneeBytes = Bytes.fromHexString(onesString + eString); + return Stream.of( + // Mutable Bytes + Arguments.of(Bytes.wrap(onesBytes), hv(onesString), false), + Arguments.of(Bytes.wrap(eBytes), hv(eString), false), + Arguments.of(Bytes.wrap(onesBytes, twosBytes), hv(onesString + twosString), false), + Arguments.of(Bytes.wrap(onesBytes, eBytes), hv(onesString + eString), false), + // Array Wrapping Bytes + Arguments.of(Bytes.fromHexString(onesString), hv(onesString), false), + Arguments.of(Bytes.fromHexString(eString), hv(eString), false), + Arguments.of( + Bytes.fromHexString(onesString + twosString), hv(onesString + twosString), false), + Arguments.of(Bytes.fromHexString(onesString + eString), hv(onesString + eString), false), + // Delegating Bytes32 + Arguments.of(Bytes32.wrap(onetwoBytes), hv(onesString + twosString), true), + Arguments.of(Bytes32.wrap(oneeBytes), hv(onesString + eString), true)); + } + + @ParameterizedTest + @MethodSource("toMinimalBytesProvider") + void toMinimalBytesTest(UInt256 value, Bytes expected) { + assertEquals(expected, value.toMinimalBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toMinimalBytesProvider() { + return Stream.of( + Arguments.of(hv("0x00"), Bytes.EMPTY), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000")), + Arguments.of(hv("0x0100000000"), Bytes.fromHexString("0x0100000000")), + Arguments.of(hv("0xf100000000ab"), Bytes.fromHexString("0xf100000000ab")), + Arguments.of( + hv("0x0400000000000000000000000000000000000000000000000000f100000000ab"), + Bytes.fromHexString( + "0x0400000000000000000000000000000000000000000000000000f100000000ab"))); + } + + @ParameterizedTest + @MethodSource("numberOfLeadingZerosProvider") + void numberOfLeadingZeros(UInt256 value, int expected) { + assertEquals(expected, value.numberOfLeadingZeros()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream numberOfLeadingZerosProvider() { + return Stream.of( + Arguments.of(hv("0x00"), 256), + Arguments.of(hv("0x01"), 255), + Arguments.of(hv("0x02"), 254), + Arguments.of(hv("0x03"), 254), + Arguments.of(hv("0x0F"), 252), + Arguments.of(hv("0x8F"), 248), + Arguments.of(hv("0x100000000"), 223)); + } + + @ParameterizedTest + @MethodSource("bitLengthProvider") + void bitLength(UInt256 value, int expected) { + assertEquals(expected, value.bitLength()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream bitLengthProvider() { + return Stream.of( + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x02"), 2), + Arguments.of(hv("0x03"), 2), + Arguments.of(hv("0x0F"), 4), + Arguments.of(hv("0x8F"), 8), + Arguments.of(hv("0x100000000"), 33)); + } + + @ParameterizedTest + @MethodSource("addExactProvider") + void addExact(UInt256 value, UInt256 operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactProvider() { + return Stream.of( + Arguments.of(UInt256.MAX_VALUE, v(1)), Arguments.of(UInt256.MAX_VALUE, UInt256.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addExactLongProvider") + void addExactLong(UInt256 value, long operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactLongProvider() { + return Stream.of( + Arguments.of(UInt256.MAX_VALUE, 3), + Arguments.of(UInt256.MAX_VALUE, Long.MAX_VALUE), + Arguments.of(v(0), -1)); + } + + @ParameterizedTest + @MethodSource("subtractExactProvider") + void subtractExact(UInt256 value, UInt256 operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactProvider() { + return Stream.of(Arguments.of(v(0), v(1)), Arguments.of(v(0), UInt256.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("subtractExactLongProvider") + void subtractExactLong(UInt256 value, long operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @Test + void testGet() { + UInt256 value = UInt256.ONE; + assertEquals(1, value.get(31)); + UInt256 value5 = UInt256.valueOf(5); + assertEquals(5, value5.get(31)); + UInt256 value255 = UInt256.valueOf(255); + assertEquals((byte) 0xff, value255.get(31)); + UInt256 value256 = UInt256.valueOf(256); + assertEquals(1, value256.get(30)); + + for (int i = 0; i < 32; i++) { + assertEquals(0, UInt256.ZERO.get(i)); + } + } + + @Test + void testHashcode() { + UInt256 value = UInt256.ZERO; + assertEquals(2111290369, value.hashCode()); + UInt256 valueOne = UInt256.ONE; + assertEquals(2111290370, valueOne.hashCode()); + } + + @Test + void testOverflowSubtraction() { + UInt256 value = + UInt256.fromHexString("0x8000000000000000000000000000000000000000000000000000000000000000"); + UInt256 result = UInt256.ZERO.subtract(value); + assertEquals(value, result); + } + + @Test + void testEquals() { + UInt256 value = UInt256.ZERO; + assertEquals(MutableBytes.create(32), value); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactLongProvider() { + return Stream.of( + Arguments.of(v(0), 1), + Arguments.of(v(0), Long.MAX_VALUE), + Arguments.of(UInt256.MAX_VALUE, -1)); + } + + private void assertValueEquals(UInt256 expected, UInt256 actual) { + String msg = + String.format("Expected %s but got %s", expected.toHexString(), actual.toHexString()); + assertEquals(expected, actual, msg); + } + + @Test + void testToDecimalString() { + assertEquals("3456", UInt256.valueOf(3456).toDecimalString()); + } +} diff --git a/units/src/test/java/org/apache/tuweni/v2/units/bigints/UInt32Test.java b/units/src/test/java/org/apache/tuweni/v2/units/bigints/UInt32Test.java new file mode 100644 index 000000000..7b5af69d9 --- /dev/null +++ b/units/src/test/java/org/apache/tuweni/v2/units/bigints/UInt32Test.java @@ -0,0 +1,832 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.units.bigints; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.tuweni.v2.bytes.Bytes; + +import java.math.BigInteger; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class UInt32Test { + + private static UInt32 v(int v) { + return UInt32.valueOf(v); + } + + private static UInt32 hv(String s) { + return UInt32.fromHexString(s); + } + + private static Bytes b(String s) { + return Bytes.fromHexString(s); + } + + @Test + void valueOfInt() { + assertThrows(IllegalArgumentException.class, () -> UInt32.valueOf(-1)); + assertThrows(IllegalArgumentException.class, () -> UInt32.valueOf(Integer.MIN_VALUE)); + assertThrows(IllegalArgumentException.class, () -> UInt32.valueOf(~0)); + } + + @Test + void valueOfBigInteger() { + assertThrows(IllegalArgumentException.class, () -> UInt32.valueOf(BigInteger.valueOf(-1))); + assertThrows( + IllegalArgumentException.class, () -> UInt32.valueOf(BigInteger.valueOf(2).pow(32))); + } + + @ParameterizedTest + @MethodSource("addProvider") + void add(UInt32 v1, UInt32 v2, UInt32 expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addProvider() { + return Stream.of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(0), v(1), v(1)), + Arguments.of(v(0), v(100), v(100)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(100), v(90), v(190)), + Arguments.of(UInt32.MAX_VALUE, v(1), v(0)), + Arguments.of(UInt32.MAX_VALUE, v(2), v(1)), + Arguments.of(hv("0xFFFFFFF0"), v(1), hv("0xFFFFFFF1")), + Arguments.of(hv("0xFFFFFFFE"), v(1), UInt32.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addLongProvider") + void addLong(UInt32 v1, int v2, UInt32 expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addLongProvider() { + return Stream.of( + Arguments.of(v(1), 0, v(1)), + Arguments.of(v(5), 0, v(5)), + Arguments.of(v(0), 1, v(1)), + Arguments.of(v(0), 100, v(100)), + Arguments.of(v(2), 2, v(4)), + Arguments.of(v(100), 90, v(190)), + Arguments.of(UInt32.MAX_VALUE, 1, v(0)), + Arguments.of(UInt32.MAX_VALUE, 2, v(1)), + Arguments.of(hv("0xFFFFFFF0"), 1, hv("0xFFFFFFF1")), + Arguments.of(hv("0xFFFFFFFE"), 1, UInt32.MAX_VALUE), + Arguments.of(v(10), -5, v(5)), + Arguments.of(v(0), -1, UInt32.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addModProvider") + void addMod(UInt32 v1, UInt32 v2, UInt32 m, UInt32 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModProvider() { + return Stream.of( + Arguments.of(v(0), v(1), UInt32.valueOf(2), v(1)), + Arguments.of(v(1), v(1), UInt32.valueOf(2), v(0)), + Arguments.of( + UInt32.MAX_VALUE.subtract(2), v(1), UInt32.MAX_VALUE, UInt32.MAX_VALUE.subtract(1)), + Arguments.of(UInt32.MAX_VALUE.subtract(1), v(1), UInt32.MAX_VALUE, v(0)), + Arguments.of(v(2), v(1), UInt32.valueOf(2), v(1)), + Arguments.of(v(3), v(2), UInt32.valueOf(6), v(5)), + Arguments.of(v(3), v(4), UInt32.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModOfZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).addMod(v(1), UInt32.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModUInt32UInt32Provider") + void addModUInt32UInt32(UInt32 v1, UInt32 v2, UInt32 m, UInt32 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModUInt32UInt32Provider() { + return Stream.of( + Arguments.of(v(0), UInt32.ONE, UInt32.valueOf(2), v(1)), + Arguments.of(v(1), UInt32.ONE, UInt32.valueOf(2), v(0)), + Arguments.of( + UInt32.MAX_VALUE.subtract(2), + UInt32.ONE, + UInt32.MAX_VALUE, + UInt32.MAX_VALUE.subtract(1)), + Arguments.of(UInt32.MAX_VALUE.subtract(1), UInt32.ONE, UInt32.MAX_VALUE, v(0)), + Arguments.of(v(2), UInt32.ONE, UInt32.valueOf(2), v(1)), + Arguments.of(v(3), UInt32.valueOf(2), UInt32.valueOf(6), v(5)), + Arguments.of(v(3), UInt32.valueOf(4), UInt32.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModLongUInt32OfZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).addMod(1, UInt32.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongUInt32Provider") + void addModLongUInt32(UInt32 v1, long v2, UInt32 m, UInt32 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongUInt32Provider() { + return Stream.of( + Arguments.of(v(0), 1, UInt32.valueOf(2), v(1)), + Arguments.of(v(1), 1, UInt32.valueOf(2), v(0)), + Arguments.of( + UInt32.MAX_VALUE.subtract(2), 1, UInt32.MAX_VALUE, UInt32.MAX_VALUE.subtract(1)), + Arguments.of(UInt32.MAX_VALUE.subtract(1), 1, UInt32.MAX_VALUE, v(0)), + Arguments.of(v(2), 1, UInt32.valueOf(2), v(1)), + Arguments.of(v(2), -1, UInt32.valueOf(2), v(1)), + Arguments.of(v(1), -7, UInt32.valueOf(5), v(4))); + } + + @Test + void shouldThrowForAddModUInt32UInt32OfZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).addMod(UInt32.ONE, UInt32.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongLongProvider") + void addModLongLong(UInt32 v1, long v2, long m, UInt32 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongLongProvider() { + return Stream.of( + Arguments.of(v(0), 1, 2, v(1)), + Arguments.of(v(1), 1, 2, v(0)), + Arguments.of(v(2), 1, 2, v(1))); + } + + @Test + void shouldThrowForAddModLongLongOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, 0)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForAddModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, -5)); + assertEquals("addMod unsigned with negative modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("subtractProvider") + void subtract(UInt32 v1, UInt32 v2, UInt32 expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractProvider() { + return Stream.of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(2), v(1), v(1)), + Arguments.of(v(100), v(100), v(0)), + Arguments.of(v(0), v(1), UInt32.MAX_VALUE), + Arguments.of(v(1), v(2), UInt32.MAX_VALUE), + Arguments.of(UInt32.MAX_VALUE, v(1), hv("0xFFFFFFFE"))); + } + + @ParameterizedTest + @MethodSource("subtractLongProvider") + void subtractLong(UInt32 v1, int v2, UInt32 expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractLongProvider() { + return Stream.of( + Arguments.of(v(1), 0, v(1)), + Arguments.of(v(5), 0, v(5)), + Arguments.of(v(2), 1, v(1)), + Arguments.of(v(100), 100, v(0)), + Arguments.of(v(0), 1, UInt32.MAX_VALUE), + Arguments.of(v(1), 2, UInt32.MAX_VALUE), + Arguments.of(UInt32.MAX_VALUE, 1, hv("0xFFFFFFFE")), + Arguments.of(v(0), -1, v(1)), + Arguments.of(v(0), -100, v(100)), + Arguments.of(v(2), -2, v(4))); + } + + @ParameterizedTest + @MethodSource("multiplyProvider") + void multiply(UInt32 v1, UInt32 v2, UInt32 expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyProvider() { + return Stream.of( + Arguments.of(v(1), v(1), v(1)), + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(2)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(3), v(2), v(6)), + Arguments.of(v(4), v(2), v(8)), + Arguments.of(v(10), v(18), v(180)), + Arguments.of(v(2), v(8), v(16)), + Arguments.of(v(7), v(8), v(56)), + Arguments.of(v(8), v(8), v(64)), + Arguments.of(v(17), v(8), v(136)), + Arguments.of(v(22), v(0), v(0))); + } + + @ParameterizedTest + @MethodSource("multiplyLongProvider") + void multiplyLong(UInt32 v1, int v2, UInt32 expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyLongProvider() { + return Stream.of( + Arguments.of(v(1), 1, v(1)), + Arguments.of(v(0), 2, v(0)), + Arguments.of(v(1), 2, v(2)), + Arguments.of(v(2), 2, v(4)), + Arguments.of(v(3), 2, v(6)), + Arguments.of(v(4), 2, v(8)), + Arguments.of(v(10), 18, v(180)), + Arguments.of(v(2), 8, v(16)), + Arguments.of(v(7), 8, v(56)), + Arguments.of(v(8), 8, v(64)), + Arguments.of(v(17), 8, v(136)), + Arguments.of(v(22), 0, v(0)), + Arguments.of(hv("0x0FFFFFFF"), 2, hv("0x1FFFFFFE")), + Arguments.of(hv("0xFFFFFFFF"), 2, hv("0xFFFFFFFE"))); + } + + @Test + void shouldThrowForMultiplyLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiply(-5)); + assertEquals("multiply unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModProvider") + void multiplyMod(UInt32 v1, UInt32 v2, UInt32 m, UInt32 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModProvider() { + return Stream.of( + Arguments.of(v(0), v(5), UInt32.valueOf(2), v(0)), + Arguments.of(v(2), v(3), UInt32.valueOf(7), v(6)), + Arguments.of(v(2), v(3), UInt32.valueOf(6), v(0)), + Arguments.of(v(2), v(0), UInt32.valueOf(6), v(0)), + Arguments.of(hv("0x0FFFFFFE"), v(2), UInt32.MAX_VALUE, hv("0x1FFFFFFC"))); + } + + @Test + void shouldThrowForMultiplyModOfModZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(v(1), UInt32.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongUInt32Provider") + void multiplyModLongUInt32(UInt32 v1, int v2, UInt32 m, UInt32 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongUInt32Provider() { + return Stream.of( + Arguments.of(v(0), 5, UInt32.valueOf(2), v(0)), + Arguments.of(v(2), 3, UInt32.valueOf(7), v(6)), + Arguments.of(v(2), 3, UInt32.valueOf(6), v(0)), + Arguments.of(v(2), 0, UInt32.valueOf(6), v(0)), + Arguments.of(hv("0x0FFFFFFE"), 2, UInt32.MAX_VALUE, hv("0x1FFFFFFC"))); + } + + @Test + void shouldThrowForMultiplyModLongUInt32OfModZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1, UInt32.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongUInt32OfNegative() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, UInt32.valueOf(2))); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongLongProvider") + void multiplyModLongLong(UInt32 v1, int v2, int m, UInt32 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongLongProvider() { + return Stream.of( + Arguments.of(v(0), 5, 2, v(0)), + Arguments.of(v(2), 3, 7, v(6)), + Arguments.of(v(2), 3, 6, v(0)), + Arguments.of(v(2), 0, 6, v(0)), + Arguments.of(hv("0x0FFFFFFE"), 2, Integer.MAX_VALUE, hv("0x1FFFFFFC"))); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1, 0)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiplyMod(5, -7)); + assertEquals("multiplyMod unsigned with negative modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, 2)); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideProvider") + void divide(UInt32 v1, UInt32 v2, UInt32 expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideProvider() { + return Stream.of( + Arguments.of(v(1), v(1), v(1)), + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(0)), + Arguments.of(v(2), v(2), v(1)), + Arguments.of(v(3), v(2), v(1)), + Arguments.of(v(4), v(2), v(2)), + Arguments.of(v(2), v(8), v(0)), + Arguments.of(v(7), v(8), v(0)), + Arguments.of(v(8), v(8), v(1)), + Arguments.of(v(9), v(8), v(1)), + Arguments.of(v(17), v(8), v(2)), + Arguments.of(v(1024), v(8), v(128)), + Arguments.of(v(1026), v(8), v(128))); + } + + @Test + void shouldThrowForDivideByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(v(0))); + assertEquals("divide by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideLongProvider") + void divideLong(UInt32 v1, int v2, UInt32 expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideLongProvider() { + return Stream.of( + Arguments.of(v(1), 1, v(1)), + Arguments.of(v(0), 2, v(0)), + Arguments.of(v(1), 2, v(0)), + Arguments.of(v(2), 2, v(1)), + Arguments.of(v(3), 2, v(1)), + Arguments.of(v(4), 2, v(2)), + Arguments.of(v(2), 8, v(0)), + Arguments.of(v(7), 8, v(0)), + Arguments.of(v(8), 8, v(1)), + Arguments.of(v(9), 8, v(1)), + Arguments.of(v(17), 8, v(2)), + Arguments.of(v(1024), 8, v(128)), + Arguments.of(v(1026), 8, v(128))); + } + + @Test + void shouldThrowForDivideLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(0)); + assertEquals("divide by zero", exception.getMessage()); + } + + @Test + void shouldThrowForDivideLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(-5)); + assertEquals("divide unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("powUInt32Provider") + void powUInt32(UInt32 v1, UInt32 v2, UInt32 expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powUInt32Provider() { + return Stream.of( + Arguments.of(v(0), UInt32.valueOf(2), v(0)), + Arguments.of(v(2), UInt32.valueOf(2), v(4)), + Arguments.of(v(2), UInt32.valueOf(8), v(256)), + Arguments.of(v(3), UInt32.valueOf(3), v(27)), + Arguments.of(hv("0xFFF0F0F0"), UInt32.valueOf(3), hv("0x19A2F000"))); + } + + @ParameterizedTest + @MethodSource("powLongProvider") + void powLong(UInt32 v1, long v2, UInt32 expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powLongProvider() { + return Stream.of( + Arguments.of(v(0), 2, v(0)), + Arguments.of(v(2), 2, v(4)), + Arguments.of(v(2), 8, v(256)), + Arguments.of(v(3), 3, v(27)), + Arguments.of(hv("0xFFF0F0F0"), 3, hv("0x19A2F000"))); + } + + @ParameterizedTest + @MethodSource("modLongProvider") + void modLong(UInt32 v1, int v2, UInt32 expected) { + assertValueEquals(expected, v1.mod(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream modLongProvider() { + return Stream.of( + Arguments.of(v(0), 2, v(0)), + Arguments.of(v(1), 2, v(1)), + Arguments.of(v(2), 2, v(0)), + Arguments.of(v(3), 2, v(1)), + Arguments.of(v(0), 8, v(0)), + Arguments.of(v(1), 8, v(1)), + Arguments.of(v(2), 8, v(2)), + Arguments.of(v(3), 8, v(3)), + Arguments.of(v(7), 8, v(7)), + Arguments.of(v(8), 8, v(0)), + Arguments.of(v(9), 8, v(1)), + Arguments.of(v(1024), 8, v(0)), + Arguments.of(v(1026), 8, v(2))); + } + + @Test + void shouldThrowForModLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(0)); + assertEquals("mod by zero", exception.getMessage()); + } + + @Test + void shouldThrowForModLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(-5)); + assertEquals("mod by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("andProvider") + void and(UInt32 v1, Bytes v2, UInt32 expected) { + assertValueEquals(expected, UInt32.fromBytes(v1.mutableCopy().and(v2))); + } + + @SuppressWarnings("UnusedMethod") + private static Stream andProvider() { + return Stream.of( + Arguments.of(hv("0x0000FFFF"), b("0xFFFF0000"), hv("0x00000000")), + Arguments.of(hv("0x0000FFFF"), b("0xFFFFFF00"), hv("0x0000FF00")), + Arguments.of(hv("0x0000FFFF"), hv("0xFFFF0000"), hv("0x00000000")), + Arguments.of(hv("0x0000FFFF"), hv("0xFFFFFF00"), hv("0x0000FF00"))); + } + + @ParameterizedTest + @MethodSource("orProvider") + void or(UInt32 v1, Bytes v2, UInt32 expected) { + assertValueEquals(expected, UInt32.fromBytes(v1.mutableCopy().or(v2))); + } + + @SuppressWarnings("UnusedMethod") + private static Stream orProvider() { + return Stream.of( + Arguments.of(hv("0x0000FFFF"), b("0xFFFF0000"), hv("0xFFFFFFFF")), + Arguments.of(hv("0x0000FFFF"), b("0xFFFF0000"), hv("0xFFFFFFFF")), + Arguments.of(hv("0x000000FF"), b("0xFFFF0000"), hv("0xFFFF00FF")), + Arguments.of(hv("0x0000FFFF"), hv("0xFFFF0000"), hv("0xFFFFFFFF")), + Arguments.of(hv("0x0000FFFF"), hv("0xFFFF0000"), hv("0xFFFFFFFF")), + Arguments.of(hv("0x000000FF"), hv("0xFFFF0000"), hv("0xFFFF00FF"))); + } + + @ParameterizedTest + @MethodSource("xorProvider") + void xor(UInt32 v1, Bytes v2, UInt32 expected) { + assertValueEquals(expected, UInt32.fromBytes(v1.mutableCopy().xor(v2))); + } + + @SuppressWarnings("UnusedMethod") + private static Stream xorProvider() { + return Stream.of( + Arguments.of(hv("0xFFFFFFFF"), b("0xFFFFFFFF"), hv("0x00000000")), + Arguments.of(hv("0x0000FFFF"), b("0xFFFF0000"), hv("0xFFFFFFFF")), + Arguments.of(hv("0x0000FFFF"), b("0xFFFFFF00"), hv("0xFFFF00FF")), + Arguments.of(hv("0xFFFFFFFF"), hv("0xFFFFFFFF"), hv("0x00000000")), + Arguments.of(hv("0x0000FFFF"), hv("0xFFFF0000"), hv("0xFFFFFFFF")), + Arguments.of(hv("0x0000FFFF"), hv("0xFFFFFF00"), hv("0xFFFF00FF"))); + } + + @ParameterizedTest + @MethodSource("notProvider") + void not(UInt32 value, UInt32 expected) { + assertValueEquals(expected, UInt32.fromBytes(value.mutableCopy().not())); + } + + @SuppressWarnings("UnusedMethod") + private static Stream notProvider() { + return Stream.of( + Arguments.of(hv("0xFFFFFFFF"), hv("0x00000000")), + Arguments.of(hv("0x00000000"), hv("0xFFFFFFFF")), + Arguments.of(hv("0x0000FFFF"), hv("0xFFFF0000"))); + } + + @ParameterizedTest + @MethodSource("shiftLeftProvider") + void shiftLeft(UInt32 value, int distance, UInt32 expected) { + assertValueEquals(expected, UInt32.fromBytes(value.mutableCopy().shiftLeft(distance))); + } + + @SuppressWarnings("UnusedMethod") + private static Stream shiftLeftProvider() { + return Stream.of( + Arguments.of(hv("0x01"), 0, hv("0x01")), + Arguments.of(hv("0x01"), 1, hv("0x02")), + Arguments.of(hv("0x01"), 2, hv("0x04")), + Arguments.of(hv("0x01"), 8, hv("0x0100")), + Arguments.of(hv("0x01"), 9, hv("0x0200")), + Arguments.of(hv("0x01"), 16, hv("0x10000")), + Arguments.of(hv("0x00FF00"), 4, hv("0x0FF000")), + Arguments.of(hv("0x00FF00"), 8, hv("0xFF0000")), + Arguments.of(hv("0x00FF00"), 1, hv("0x01FE00")), + Arguments.of(hv("0x00000001"), 16, hv("0x00010000")), + Arguments.of(hv("0x00000001"), 15, hv("0x00008000")), + Arguments.of(hv("0xFFFFFFFF"), 23, hv("0xFF800000")), + Arguments.of(hv("0x0000FFFF"), 18, hv("0xFFFC0000"))); + } + + @ParameterizedTest + @MethodSource("shiftRightProvider") + void shiftRight(UInt32 value, int distance, UInt32 expected) { + assertValueEquals(expected, UInt32.fromBytes(value.mutableCopy().shiftRight(distance))); + } + + @SuppressWarnings("UnusedMethod") + private static Stream shiftRightProvider() { + return Stream.of( + Arguments.of(hv("0x01"), 0, hv("0x01")), + Arguments.of(hv("0x01"), 1, hv("0x00")), + Arguments.of(hv("0x10"), 1, hv("0x08")), + Arguments.of(hv("0x10"), 2, hv("0x04")), + Arguments.of(hv("0x10"), 8, hv("0x00")), + Arguments.of(hv("0x1000"), 4, hv("0x0100")), + Arguments.of(hv("0x1000"), 5, hv("0x0080")), + Arguments.of(hv("0x1000"), 8, hv("0x0010")), + Arguments.of(hv("0x1000"), 9, hv("0x0008")), + Arguments.of(hv("0x1000"), 16, hv("0x0000")), + Arguments.of(hv("0x00FF00"), 4, hv("0x000FF0")), + Arguments.of(hv("0x00FF00"), 8, hv("0x0000FF")), + Arguments.of(hv("0x00FF00"), 1, hv("0x007F80")), + Arguments.of(hv("0x100000"), 16, hv("0x000010")), + Arguments.of(hv("0x100000"), 15, hv("0x000020")), + Arguments.of(hv("0xFFFFFFFF"), 23, hv("0x000001FF")), + Arguments.of(hv("0xFFFFFFFF"), 202, hv("0x00000000"))); + } + + @ParameterizedTest + @MethodSource("intValueProvider") + void intValue(UInt32 value, int expected) { + assertEquals(expected, value.toInt()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream intValueProvider() { + return Stream.of( + Arguments.of(hv("0x"), 0), + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x00000000"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x0001"), 1), + Arguments.of(hv("0x000001"), 1), + Arguments.of(hv("0x00000001"), 1), + Arguments.of(hv("0x0100"), 256), + Arguments.of(hv("0x000100"), 256), + Arguments.of(hv("0x00000100"), 256)); + } + + @ParameterizedTest + @MethodSource("longValueProvider") + void longValue(UInt32 value, long expected) { + assertEquals(expected, value.toLong()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream longValueProvider() { + return Stream.of( + Arguments.of(hv("0x"), 0L), + Arguments.of(hv("0x00"), 0L), + Arguments.of(hv("0x00000000"), 0L), + Arguments.of(hv("0x01"), 1L), + Arguments.of(hv("0x0001"), 1L), + Arguments.of(hv("0x000001"), 1L), + Arguments.of(hv("0x0100"), 256L), + Arguments.of(hv("0x000100"), 256L), + Arguments.of(hv("0x00000100"), 256L), + Arguments.of(hv("0xFFFFFFFF"), (1L << 32) - 1)); + } + + @ParameterizedTest + @MethodSource("compareToProvider") + void compareTo(UInt32 v1, UInt32 v2, int expected) { + assertEquals(expected, v1.compareTo(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream compareToProvider() { + return Stream.of( + Arguments.of(v(5), v(5), 0), + Arguments.of(v(5), v(3), 1), + Arguments.of(v(5), v(6), -1), + Arguments.of(hv("0x00000000"), hv("0x00000000"), 0), + Arguments.of(hv("0xFFFFFFFF"), hv("0xFFFFFFFF"), 0), + Arguments.of(hv("0x0000FFFF"), hv("0x0000FFFF"), 0), + Arguments.of(hv("0xFFFFFFFF"), hv("0x00000000"), 1), + Arguments.of(hv("0x00000000"), hv("0xFFFFFFFF"), -1), + Arguments.of(hv("0x0001FFFF"), hv("0x0000FFFF"), 1), + Arguments.of(hv("0x0000FFFE"), hv("0x0000FFFF"), -1)); + } + + @ParameterizedTest + @MethodSource("toBytesProvider") + void toBytesTest(UInt32 value, Bytes expected) { + assertEquals(expected, value.toBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toBytesProvider() { + return Stream.of( + Arguments.of(hv("0x00"), Bytes.fromHexString("0x00000000")), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000")), + Arguments.of(hv("0xf10000ab"), Bytes.fromHexString("0xF10000AB"))); + } + + @ParameterizedTest + @MethodSource("toMinimalBytesProvider") + void toMinimalBytesTest(UInt32 value, Bytes expected) { + assertEquals(expected, value.toMinimalBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toMinimalBytesProvider() { + return Stream.of( + Arguments.of(hv("0x00"), Bytes.EMPTY), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000")), + Arguments.of(hv("0xf10000ab"), Bytes.fromHexString("0xf10000ab")), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000"))); + } + + @ParameterizedTest + @MethodSource("numberOfLeadingZerosProvider") + void numberOfLeadingZeros(UInt32 value, int expected) { + assertEquals(expected, value.numberOfLeadingZeros()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream numberOfLeadingZerosProvider() { + return Stream.of( + Arguments.of(hv("0x00"), 32), + Arguments.of(hv("0x01"), 31), + Arguments.of(hv("0x02"), 30), + Arguments.of(hv("0x03"), 30), + Arguments.of(hv("0x0F"), 28), + Arguments.of(hv("0x8F"), 24), + Arguments.of(hv("0x1000000"), 7)); + } + + @ParameterizedTest + @MethodSource("bitLengthProvider") + void bitLength(UInt32 value, int expected) { + assertEquals(expected, value.bitLength()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream bitLengthProvider() { + return Stream.of( + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x02"), 2), + Arguments.of(hv("0x03"), 2), + Arguments.of(hv("0x0F"), 4), + Arguments.of(hv("0x8F"), 8), + Arguments.of(hv("0x10000000"), 29)); + } + + @ParameterizedTest + @MethodSource("addExactProvider") + void addExact(UInt32 value, UInt32 operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactProvider() { + return Stream.of( + Arguments.of(UInt32.MAX_VALUE, v(1)), Arguments.of(UInt32.MAX_VALUE, UInt32.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addExactLongProvider") + void addExactLong(UInt32 value, int operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactLongProvider() { + return Stream.of( + Arguments.of(UInt32.MAX_VALUE, 3), + Arguments.of(UInt32.MAX_VALUE, Integer.MAX_VALUE), + Arguments.of(v(0), -1)); + } + + @ParameterizedTest + @MethodSource("subtractExactProvider") + void subtractExact(UInt32 value, UInt32 operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactProvider() { + return Stream.of(Arguments.of(v(0), v(1)), Arguments.of(v(0), UInt32.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("subtractExactLongProvider") + void subtractExactLong(UInt32 value, int operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactLongProvider() { + return Stream.of( + Arguments.of(v(0), 1), + Arguments.of(v(0), Integer.MAX_VALUE), + Arguments.of(UInt32.MAX_VALUE, -1)); + } + + private void assertValueEquals(UInt32 expected, UInt32 actual) { + String msg = + String.format("Expected %s but got %s", expected.toHexString(), actual.toHexString()); + assertEquals(expected, actual, msg); + } + + @Test + void testToUInt32() { + UInt32 value = UInt32.valueOf(42); + assertSame(value, value.toUInt32()); + } + + @Test + void toIntTooLarge() { + assertThrows(ArithmeticException.class, () -> UInt32.MAX_VALUE.toBigInteger().intValueExact()); + } + + @Test + void toLongTooLarge() { + assertEquals(4294967295L, UInt32.MAX_VALUE.toLong()); + } + + @Test + void testToDecimalString() { + assertEquals("3456", UInt32.valueOf(3456).toDecimalString()); + } +} diff --git a/units/src/test/java/org/apache/tuweni/v2/units/bigints/UInt384Test.java b/units/src/test/java/org/apache/tuweni/v2/units/bigints/UInt384Test.java new file mode 100644 index 000000000..918c20360 --- /dev/null +++ b/units/src/test/java/org/apache/tuweni/v2/units/bigints/UInt384Test.java @@ -0,0 +1,1144 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.units.bigints; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.tuweni.v2.bytes.Bytes; +import org.apache.tuweni.v2.bytes.Bytes48; + +import java.math.BigInteger; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class UInt384Test { + + private static UInt384 v(long v) { + return UInt384.valueOf(v); + } + + private static UInt384 biv(String s) { + return UInt384.valueOf(new BigInteger(s)); + } + + private static UInt384 hv(String s) { + return UInt384.fromHexString(s); + } + + @Test + void valueOfLong() { + assertThrows(IllegalArgumentException.class, () -> UInt384.valueOf(-1)); + assertThrows(IllegalArgumentException.class, () -> UInt384.valueOf(Long.MIN_VALUE)); + assertThrows(IllegalArgumentException.class, () -> UInt384.valueOf(~0L)); + } + + @Test + void valueOfBigInteger() { + assertThrows(IllegalArgumentException.class, () -> UInt384.valueOf(BigInteger.valueOf(-1))); + assertThrows( + IllegalArgumentException.class, () -> UInt384.valueOf(BigInteger.valueOf(2).pow(384))); + } + + @ParameterizedTest + @MethodSource("addProvider") + void add(UInt384 v1, UInt384 v2, UInt384 expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addProvider() { + return Stream.of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(0), v(1), v(1)), + Arguments.of(v(0), v(100), v(100)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(100), v(90), v(190)), + Arguments.of(biv("9223372036854775807"), v(1), biv("9223372036854775808")), + Arguments.of( + biv("13492324908428420834234908342"), v(10), biv("13492324908428420834234908352")), + Arguments.of( + biv("13492324908428420834234908342"), + v(23422141424214L), + biv("13492324908428444256376332556")), + Arguments.of(UInt384.MAX_VALUE, v(1), v(0)), + Arguments.of(UInt384.MAX_VALUE, v(2), v(1)), + Arguments.of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), + v(1), + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), + Arguments.of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + v(1), + UInt384.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addLongProvider") + void addLong(UInt384 v1, long v2, UInt384 expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addLongProvider() { + return Stream.of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(0), 1L, v(1)), + Arguments.of(v(0), 100L, v(100)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(100), 90L, v(190)), + Arguments.of( + biv("13492324908428420834234908342"), 10L, biv("13492324908428420834234908352")), + Arguments.of( + biv("13492324908428420834234908342"), + 23422141424214L, + biv("13492324908428444256376332556")), + Arguments.of(UInt384.MAX_VALUE, 1L, v(0)), + Arguments.of(UInt384.MAX_VALUE, 2L, v(1)), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), + 1L, + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), + Arguments.of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + 1L, + UInt384.MAX_VALUE), + Arguments.of(v(10), -5L, v(5)), + Arguments.of(v(0), -1L, UInt384.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addModProvider") + void addMod(UInt384 v1, UInt384 v2, UInt384 m, UInt384 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModProvider() { + return Stream.of( + Arguments.of(v(0), v(1), UInt384.valueOf(2), v(1)), + Arguments.of(v(1), v(1), UInt384.valueOf(2), v(0)), + Arguments.of( + UInt384.MAX_VALUE.subtract(2), v(1), UInt384.MAX_VALUE, UInt384.MAX_VALUE.subtract(1)), + Arguments.of(UInt384.MAX_VALUE.subtract(1), v(1), UInt384.MAX_VALUE, v(0)), + Arguments.of(v(2), v(1), UInt384.valueOf(2), v(1)), + Arguments.of(v(3), v(2), UInt384.valueOf(6), v(5)), + Arguments.of(v(3), v(4), UInt384.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModOfZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).addMod(v(1), UInt384.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModUInt384UInt384Provider") + void addModUInt384UInt384(UInt384 v1, UInt384 v2, UInt384 m, UInt384 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModUInt384UInt384Provider() { + return Stream.of( + Arguments.of(v(0), UInt384.ONE, UInt384.valueOf(2), v(1)), + Arguments.of(v(1), UInt384.ONE, UInt384.valueOf(2), v(0)), + Arguments.of( + UInt384.MAX_VALUE.subtract(2), + UInt384.ONE, + UInt384.MAX_VALUE, + UInt384.MAX_VALUE.subtract(1)), + Arguments.of(UInt384.MAX_VALUE.subtract(1), UInt384.ONE, UInt384.MAX_VALUE, v(0)), + Arguments.of(v(2), UInt384.ONE, UInt384.valueOf(2), v(1)), + Arguments.of(v(3), UInt384.valueOf(2), UInt384.valueOf(6), v(5)), + Arguments.of(v(3), UInt384.valueOf(4), UInt384.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModLongUInt384OfZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).addMod(1, UInt384.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongUInt384Provider") + void addModLongUInt384(UInt384 v1, long v2, UInt384 m, UInt384 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongUInt384Provider() { + return Stream.of( + Arguments.of(v(0), 1L, UInt384.valueOf(2), v(1)), + Arguments.of(v(1), 1L, UInt384.valueOf(2), v(0)), + Arguments.of( + UInt384.MAX_VALUE.subtract(2), 1L, UInt384.MAX_VALUE, UInt384.MAX_VALUE.subtract(1)), + Arguments.of(UInt384.MAX_VALUE.subtract(1), 1L, UInt384.MAX_VALUE, v(0)), + Arguments.of(v(2), 1L, UInt384.valueOf(2), v(1)), + Arguments.of(v(2), -1L, UInt384.valueOf(2), v(1)), + Arguments.of(v(1), -7L, UInt384.valueOf(5), v(4))); + } + + @Test + void shouldThrowForAddModUInt384UInt384OfZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).addMod(UInt384.ONE, UInt384.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongLongProvider") + void addModLongLong(UInt384 v1, long v2, long m, UInt384 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongLongProvider() { + return Stream.of( + Arguments.of(v(0), 1L, 2L, v(1)), + Arguments.of(v(1), 1L, 2L, v(0)), + Arguments.of(v(2), 1L, 2L, v(1))); + } + + @Test + void shouldThrowForAddModLongLongOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, 0)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForAddModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, -5)); + assertEquals("addMod unsigned with negative modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("subtractProvider") + void subtract(UInt384 v1, UInt384 v2, UInt384 expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractProvider() { + return Stream.of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(2), v(1), v(1)), + Arguments.of(v(100), v(100), v(0)), + Arguments.of( + biv("13492324908428420834234908342"), v(10), biv("13492324908428420834234908332")), + Arguments.of( + biv("13492324908428420834234908342"), + v(23422141424214L), + biv("13492324908428397412093484128")), + Arguments.of(v(0), v(1), UInt384.MAX_VALUE), + Arguments.of(v(1), v(2), UInt384.MAX_VALUE), + Arguments.of( + UInt384.MAX_VALUE, + v(1), + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); + } + + @ParameterizedTest + @MethodSource("subtractLongProvider") + void subtractLong(UInt384 v1, long v2, UInt384 expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractLongProvider() { + return Stream.of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(2), 1L, v(1)), + Arguments.of(v(100), 100L, v(0)), + Arguments.of( + biv("13492324908428420834234908342"), 10L, biv("13492324908428420834234908332")), + Arguments.of( + biv("13492324908428420834234908342"), + 23422141424214L, + biv("13492324908428397412093484128")), + Arguments.of(v(0), 1L, UInt384.MAX_VALUE), + Arguments.of(v(1), 2L, UInt384.MAX_VALUE), + Arguments.of( + UInt384.MAX_VALUE, + 1L, + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), + Arguments.of(v(0), -1L, v(1)), + Arguments.of(v(0), -100L, v(100)), + Arguments.of(v(2), -2L, v(4))); + } + + @ParameterizedTest + @MethodSource("multiplyProvider") + void multiply(UInt384 v1, UInt384 v2, UInt384 expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyProvider() { + return Stream.of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(2)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(3), v(2), v(6)), + Arguments.of(v(4), v(2), v(8)), + Arguments.of(v(10), v(18), v(180)), + Arguments.of( + biv("13492324908428420834234908341"), v(2), biv("26984649816856841668469816682")), + Arguments.of( + biv("13492324908428420834234908342"), v(2), biv("26984649816856841668469816684")), + Arguments.of(v(2), v(8), v(16)), + Arguments.of(v(7), v(8), v(56)), + Arguments.of(v(8), v(8), v(64)), + Arguments.of(v(17), v(8), v(136)), + Arguments.of( + biv("13492324908428420834234908342"), v(8), biv("107938599267427366673879266736")), + Arguments.of( + biv("13492324908428420834234908342"), v(2048), biv("27632281412461405868513092284416")), + Arguments.of( + biv("13492324908428420834234908342"), + v(131072), + biv("1768466010397529975584837906202624")), + Arguments.of(v(22), v(0), v(0))); + } + + @ParameterizedTest + @MethodSource("multiplyLongProvider") + void multiplyLong(UInt384 v1, long v2, UInt384 expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyLongProvider() { + return Stream.of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(2)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(3), 2L, v(6)), + Arguments.of(v(4), 2L, v(8)), + Arguments.of(v(10), 18L, v(180)), + Arguments.of( + biv("13492324908428420834234908341"), 2L, biv("26984649816856841668469816682")), + Arguments.of( + biv("13492324908428420834234908342"), 2L, biv("26984649816856841668469816684")), + Arguments.of(v(2), 8L, v(16)), + Arguments.of(v(7), 8L, v(56)), + Arguments.of(v(8), 8L, v(64)), + Arguments.of(v(17), 8L, v(136)), + Arguments.of( + biv("13492324908428420834234908342"), 8L, biv("107938599267427366673879266736")), + Arguments.of( + biv("13492324908428420834234908342"), 2048L, biv("27632281412461405868513092284416")), + Arguments.of( + biv("13492324908428420834234908342"), + 131072L, + biv("1768466010397529975584837906202624")), + Arguments.of(v(22), 0L, v(0)), + Arguments.of( + hv( + "0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 2L, + hv( + "0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), + Arguments.of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 2L, + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); + } + + @Test + void shouldThrowForMultiplyLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiply(-5)); + assertEquals("multiply unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModProvider") + void multiplyMod(UInt384 v1, UInt384 v2, UInt384 m, UInt384 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModProvider() { + return Stream.of( + Arguments.of(v(0), v(5), UInt384.valueOf(2), v(0)), + Arguments.of(v(2), v(3), UInt384.valueOf(7), v(6)), + Arguments.of(v(2), v(3), UInt384.valueOf(6), v(0)), + Arguments.of(v(2), v(0), UInt384.valueOf(6), v(0)), + Arguments.of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + v(2), + UInt384.MAX_VALUE, + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModOfModZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(v(1), UInt384.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongUInt384Provider") + void multiplyModLongUInt384(UInt384 v1, long v2, UInt384 m, UInt384 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongUInt384Provider() { + return Stream.of( + Arguments.of(v(0), 5L, UInt384.valueOf(2), v(0)), + Arguments.of(v(2), 3L, UInt384.valueOf(7), v(6)), + Arguments.of(v(2), 3L, UInt384.valueOf(6), v(0)), + Arguments.of(v(2), 0L, UInt384.valueOf(6), v(0)), + Arguments.of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + 2L, + UInt384.MAX_VALUE, + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); + } + + @Test + void shouldThrowForMultiplyModLongUInt384OfModZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1L, UInt384.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongUInt384OfNegative() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, UInt384.valueOf(2))); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongLongProvider") + void multiplyModLongLong(UInt384 v1, long v2, long m, UInt384 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongLongProvider() { + return Stream.of( + Arguments.of(v(0), 5L, 2L, v(0)), + Arguments.of(v(2), 3L, 7L, v(6)), + Arguments.of(v(2), 3L, 6L, v(0)), + Arguments.of(v(2), 0L, 6L, v(0)), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + 2L, + Long.MAX_VALUE, + hv("0x000000000000000000000000000000000000000000000000000000000000001C"))); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1, 0)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiplyMod(5, -7)); + assertEquals("multiplyMod unsigned with negative modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, 2)); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideProvider") + void divide(UInt384 v1, UInt384 v2, UInt384 expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideProvider() { + return Stream.of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(0)), + Arguments.of(v(2), v(2), v(1)), + Arguments.of(v(3), v(2), v(1)), + Arguments.of(v(4), v(2), v(2)), + Arguments.of( + biv("13492324908428420834234908341"), v(2), biv("6746162454214210417117454170")), + Arguments.of( + biv("13492324908428420834234908342"), v(2), biv("6746162454214210417117454171")), + Arguments.of( + biv("13492324908428420834234908343"), v(2), biv("6746162454214210417117454171")), + Arguments.of(v(2), v(8), v(0)), + Arguments.of(v(7), v(8), v(0)), + Arguments.of(v(8), v(8), v(1)), + Arguments.of(v(9), v(8), v(1)), + Arguments.of(v(17), v(8), v(2)), + Arguments.of(v(1024), v(8), v(128)), + Arguments.of(v(1026), v(8), v(128)), + Arguments.of( + biv("13492324908428420834234908342"), v(8), biv("1686540613553552604279363542")), + Arguments.of( + biv("13492324908428420834234908342"), v(2048), biv("6588049271693564860466263")), + Arguments.of( + biv("13492324908428420834234908342"), v(131072), biv("102938269870211950944785"))); + } + + @Test + void shouldThrowForDivideByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(v(0))); + assertEquals("divide by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideLongProvider") + void divideLong(UInt384 v1, long v2, UInt384 expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideLongProvider() { + return Stream.of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(0)), + Arguments.of(v(2), 2L, v(1)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(v(4), 2L, v(2)), + Arguments.of(biv("13492324908428420834234908341"), 2L, biv("6746162454214210417117454170")), + Arguments.of(biv("13492324908428420834234908342"), 2L, biv("6746162454214210417117454171")), + Arguments.of(biv("13492324908428420834234908343"), 2L, biv("6746162454214210417117454171")), + Arguments.of(v(2), 8L, v(0)), + Arguments.of(v(7), 8L, v(0)), + Arguments.of(v(8), 8L, v(1)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(17), 8L, v(2)), + Arguments.of(v(1024), 8L, v(128)), + Arguments.of(v(1026), 8L, v(128)), + Arguments.of(biv("13492324908428420834234908342"), 8L, biv("1686540613553552604279363542")), + Arguments.of(biv("13492324908428420834234908342"), 2048L, biv("6588049271693564860466263")), + Arguments.of( + biv("13492324908428420834234908342"), 131072L, biv("102938269870211950944785"))); + } + + @Test + void shouldThrowForDivideLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(0)); + assertEquals("divide by zero", exception.getMessage()); + } + + @Test + void shouldThrowForDivideLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(-5)); + assertEquals("divide unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("powUInt384Provider") + void powUInt384(UInt384 v1, UInt384 v2, UInt384 expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powUInt384Provider() { + return Stream.of( + Arguments.of(v(0), UInt384.valueOf(2), v(0)), + Arguments.of(v(2), UInt384.valueOf(2), v(4)), + Arguments.of(v(2), UInt384.valueOf(8), v(256)), + Arguments.of(v(3), UInt384.valueOf(3), v(27)), + Arguments.of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0F0"), + UInt384.valueOf(3), + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A920E119A2F000"))); + } + + @ParameterizedTest + @MethodSource("powLongProvider") + void powLong(UInt384 v1, long v2, UInt384 expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powLongProvider() { + return Stream.of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(2), 8L, v(256)), + Arguments.of(v(3), 3L, v(27)), + Arguments.of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0F0"), + 3L, + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A920E119A2F000")), + Arguments.of( + v(3), + -3L, + hv( + "0x4BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA13"))); + } + + @ParameterizedTest + @MethodSource("modLongProvider") + void modLong(UInt384 v1, long v2, UInt384 expected) { + assertValueEquals(expected, v1.mod(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream modLongProvider() { + return Stream.of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(1)), + Arguments.of(v(2), 2L, v(0)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(biv("13492324908428420834234908342"), 2L, v(0)), + Arguments.of(biv("13492324908428420834234908343"), 2L, v(1)), + Arguments.of(v(0), 8L, v(0)), + Arguments.of(v(1), 8L, v(1)), + Arguments.of(v(2), 8L, v(2)), + Arguments.of(v(3), 8L, v(3)), + Arguments.of(v(7), 8L, v(7)), + Arguments.of(v(8), 8L, v(0)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(1024), 8L, v(0)), + Arguments.of(v(1026), 8L, v(2)), + Arguments.of(biv("13492324908428420834234908342"), 8L, v(6)), + Arguments.of(biv("13492324908428420834234908343"), 8L, v(7)), + Arguments.of(biv("13492324908428420834234908344"), 8L, v(0))); + } + + @Test + void shouldThrowForModLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(0)); + assertEquals("mod by zero", exception.getMessage()); + } + + @Test + void shouldThrowForModLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(-5)); + assertEquals("mod by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("andProvider") + void and(UInt384 v1, UInt384 v2, UInt384 expected) { + assertValueEquals(expected, v1.and(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream andProvider() { + return Stream.of( + Arguments.of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000")), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0x000000000000000000000000000000FF00000000000000000000000000000000"))); + } + + @ParameterizedTest + @MethodSource("andProviderBytes48") + void andBytes48(UInt384 v1, Bytes48 v2, UInt384 expected) { + assertValueEquals(expected, v1.and(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream andProviderBytes48() { + return Stream.of( + Arguments.of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + Bytes48.wrapHexString( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000")), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + Bytes48.wrapHexString( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0x000000000000000000000000000000FF00000000000000000000000000000000"))); + } + + @ParameterizedTest + @MethodSource("orProvider") + void or(UInt384 v1, UInt384 v2, UInt384 expected) { + assertValueEquals(expected, v1.or(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream orProvider() { + return Stream.of( + Arguments.of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments.of( + hv("0x0000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))); + } + + @ParameterizedTest + @MethodSource("orProviderBytes48") + void orBytes48(UInt384 v1, Bytes48 v2, UInt384 expected) { + assertValueEquals(expected, v1.or(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream orProviderBytes48() { + return Stream.of( + Arguments.of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + Bytes48.wrapHexString( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + Bytes48.wrapHexString( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments.of( + hv("0x0000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + Bytes48.wrapHexString( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))); + } + + @ParameterizedTest + @MethodSource("xorProvider") + void xor(UInt384 v1, UInt384 v2, UInt384 expected) { + assertValueEquals(expected, v1.xor(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream xorProvider() { + return Stream.of( + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000")), + Arguments.of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))); + } + + @ParameterizedTest + @MethodSource("xorProviderBytes48") + void xorBytes48(UInt384 v1, Bytes48 v2, UInt384 expected) { + assertValueEquals(expected, v1.xor(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream xorProviderBytes48() { + return Stream.of( + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + Bytes48.wrapHexString( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000")), + Arguments.of( + hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + Bytes48.wrapHexString( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + Bytes48.wrapHexString( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))); + } + + @ParameterizedTest + @MethodSource("notProvider") + void not(UInt384 value, UInt384 expected) { + assertValueEquals(expected, value.not()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream notProvider() { + return Stream.of( + Arguments.of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")), + Arguments.of( + hv( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments.of( + hv( + "0x00000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000"))); + } + + @ParameterizedTest + @MethodSource("shiftLeftProvider") + void shiftLeft(UInt384 value, int distance, UInt384 expected) { + assertValueEquals(expected, value.shiftLeft(distance)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream shiftLeftProvider() { + return Stream.of( + Arguments.of(hv("0x01"), 1, hv("0x02")), + Arguments.of(hv("0x01"), 2, hv("0x04")), + Arguments.of(hv("0x01"), 8, hv("0x0100")), + Arguments.of(hv("0x01"), 9, hv("0x0200")), + Arguments.of(hv("0x01"), 16, hv("0x10000")), + Arguments.of(hv("0x00FF00"), 4, hv("0x0FF000")), + Arguments.of(hv("0x00FF00"), 8, hv("0xFF0000")), + Arguments.of(hv("0x00FF00"), 1, hv("0x01FE00")), + Arguments.of( + hv( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), + 16, + hv( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000")), + Arguments.of( + hv( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), + 15, + hv( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000")), + Arguments.of( + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 55, + hv( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000000000000")), + Arguments.of( + hv( + "0x00000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 330, + hv( + "0xFFFFFFFFFFFFFC0000000000000000000000000000000000000000000000000000000000000000000000000000000000"))); + } + + @ParameterizedTest + @MethodSource("shiftRightProvider") + void shiftRight(UInt384 value, int distance, UInt384 expected) { + assertValueEquals(expected, value.shiftRight(distance)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream shiftRightProvider() { + return Stream.of( + Arguments.of(hv("0x01"), 1, hv("0x00")), + Arguments.of(hv("0x10"), 1, hv("0x08")), + Arguments.of(hv("0x10"), 2, hv("0x04")), + Arguments.of(hv("0x10"), 8, hv("0x00")), + Arguments.of(hv("0x1000"), 4, hv("0x0100")), + Arguments.of(hv("0x1000"), 5, hv("0x0080")), + Arguments.of(hv("0x1000"), 8, hv("0x0010")), + Arguments.of(hv("0x1000"), 9, hv("0x0008")), + Arguments.of(hv("0x1000"), 16, hv("0x0000")), + Arguments.of(hv("0x00FF00"), 4, hv("0x000FF0")), + Arguments.of(hv("0x00FF00"), 8, hv("0x0000FF")), + Arguments.of(hv("0x00FF00"), 1, hv("0x007F80")), + Arguments.of( + hv("0x1000000000000000000000000000000000000000000000000000000000000000"), + 16, + hv("0x0000100000000000000000000000000000000000000000000000000000000000")), + Arguments.of( + hv("0x1000000000000000000000000000000000000000000000000000000000000000"), + 15, + hv("0x0000200000000000000000000000000000000000000000000000000000000000")), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 55, + hv("0x00000000000001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000000000"), + 202, + hv("0x000000000000000000000000000000000000000000000000003FFFFFFFFFFFFF"))); + } + + @ParameterizedTest + @MethodSource("intValueProvider") + void intValue(UInt384 value, int expected) { + assertEquals(expected, value.intValue()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream intValueProvider() { + return Stream.of( + Arguments.of(hv("0x"), 0), + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x00000000"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x0001"), 1), + Arguments.of(hv("0x000001"), 1), + Arguments.of(hv("0x00000001"), 1), + Arguments.of(hv("0x0100"), 256), + Arguments.of(hv("0x000100"), 256), + Arguments.of(hv("0x00000100"), 256)); + } + + @Test + void shouldThrowForIntValueOfOversizeValue() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> hv("0x0100000000").intValue()); + assertEquals("Value does not fit a 4 byte int", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("longValueProvider") + void longValue(UInt384 value, long expected) { + assertEquals(expected, value.toLong()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream longValueProvider() { + return Stream.of( + Arguments.of(hv("0x"), 0L), + Arguments.of(hv("0x00"), 0L), + Arguments.of(hv("0x00000000"), 0L), + Arguments.of(hv("0x01"), 1L), + Arguments.of(hv("0x0001"), 1L), + Arguments.of(hv("0x000001"), 1L), + Arguments.of(hv("0x00000001"), 1L), + Arguments.of(hv("0x0000000001"), 1L), + Arguments.of(hv("0x000000000001"), 1L), + Arguments.of(hv("0x0100"), 256L), + Arguments.of(hv("0x000100"), 256L), + Arguments.of(hv("0x00000100"), 256L), + Arguments.of(hv("0x00000100"), 256L), + Arguments.of(hv("0x000000000100"), 256L), + Arguments.of(hv("0x00000000000100"), 256L), + Arguments.of(hv("0x0000000000000100"), 256L), + Arguments.of(hv("0xFFFFFFFF"), (1L << 32) - 1)); + } + + @Test + void shouldThrowForLongValueOfOversizeValue() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> hv("0x010000000000000000").toLong()); + assertEquals("Value does not fit a 8 byte long", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("compareToProvider") + void compareTo(UInt384 v1, UInt384 v2, int expected) { + assertEquals(expected, v1.compareTo(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream compareToProvider() { + return Stream.of( + Arguments.of(v(5), v(5), 0), + Arguments.of(v(5), v(3), 1), + Arguments.of(v(5), v(6), -1), + Arguments.of( + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + 0), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 0), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 0), + Arguments.of( + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + 1), + Arguments.of( + hv("0x0000000000000000000000000000000000000000000000000000000000000000"), + hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + -1), + Arguments.of( + hv("0x000000000000000000000000000001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + 1), + Arguments.of( + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), + hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + -1)); + } + + @ParameterizedTest + @MethodSource("toBytesProvider") + void toBytesTest(UInt384 value, Bytes expected) { + assertEquals(expected, value); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toBytesProvider() { + return Stream.of( + Arguments.of( + hv("0x00"), + Bytes.fromHexString( + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")), + Arguments.of( + hv("0x01000000"), + Bytes.fromHexString( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000")), + Arguments.of( + hv("0x0100000000"), + Bytes.fromHexString( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000")), + Arguments.of( + hv("0xf100000000ab"), + Bytes.fromHexString( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000f100000000ab")), + Arguments.of( + hv("0x0400000000000000000000000000000000000000000000000000f100000000ab"), + Bytes.fromHexString( + "0x000000000000000000000000000000000400000000000000000000000000000000000000000000000000f100000000ab"))); + } + + @ParameterizedTest + @MethodSource("toMinimalBytesProvider") + void toMinimalBytesTest(UInt384 value, Bytes expected) { + assertEquals(expected, value.toMinimalBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toMinimalBytesProvider() { + return Stream.of( + Arguments.of(hv("0x00"), Bytes.EMPTY), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000")), + Arguments.of(hv("0x0100000000"), Bytes.fromHexString("0x0100000000")), + Arguments.of(hv("0xf100000000ab"), Bytes.fromHexString("0xf100000000ab")), + Arguments.of( + hv("0x0400000000000000000000000000000000000000000000000000f100000000ab"), + Bytes.fromHexString( + "0x0400000000000000000000000000000000000000000000000000f100000000ab"))); + } + + @ParameterizedTest + @MethodSource("numberOfLeadingZerosProvider") + void numberOfLeadingZeros(UInt384 value, int expected) { + assertEquals(expected, value.numberOfLeadingZeros()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream numberOfLeadingZerosProvider() { + return Stream.of( + Arguments.of(hv("0x00"), 384), + Arguments.of(hv("0x01"), 383), + Arguments.of(hv("0x02"), 382), + Arguments.of(hv("0x03"), 382), + Arguments.of(hv("0x0F"), 380), + Arguments.of(hv("0x8F"), 376), + Arguments.of(hv("0x100000000"), 351)); + } + + @ParameterizedTest + @MethodSource("bitLengthProvider") + void bitLength(UInt384 value, int expected) { + assertEquals(expected, value.bitLength()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream bitLengthProvider() { + return Stream.of( + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x02"), 2), + Arguments.of(hv("0x03"), 2), + Arguments.of(hv("0x0F"), 4), + Arguments.of(hv("0x8F"), 8), + Arguments.of(hv("0x100000000"), 33)); + } + + @ParameterizedTest + @MethodSource("addExactProvider") + void addExact(UInt384 value, UInt384 operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactProvider() { + return Stream.of( + Arguments.of(UInt384.MAX_VALUE, v(1)), Arguments.of(UInt384.MAX_VALUE, UInt384.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addExactLongProvider") + void addExactLong(UInt384 value, long operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactLongProvider() { + return Stream.of( + Arguments.of(UInt384.MAX_VALUE, 3), + Arguments.of(UInt384.MAX_VALUE, Long.MAX_VALUE), + Arguments.of(v(0), -1)); + } + + @ParameterizedTest + @MethodSource("subtractExactProvider") + void subtractExact(UInt384 value, UInt384 operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactProvider() { + return Stream.of(Arguments.of(v(0), v(1)), Arguments.of(v(0), UInt384.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("subtractExactLongProvider") + void subtractExactLong(UInt384 value, long operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactLongProvider() { + return Stream.of( + Arguments.of(v(0), 1), + Arguments.of(v(0), Long.MAX_VALUE), + Arguments.of(UInt384.MAX_VALUE, -1)); + } + + private void assertValueEquals(UInt384 expected, UInt384 actual) { + String msg = + String.format("Expected %s but got %s", expected.toHexString(), actual.toHexString()); + assertEquals(expected, actual, msg); + } + + @Test + void testToDecimalString() { + assertEquals("3456", UInt384.valueOf(3456).toDecimalString()); + } +} diff --git a/units/src/test/java/org/apache/tuweni/v2/units/bigints/UInt64Test.java b/units/src/test/java/org/apache/tuweni/v2/units/bigints/UInt64Test.java new file mode 100644 index 000000000..798659a46 --- /dev/null +++ b/units/src/test/java/org/apache/tuweni/v2/units/bigints/UInt64Test.java @@ -0,0 +1,820 @@ +// Copyright The Tuweni Authors +// SPDX-License-Identifier: Apache-2.0 +package org.apache.tuweni.v2.units.bigints; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.tuweni.v2.bytes.Bytes; + +import java.math.BigInteger; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class UInt64Test { + + private static UInt64 v(long v) { + return UInt64.valueOf(v); + } + + private static UInt64 hv(String s) { + return UInt64.fromHexString(s); + } + + private static Bytes b(String s) { + return Bytes.fromHexString(s); + } + + @Test + void valueOfLong() { + assertThrows(IllegalArgumentException.class, () -> UInt64.valueOf(-1)); + assertThrows(IllegalArgumentException.class, () -> UInt64.valueOf(Long.MIN_VALUE)); + assertThrows(IllegalArgumentException.class, () -> UInt64.valueOf(~0L)); + } + + @Test + void valueOfBigInteger() { + assertThrows(IllegalArgumentException.class, () -> UInt64.valueOf(BigInteger.valueOf(-1))); + assertThrows( + IllegalArgumentException.class, () -> UInt64.valueOf(BigInteger.valueOf(2).pow(64))); + } + + @ParameterizedTest + @MethodSource("addProvider") + void add(UInt64 v1, UInt64 v2, UInt64 expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addProvider() { + return Stream.of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(0), v(1), v(1)), + Arguments.of(v(0), v(100), v(100)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(100), v(90), v(190)), + Arguments.of(UInt64.MAX_VALUE, v(1), v(0)), + Arguments.of(UInt64.MAX_VALUE, v(2), v(1)), + Arguments.of(hv("0xFFFFFFFFFFFFFFF0"), v(1), hv("0xFFFFFFFFFFFFFFF1")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFE"), v(1), UInt64.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addLongProvider") + void addLong(UInt64 v1, long v2, UInt64 expected) { + assertValueEquals(expected, v1.add(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addLongProvider() { + return Stream.of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(0), 1L, v(1)), + Arguments.of(v(0), 100L, v(100)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(100), 90L, v(190)), + Arguments.of(UInt64.MAX_VALUE, 1L, v(0)), + Arguments.of(UInt64.MAX_VALUE, 2L, v(1)), + Arguments.of(hv("0xFFFFFFFFFFFFFFF0"), 1L, hv("0xFFFFFFFFFFFFFFF1")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFE"), 1L, UInt64.MAX_VALUE), + Arguments.of(v(10), -5L, v(5)), + Arguments.of(v(0), -1L, UInt64.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addModProvider") + void addMod(UInt64 v1, UInt64 v2, UInt64 m, UInt64 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModProvider() { + return Stream.of( + Arguments.of(v(0), v(1), UInt64.valueOf(2), v(1)), + Arguments.of(v(1), v(1), UInt64.valueOf(2), v(0)), + Arguments.of( + UInt64.MAX_VALUE.subtract(2), v(1), UInt64.MAX_VALUE, UInt64.MAX_VALUE.subtract(1)), + Arguments.of(UInt64.MAX_VALUE.subtract(1), v(1), UInt64.MAX_VALUE, v(0)), + Arguments.of(v(2), v(1), UInt64.valueOf(2), v(1)), + Arguments.of(v(3), v(2), UInt64.valueOf(6), v(5)), + Arguments.of(v(3), v(4), UInt64.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModOfZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).addMod(v(1), UInt64.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModUInt64UInt64Provider") + void addModUInt64UInt64(UInt64 v1, UInt64 v2, UInt64 m, UInt64 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModUInt64UInt64Provider() { + return Stream.of( + Arguments.of(v(0), UInt64.ONE, UInt64.valueOf(2), v(1)), + Arguments.of(v(1), UInt64.ONE, UInt64.valueOf(2), v(0)), + Arguments.of( + UInt64.MAX_VALUE.subtract(2), + UInt64.ONE, + UInt64.MAX_VALUE, + UInt64.MAX_VALUE.subtract(1)), + Arguments.of(UInt64.MAX_VALUE.subtract(1), UInt64.ONE, UInt64.MAX_VALUE, v(0)), + Arguments.of(v(2), UInt64.ONE, UInt64.valueOf(2), v(1)), + Arguments.of(v(3), UInt64.valueOf(2), UInt64.valueOf(6), v(5)), + Arguments.of(v(3), UInt64.valueOf(4), UInt64.valueOf(2), v(1))); + } + + @Test + void shouldThrowForAddModLongUInt64OfZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).addMod(1, UInt64.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongUInt64Provider") + void addModLongUInt64(UInt64 v1, long v2, UInt64 m, UInt64 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongUInt64Provider() { + return Stream.of( + Arguments.of(v(0), 1L, UInt64.valueOf(2), v(1)), + Arguments.of(v(1), 1L, UInt64.valueOf(2), v(0)), + Arguments.of( + UInt64.MAX_VALUE.subtract(2), 1L, UInt64.MAX_VALUE, UInt64.MAX_VALUE.subtract(1)), + Arguments.of(UInt64.MAX_VALUE.subtract(1), 1L, UInt64.MAX_VALUE, v(0)), + Arguments.of(v(2), 1L, UInt64.valueOf(2), v(1)), + Arguments.of(v(2), -1L, UInt64.valueOf(2), v(1)), + Arguments.of(v(1), -7L, UInt64.valueOf(5), v(4))); + } + + @Test + void shouldThrowForAddModUInt64UInt64OfZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).addMod(UInt64.ONE, UInt64.ZERO)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("addModLongLongProvider") + void addModLongLong(UInt64 v1, long v2, long m, UInt64 expected) { + assertValueEquals(expected, v1.addMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addModLongLongProvider() { + return Stream.of( + Arguments.of(v(0), 1L, 2L, v(1)), + Arguments.of(v(1), 1L, 2L, v(0)), + Arguments.of(v(2), 1L, 2L, v(1))); + } + + @Test + void shouldThrowForAddModLongLongOfZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, 0)); + assertEquals("addMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForAddModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, -5)); + assertEquals("addMod unsigned with negative modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("subtractProvider") + void subtract(UInt64 v1, UInt64 v2, UInt64 expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractProvider() { + return Stream.of( + Arguments.of(v(1), v(0), v(1)), + Arguments.of(v(5), v(0), v(5)), + Arguments.of(v(2), v(1), v(1)), + Arguments.of(v(100), v(100), v(0)), + Arguments.of(v(0), v(1), UInt64.MAX_VALUE), + Arguments.of(v(1), v(2), UInt64.MAX_VALUE), + Arguments.of(UInt64.MAX_VALUE, v(1), hv("0xFFFFFFFFFFFFFFFE"))); + } + + @ParameterizedTest + @MethodSource("subtractLongProvider") + void subtractLong(UInt64 v1, long v2, UInt64 expected) { + assertValueEquals(expected, v1.subtract(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractLongProvider() { + return Stream.of( + Arguments.of(v(1), 0L, v(1)), + Arguments.of(v(5), 0L, v(5)), + Arguments.of(v(2), 1L, v(1)), + Arguments.of(v(100), 100L, v(0)), + Arguments.of(v(0), 1L, UInt64.MAX_VALUE), + Arguments.of(v(1), 2L, UInt64.MAX_VALUE), + Arguments.of(UInt64.MAX_VALUE, 1L, hv("0xFFFFFFFFFFFFFFFE")), + Arguments.of(v(0), -1L, v(1)), + Arguments.of(v(0), -100L, v(100)), + Arguments.of(v(2), -2L, v(4))); + } + + @ParameterizedTest + @MethodSource("multiplyProvider") + void multiply(UInt64 v1, UInt64 v2, UInt64 expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyProvider() { + return Stream.of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(2)), + Arguments.of(v(2), v(2), v(4)), + Arguments.of(v(3), v(2), v(6)), + Arguments.of(v(4), v(2), v(8)), + Arguments.of(v(10), v(18), v(180)), + Arguments.of(v(2), v(8), v(16)), + Arguments.of(v(7), v(8), v(56)), + Arguments.of(v(8), v(8), v(64)), + Arguments.of(v(17), v(8), v(136)), + Arguments.of(v(22), v(0), v(0))); + } + + @ParameterizedTest + @MethodSource("multiplyLongProvider") + void multiplyLong(UInt64 v1, long v2, UInt64 expected) { + assertValueEquals(expected, v1.multiply(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyLongProvider() { + return Stream.of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(2)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(3), 2L, v(6)), + Arguments.of(v(4), 2L, v(8)), + Arguments.of(v(10), 18L, v(180)), + Arguments.of(v(2), 8L, v(16)), + Arguments.of(v(7), 8L, v(56)), + Arguments.of(v(8), 8L, v(64)), + Arguments.of(v(17), 8L, v(136)), + Arguments.of(v(22), 0L, v(0)), + Arguments.of(hv("0x0FFFFFFFFFFFFFFF"), 2L, hv("0x1FFFFFFFFFFFFFFE")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), 2L, hv("0xFFFFFFFFFFFFFFFE"))); + } + + @Test + void shouldThrowForMultiplyLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiply(-5)); + assertEquals("multiply unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModProvider") + void multiplyMod(UInt64 v1, UInt64 v2, UInt64 m, UInt64 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModProvider() { + return Stream.of( + Arguments.of(v(0), v(5), UInt64.valueOf(2), v(0)), + Arguments.of(v(2), v(3), UInt64.valueOf(7), v(6)), + Arguments.of(v(2), v(3), UInt64.valueOf(6), v(0)), + Arguments.of(v(2), v(0), UInt64.valueOf(6), v(0)), + Arguments.of(hv("0x0FFFFFFFFFFFFFFE"), v(2), UInt64.MAX_VALUE, hv("0x1FFFFFFFFFFFFFFC"))); + } + + @Test + void shouldThrowForMultiplyModOfModZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(v(1), UInt64.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongUInt64Provider") + void multiplyModLongUInt64(UInt64 v1, long v2, UInt64 m, UInt64 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongUInt64Provider() { + return Stream.of( + Arguments.of(v(0), 5L, UInt64.valueOf(2), v(0)), + Arguments.of(v(2), 3L, UInt64.valueOf(7), v(6)), + Arguments.of(v(2), 3L, UInt64.valueOf(6), v(0)), + Arguments.of(v(2), 0L, UInt64.valueOf(6), v(0)), + Arguments.of(hv("0x0FFFFFFFFFFFFFFE"), 2L, UInt64.MAX_VALUE, hv("0x1FFFFFFFFFFFFFFC"))); + } + + @Test + void shouldThrowForMultiplyModLongUInt64OfModZero() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1L, UInt64.ZERO)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongUInt64OfNegative() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, UInt64.valueOf(2))); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("multiplyModLongLongProvider") + void multiplyModLongLong(UInt64 v1, long v2, long m, UInt64 expected) { + assertValueEquals(expected, v1.multiplyMod(v2, m)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream multiplyModLongLongProvider() { + return Stream.of( + Arguments.of(v(0), 5L, 2L, v(0)), + Arguments.of(v(2), 3L, 7L, v(6)), + Arguments.of(v(2), 3L, 6L, v(0)), + Arguments.of(v(2), 0L, 6L, v(0)), + Arguments.of(hv("0x0FFFFFFFFFFFFFFE"), 2L, Long.MAX_VALUE, hv("0x1FFFFFFFFFFFFFFC"))); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1, 0)); + assertEquals("multiplyMod with zero modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfModNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiplyMod(5, -7)); + assertEquals("multiplyMod unsigned with negative modulus", exception.getMessage()); + } + + @Test + void shouldThrowForMultiplyModLongLongOfNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, 2)); + assertEquals("multiplyMod unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideProvider") + void divide(UInt64 v1, UInt64 v2, UInt64 expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideProvider() { + return Stream.of( + Arguments.of(v(0), v(2), v(0)), + Arguments.of(v(1), v(2), v(0)), + Arguments.of(v(2), v(2), v(1)), + Arguments.of(v(3), v(2), v(1)), + Arguments.of(v(4), v(2), v(2)), + Arguments.of(v(2), v(8), v(0)), + Arguments.of(v(7), v(8), v(0)), + Arguments.of(v(8), v(8), v(1)), + Arguments.of(v(9), v(8), v(1)), + Arguments.of(v(17), v(8), v(2)), + Arguments.of(v(1024), v(8), v(128)), + Arguments.of(v(1026), v(8), v(128))); + } + + @Test + void shouldThrowForDivideByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(v(0))); + assertEquals("divide by zero", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("divideLongProvider") + void divideLong(UInt64 v1, long v2, UInt64 expected) { + assertValueEquals(expected, v1.divide(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream divideLongProvider() { + return Stream.of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(0)), + Arguments.of(v(2), 2L, v(1)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(v(4), 2L, v(2)), + Arguments.of(v(2), 8L, v(0)), + Arguments.of(v(7), 8L, v(0)), + Arguments.of(v(8), 8L, v(1)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(17), 8L, v(2)), + Arguments.of(v(1024), 8L, v(128)), + Arguments.of(v(1026), 8L, v(128))); + } + + @Test + void shouldThrowForDivideLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(0)); + assertEquals("divide by zero", exception.getMessage()); + } + + @Test + void shouldThrowForDivideLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(-5)); + assertEquals("divide unsigned by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("powUInt64Provider") + void powUInt64(UInt64 v1, UInt64 v2, UInt64 expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powUInt64Provider() { + return Stream.of( + Arguments.of(v(0), UInt64.valueOf(2), v(0)), + Arguments.of(v(2), UInt64.valueOf(2), v(4)), + Arguments.of(v(2), UInt64.valueOf(8), v(256)), + Arguments.of(v(3), UInt64.valueOf(3), v(27)), + Arguments.of(hv("0xFFFFFFFFFFF0F0F0"), UInt64.valueOf(3), hv("0xF2A920E119A2F000"))); + } + + @ParameterizedTest + @MethodSource("powLongProvider") + void powLong(UInt64 v1, long v2, UInt64 expected) { + assertValueEquals(expected, v1.pow(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream powLongProvider() { + return Stream.of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(2), 2L, v(4)), + Arguments.of(v(2), 8L, v(256)), + Arguments.of(v(3), 3L, v(27)), + Arguments.of(hv("0xFFFFFFFFFFF0F0F0"), 3L, hv("0xF2A920E119A2F000"))); + } + + @ParameterizedTest + @MethodSource("modLongProvider") + void modLong(UInt64 v1, long v2, UInt64 expected) { + assertValueEquals(expected, v1.mod(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream modLongProvider() { + return Stream.of( + Arguments.of(v(0), 2L, v(0)), + Arguments.of(v(1), 2L, v(1)), + Arguments.of(v(2), 2L, v(0)), + Arguments.of(v(3), 2L, v(1)), + Arguments.of(v(0), 8L, v(0)), + Arguments.of(v(1), 8L, v(1)), + Arguments.of(v(2), 8L, v(2)), + Arguments.of(v(3), 8L, v(3)), + Arguments.of(v(7), 8L, v(7)), + Arguments.of(v(8), 8L, v(0)), + Arguments.of(v(9), 8L, v(1)), + Arguments.of(v(1024), 8L, v(0)), + Arguments.of(v(1026), 8L, v(2))); + } + + @Test + void shouldThrowForModLongByZero() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(0)); + assertEquals("mod by zero", exception.getMessage()); + } + + @Test + void shouldThrowForModLongByNegative() { + Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(-5)); + assertEquals("mod by negative", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("andProvider") + void and(UInt64 v1, Bytes v2, UInt64 expected) { + assertValueEquals(expected, UInt64.fromBytes(v1.mutableCopy().and(v2))); + } + + @SuppressWarnings("UnusedMethod") + private static Stream andProvider() { + return Stream.of( + Arguments.of(hv("0x00000000FFFFFFFF"), hv("0xFFFFFFFF00000000"), hv("0x0000000000000000")), + Arguments.of(hv("0x00000000FFFFFFFF"), hv("0xFFFFFFFFFF000000"), hv("0x00000000FF000000")), + Arguments.of(hv("0x00000000FFFFFFFF"), b("0xFFFFFFFFFF000000"), hv("0x00000000FF000000"))); + } + + @ParameterizedTest + @MethodSource("orProvider") + void or(UInt64 v1, Bytes v2, UInt64 expected) { + assertValueEquals(expected, UInt64.fromBytes(v1.mutableCopy().or(v2))); + } + + @SuppressWarnings("UnusedMethod") + private static Stream orProvider() { + return Stream.of( + Arguments.of(hv("0x00000000FFFFFFFF"), hv("0xFFFFFFFF00000000"), hv("0xFFFFFFFFFFFFFFFF")), + Arguments.of(hv("0x00000000FFFFFFFF"), hv("0xFFFFFFFF00000000"), hv("0xFFFFFFFFFFFFFFFF")), + Arguments.of(hv("0x00000000000000FF"), hv("0xFFFFFFFF00000000"), hv("0xFFFFFFFF000000FF")), + Arguments.of(hv("0x00000000000000FF"), b("0xFFFFFFFF00000000"), hv("0xFFFFFFFF000000FF"))); + } + + @ParameterizedTest + @MethodSource("xorProvider") + void xor(UInt64 v1, Bytes v2, UInt64 expected) { + assertValueEquals(expected, UInt64.fromBytes(v1.mutableCopy().xor(v2))); + } + + @SuppressWarnings("UnusedMethod") + private static Stream xorProvider() { + return Stream.of( + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), hv("0xFFFFFFFFFFFFFFFF"), hv("0x0000000000000000")), + Arguments.of(hv("0x00000000FFFFFFFF"), hv("0xFFFFFFFF00000000"), hv("0xFFFFFFFFFFFFFFFF")), + Arguments.of(hv("0x00000000FFFFFFFF"), hv("0xFFFFFFFFFF000000"), hv("0xFFFFFFFF00FFFFFF")), + Arguments.of(hv("0x00000000FFFFFFFF"), b("0xFFFFFFFFFF000000"), hv("0xFFFFFFFF00FFFFFF"))); + } + + @ParameterizedTest + @MethodSource("notProvider") + void not(UInt64 value, UInt64 expected) { + assertValueEquals(expected, UInt64.fromBytes(value.mutableCopy().not())); + } + + @SuppressWarnings("UnusedMethod") + private static Stream notProvider() { + return Stream.of( + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), hv("0x0000000000000000")), + Arguments.of(hv("0x0000000000000000"), hv("0xFFFFFFFFFFFFFFFF")), + Arguments.of(hv("0x00000000FFFFFFFF"), hv("0xFFFFFFFF00000000"))); + } + + @ParameterizedTest + @MethodSource("shiftLeftProvider") + void shiftLeft(UInt64 value, int distance, UInt64 expected) { + assertValueEquals(expected, UInt64.fromBytes(value.mutableCopy().shiftLeft(distance))); + } + + @SuppressWarnings("UnusedMethod") + private static Stream shiftLeftProvider() { + return Stream.of( + Arguments.of(hv("0x01"), 1, hv("0x02")), + Arguments.of(hv("0x01"), 2, hv("0x04")), + Arguments.of(hv("0x01"), 8, hv("0x0100")), + Arguments.of(hv("0x01"), 9, hv("0x0200")), + Arguments.of(hv("0x01"), 16, hv("0x10000")), + Arguments.of(hv("0x00FF00"), 4, hv("0x0FF000")), + Arguments.of(hv("0x00FF00"), 8, hv("0xFF0000")), + Arguments.of(hv("0x00FF00"), 1, hv("0x01FE00")), + Arguments.of(hv("0x0000000000000001"), 16, hv("0x0000000000010000")), + Arguments.of(hv("0x0000000000000001"), 15, hv("0x0000000000008000")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), 55, hv("0xFF80000000000000")), + Arguments.of(hv("0x00000000FFFFFFFF"), 50, hv("0xFFFC000000000000"))); + } + + @ParameterizedTest + @MethodSource("shiftRightProvider") + void shiftRight(UInt64 value, int distance, UInt64 expected) { + assertValueEquals(expected, UInt64.fromBytes(value.mutableCopy().shiftRight(distance))); + } + + @SuppressWarnings("UnusedMethod") + private static Stream shiftRightProvider() { + return Stream.of( + Arguments.of(hv("0x01"), 1, hv("0x00")), + Arguments.of(hv("0x10"), 1, hv("0x08")), + Arguments.of(hv("0x10"), 2, hv("0x04")), + Arguments.of(hv("0x10"), 8, hv("0x00")), + Arguments.of(hv("0x1000"), 4, hv("0x0100")), + Arguments.of(hv("0x1000"), 5, hv("0x0080")), + Arguments.of(hv("0x1000"), 8, hv("0x0010")), + Arguments.of(hv("0x1000"), 9, hv("0x0008")), + Arguments.of(hv("0x1000"), 16, hv("0x0000")), + Arguments.of(hv("0x00FF00"), 4, hv("0x000FF0")), + Arguments.of(hv("0x00FF00"), 8, hv("0x0000FF")), + Arguments.of(hv("0x00FF00"), 1, hv("0x007F80")), + Arguments.of(hv("0x1000000000000000"), 16, hv("0x0000100000000000")), + Arguments.of(hv("0x1000000000000000"), 15, hv("0x0000200000000000")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), 55, hv("0x00000000000001FF")), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), 202, hv("0x0000000000000000"))); + } + + @ParameterizedTest + @MethodSource("intValueProvider") + void intValue(UInt64 value, int expected) { + assertEquals(expected, value.intValue()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream intValueProvider() { + return Stream.of( + Arguments.of(hv("0x"), 0), + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x00000000"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x0001"), 1), + Arguments.of(hv("0x000001"), 1), + Arguments.of(hv("0x00000001"), 1), + Arguments.of(hv("0x0100"), 256), + Arguments.of(hv("0x000100"), 256), + Arguments.of(hv("0x00000100"), 256)); + } + + @Test + void shouldThrowForIntValueOfOversizeValue() { + Throwable exception = + assertThrows(ArithmeticException.class, () -> hv("0x0100000000").intValue()); + assertEquals("Value does not fit a 4 byte int", exception.getMessage()); + } + + @ParameterizedTest + @MethodSource("longValueProvider") + void longValue(UInt64 value, long expected) { + assertEquals(expected, value.toLong()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream longValueProvider() { + return Stream.of( + Arguments.of(hv("0x"), 0L), + Arguments.of(hv("0x00"), 0L), + Arguments.of(hv("0x00000000"), 0L), + Arguments.of(hv("0x01"), 1L), + Arguments.of(hv("0x0001"), 1L), + Arguments.of(hv("0x000001"), 1L), + Arguments.of(hv("0x00000001"), 1L), + Arguments.of(hv("0x0000000001"), 1L), + Arguments.of(hv("0x000000000001"), 1L), + Arguments.of(hv("0x0100"), 256L), + Arguments.of(hv("0x000100"), 256L), + Arguments.of(hv("0x00000100"), 256L), + Arguments.of(hv("0x00000100"), 256L), + Arguments.of(hv("0x000000000100"), 256L), + Arguments.of(hv("0x00000000000100"), 256L), + Arguments.of(hv("0x00000000000100"), 256L), + Arguments.of(hv("0xFFFFFFFF"), (1L << 32) - 1)); + } + + @ParameterizedTest + @MethodSource("compareToProvider") + void compareTo(UInt64 v1, UInt64 v2, int expected) { + assertEquals(expected, v1.compareTo(v2)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream compareToProvider() { + return Stream.of( + Arguments.of(v(5), v(5), 0), + Arguments.of(v(5), v(3), 1), + Arguments.of(v(5), v(6), -1), + Arguments.of(hv("0x0000000000000000"), hv("0x0000000000000000"), 0), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), hv("0xFFFFFFFFFFFFFFFF"), 0), + Arguments.of(hv("0x00000000FFFFFFFF"), hv("0x00000000FFFFFFFF"), 0), + Arguments.of(hv("0xFFFFFFFFFFFFFFFF"), hv("0x0000000000000000"), 1), + Arguments.of(hv("0x0000000000000000"), hv("0xFFFFFFFFFFFFFFFF"), -1), + Arguments.of(hv("0x00000001FFFFFFFF"), hv("0x00000000FFFFFFFF"), 1), + Arguments.of(hv("0x00000000FFFFFFFE"), hv("0x00000000FFFFFFFF"), -1)); + } + + @ParameterizedTest + @MethodSource("toBytesProvider") + void toBytesTest(UInt64 value, Bytes expected) { + assertEquals(expected, value.toBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toBytesProvider() { + return Stream.of( + Arguments.of(hv("0x00"), Bytes.fromHexString("0x0000000000000000")), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x0000000001000000")), + Arguments.of(hv("0x0100000000"), Bytes.fromHexString("0x0000000100000000")), + Arguments.of(hv("0xf100000000ab"), Bytes.fromHexString("0x0000F100000000AB"))); + } + + @ParameterizedTest + @MethodSource("toMinimalBytesProvider") + void toMinimalBytesTest(UInt64 value, Bytes expected) { + assertEquals(expected, value.toMinimalBytes()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream toMinimalBytesProvider() { + return Stream.of( + Arguments.of(hv("0x00"), Bytes.EMPTY), + Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000")), + Arguments.of(hv("0x0100000000"), Bytes.fromHexString("0x0100000000")), + Arguments.of(hv("0xf100000000ab"), Bytes.fromHexString("0xf100000000ab")), + Arguments.of(hv("0100000000000000"), Bytes.fromHexString("0x0100000000000000"))); + } + + @ParameterizedTest + @MethodSource("numberOfLeadingZerosProvider") + void numberOfLeadingZeros(UInt64 value, int expected) { + assertEquals(expected, value.numberOfLeadingZeros()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream numberOfLeadingZerosProvider() { + return Stream.of( + Arguments.of(hv("0x00"), 64), + Arguments.of(hv("0x01"), 63), + Arguments.of(hv("0x02"), 62), + Arguments.of(hv("0x03"), 62), + Arguments.of(hv("0x0F"), 60), + Arguments.of(hv("0x8F"), 56), + Arguments.of(hv("0x100000000"), 31)); + } + + @ParameterizedTest + @MethodSource("bitLengthProvider") + void bitLength(UInt64 value, int expected) { + assertEquals(expected, value.bitLength()); + } + + @SuppressWarnings("UnusedMethod") + private static Stream bitLengthProvider() { + return Stream.of( + Arguments.of(hv("0x00"), 0), + Arguments.of(hv("0x01"), 1), + Arguments.of(hv("0x02"), 2), + Arguments.of(hv("0x03"), 2), + Arguments.of(hv("0x0F"), 4), + Arguments.of(hv("0x8F"), 8), + Arguments.of(hv("0x100000000"), 33)); + } + + @ParameterizedTest + @MethodSource("addExactProvider") + void addExact(UInt64 value, UInt64 operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactProvider() { + return Stream.of( + Arguments.of(UInt64.MAX_VALUE, v(1)), Arguments.of(UInt64.MAX_VALUE, UInt64.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("addExactLongProvider") + void addExactLong(UInt64 value, long operand) { + assertThrows(ArithmeticException.class, () -> value.addExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream addExactLongProvider() { + return Stream.of( + Arguments.of(UInt64.MAX_VALUE, 3), + Arguments.of(UInt64.MAX_VALUE, Long.MAX_VALUE), + Arguments.of(v(0), -1)); + } + + @ParameterizedTest + @MethodSource("subtractExactProvider") + void subtractExact(UInt64 value, UInt64 operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactProvider() { + return Stream.of(Arguments.of(v(0), v(1)), Arguments.of(v(0), UInt64.MAX_VALUE)); + } + + @ParameterizedTest + @MethodSource("subtractExactLongProvider") + void subtractExactLong(UInt64 value, long operand) { + assertThrows(ArithmeticException.class, () -> value.subtractExact(operand)); + } + + @SuppressWarnings("UnusedMethod") + private static Stream subtractExactLongProvider() { + return Stream.of( + Arguments.of(v(0), 1), + Arguments.of(v(0), Long.MAX_VALUE), + Arguments.of(UInt64.MAX_VALUE, -1)); + } + + private void assertValueEquals(UInt64 expected, UInt64 actual) { + String msg = + String.format("Expected %s but got %s", expected.toHexString(), actual.toHexString()); + assertEquals(expected, actual, msg); + } + + @Test + void testToDecimalString() { + assertEquals("3456", UInt64.valueOf(3456).toDecimalString()); + } +}