diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 00000000..7d4a55cc
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,145 @@
+version: 2
+jobs:
+ build:
+ docker:
+ - image: circleci/openjdk:8u171-jdk-stretch
+
+ working_directory: ~/repo
+
+ environment:
+ TERM: dumb
+ JAVA_TOOL_OPTIONS: -Xmx2048m
+ GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=2
+ GRADLE_MAX_TEST_FORKS: 2
+
+ steps:
+ - checkout
+ - run:
+ name: Check submodule status
+ command: git submodule status | tee ~/submodule-status
+
+ - restore_cache:
+ name: Restoring cached submodules
+ keys:
+ - v1-submodules-{{ checksum "~/submodule-status" }}
+
+ - run:
+ name: Update submodules
+ command: git submodule update --init --recursive
+
+ - run:
+ name: Install Sodium Library
+ command: |
+ sudo sh -c "echo 'deb http://deb.debian.org/debian unstable main contrib non-free' > /etc/apt/sources.list"
+ sudo apt-get update
+ sudo apt-get install -y libsodium23
+
+ - restore_cache:
+ name: Restoring cached gradle dependencies
+ keys:
+ - v1-gradle-dir-{{ checksum "build.gradle" }}
+ - v1-gradle-dir-
+
+ - run:
+ name: Downloading dependencies
+ command: ./gradlew allDependencies checkLicenses
+
+ - run:
+ name: Compiling
+ command: ./gradlew spotlessCheck assemble
+
+ - run:
+ name: Collecting artifacts
+ command: |
+ mkdir -p ~/jars
+ find . -type f -regex ".*/build/libs/.*jar" -exec cp {} ~/jars/ \;
+ when: always
+
+ - store_artifacts:
+ name: Uploading artifacts
+ path: ~/jars
+ destination: jars
+ when: always
+
+ - run:
+ name: Running tests
+ command: ./gradlew --stacktrace test
+
+ - run:
+ name: Collecting test results
+ command: |
+ ./gradlew jacocoTestReport
+ mkdir -p ~/test-results/
+ find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/test-results/ \;
+ when: always
+
+ - store_test_results:
+ name: Uploading test results
+ path: ~/test-results
+ destination: tests
+ when: always
+
+ - run:
+ name: Collecting reports
+ command: |
+ mkdir -p ~/reports/license
+ (cd ./build/reports/license && tar c .) | (cd ~/reports/license && tar x)
+ find . -type d -regex ".*/build/reports/tests/test" | while read dir; do
+ module=`echo $dir | sed -e 's/build\/reports\/tests\/test//'`
+ mkdir -p ~/reports/test/"$module"
+ (cd "$dir" && tar c .) | (cd ~/reports/test/"$module" && tar x)
+ done
+ find . -type d -regex ".*/build/reports/jacoco/test/html" | while read dir; do
+ module=`echo $dir | sed -e 's/build\/reports\/jacoco\/test\/html//'`
+ mkdir -p ~/reports/jacoco/"$module"
+ (cd "$dir" && tar c .) | (cd ~/reports/jacoco/"$module" && tar x)
+ done
+ when: always
+
+ - store_artifacts:
+ name: Uploading reports
+ path: ~/reports
+ destination: reports
+
+ - run:
+ name: Building JavaDoc
+ command: ./gradlew :javadoc
+
+ - store_artifacts:
+ name: Uploading JavaDoc
+ path: build/docs/javadoc
+ destination: javadoc
+
+ - run:
+ name: Building Dokka docs
+ command: ./gradlew :dokka
+
+ - store_artifacts:
+ name: Uploading Dokka docs
+ path: build/docs/dokka
+ destination: dokka
+
+ - deploy:
+ name: Deploying snapshot to Bintray (master branch only)
+ command: |
+ if [ "${CIRCLE_BRANCH}" == "master" ]; then
+ echo "Start deployment"
+ ./gradlew deploy
+ else
+ echo "Start dry run deployment"
+ export BINTRAY_DRYRUN=true
+ ./gradlew deploy
+ fi
+
+ - save_cache:
+ name: Caching gradle dependencies
+ paths:
+ - .gradle
+ - ~/.gradle
+ key: v1-gradle-dir-{{ checksum "build.gradle" }}-{{ .Branch }}-{{ .BuildNum }}
+
+ - save_cache:
+ name: Caching submodules
+ paths:
+ - .git/modules
+ key: v1-submodules-{{ checksum "~/submodule-status" }}
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..470eda26
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,5 @@
+[*.{kt,kts}]
+indent_size=2
+continuation_indent_size=2
+insert_final_newline=true
+max_line_length=120
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..e9de15c4
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+* text eol=lf
+*.jar -text
+*.bat -text
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..2ce8720d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,26 @@
+*.bak
+*.swp
+*.tmp
+*~.nib
+*.iml
+*.launch
+*.swp
+*.tokens
+.classpath
+.externalToolBuilders/
+.gradle/
+.idea/
+.loadpath
+.metadata
+.prefs
+.project
+.recommenders/
+.settings
+.springBeans
+.vertx
+bin/
+local.properties
+target/
+tmp/
+build/
+out/
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..184f98fb
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "eth-reference-tests/src/test/resources/tests"]
+ path = eth-reference-tests/src/test/resources/tests
+ url = https://github.com/ethereum/tests.git
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..723abef8
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,61 @@
+# Contributing to Cava
+
+Welcome to the Cava repository! This document describes the procedure and guidelines for contributing to the Cava project. The subsequent sections encapsulate the criteria used to evaluate additions to, and modifications of, the existing codebase.
+
+## Contributor Workflow
+
+The codebase is maintained using the "*contributor workflow*" where everyone without exception contributes patch proposals using "*pull-requests*". This facilitates social contribution, easy testing and peer review.
+
+To contribute a patch, the workflow is as follows:
+
+* Fork repository
+* Create topic branch
+* Commit patch
+* Create pull-request, adhering to the coding conventions herein set forth
+
+In general a commit serves a single purpose and diffs should be easily comprehensible. For this reason do not mix any formatting fixes or code moves with actual code changes.
+
+## Style Guide
+
+`La mode se démode, le style jamais.`
+
+Guided by the immortal words of Gabrielle Bonheur, we strive to adhere strictly to best stylistic practices for each line of code in this software.
+
+At this stage one should expect comments and reviews from fellow contributors. You can add more commits to your pull request by committing them locally and pushing to your fork until you have satisfied all feedback. Before merging, you should aim to have a clean commit history where each commit identifies an specific change, or where all
+commits are squashed together.
+
+#### Stylistic
+
+The fundamental resource Cava contributors should familiarize themselves with is Oracle's [Code Conventions for the Java TM Programming Language](http://www.oracle.com/technetwork/java/codeconvtoc-136057.html), to establish a general programme on Java coding. Furthermore, all pull-requests should be formatted according to the (slightly modified) [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html), as it will be checked by our continuous integration architecture, and code that does not comply stylistically will fail to build.
+
+#### Architectural Best Practices
+
+Questions on architectural best practices will be guided by the principles set forth in [Effective Java](http://index-of.es/Java/Effective%20Java.pdf) by Joshua Bloch
+
+#### Clear Commit/PR Messages
+
+Commit messages should be verbose by default consisting of a short subject line (50 chars max), a blank line and detailed explanatory text as separate paragraph(s), unless the title alone is self-explanatory (such as "`Implement EXP EVM opcode`") in which case a single title line is sufficient. Commit messages should be helpful to people reading your code in the future, so explain the reasoning for your decisions. Further explanation on commit messages can be found [here](https://chris.beams.io/posts/git-commit/).
+
+#### Test coverage
+
+The test cases are sufficient enough to provide confidence in the code’s robustness, while avoiding redundant tests.
+
+#### Readability
+
+The code is easy to understand.
+
+#### Simplicity
+
+The code is not over-engineered, sufficient effort is made to minimize the cyclomatic complexity of the software.
+
+#### Functional
+
+Insofar as is possible the code intuitively and expeditiously executes the designated task.
+
+#### Clean
+
+The code is free from glaring typos (*e.g. misspelled comments*), thinkos, or formatting issues (*e.g. incorrect indentation*).
+
+#### Appropriately Commented
+
+Ambiguous or unclear code segments are commented. The comments are written in complete sentences.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..8dada3ed
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed 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.
diff --git a/PACKAGES.md b/PACKAGES.md
new file mode 100644
index 00000000..078ef1bc
--- /dev/null
+++ b/PACKAGES.md
@@ -0,0 +1,119 @@
+# Module cava
+
+In the spirit of [Google Guava](https://github.com/google/guava/), Cava is a set of libraries and other tools to aid development of blockchain and other decentralized software in Java and other JVM languages.
+
+# Package net.consensys.cava.bytes
+
+Classes and utilities for working with byte arrays.
+
+These classes are included in the standard Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-bytes` (`cava-bytes.jar`).
+
+# Package net.consensys.cava.concurrent
+
+Classes and utilities for working with concurrency.
+
+These classes are included in the standard Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-concurrent` (`cava-concurrent.jar`).
+
+# Package net.consensys.cava.config
+
+A general-purpose library for managing configuration data.
+
+These classes are included in the standard Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-config` (`cava-config.jar`).
+
+# Package net.consensys.cava.concurrent.coroutines.experimental
+
+Extensions for mapping [AsyncResult][net.consensys.cava.concurrent.AsyncResult] and [AsyncCompletion][net.consensys.cava.concurrent.AsyncCompletion] objects to and from Kotlin coroutines.
+
+# Package net.consensys.cava.crypto
+
+Classes and utilities for working with cryptography.
+
+These classes are included in the standard Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-crypto` (`cava-crypto.jar`).
+
+# Package net.consensys.cava.crypto.sodium
+
+Classes and utilities for working with the sodium native library.
+
+Classes and utilities in this package provide an interface to the native Sodium crypto library (https://www.libsodium.org/), which must be installed on the same system as the JVM. It will be searched for in common library locations, or its it can be loaded explicitly using [net.consensys.cava.crypto.sodium.Sodium.loadLibrary].
+
+Classes in this package also depend upon the JNR-FFI library being available on the classpath, along with its dependencies. See https://github.com/jnr/jnr-ffi. JNR-FFI can be included using the gradle dependency `com.github.jnr:jnr-ffi`.
+
+# Package net.consensys.cava.eth.domain
+
+Classes and utilities for working with Ethereum domain objects.
+
+These classes are included in the standard Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-eth-domain` (`cava-eth-domain.jar`).
+
+# Package net.consensys.cava.io
+
+Classes and utilities for handling file and network IO.
+
+These classes are included in the standard Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-io` (`cava-io.jar`).
+
+# Package net.consensys.cava.io.file
+
+General utilities for working with files and the filesystem.
+
+# Package net.consensys.cava.junit
+
+Utilities for better junit testing.
+
+These classes are included in the standard Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-junit` (`cava-junit.jar`).
+
+# Package net.consensys.cava.kv
+
+Classes and utilities for working with key/value stores.
+
+These classes are included in the standard Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-kv` (`cava-kv.jar`).
+
+# Package net.consensys.cava.net
+
+Classes and utilities for working with networking.
+
+These classes are included in the standard Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-net` (`cava-net.jar`).
+
+# Package net.consensys.cava.net.tls
+
+Utilities for doing fingerprint based TLS certificate checking.
+
+# Package net.consensys.cava.rlp
+
+Recursive Length Prefix (RLP) encoding and decoding.
+
+An implementation of the Ethereum Recursive Length Prefix (RLP) algorithm, as described at https://github.com/ethereum/wiki/wiki/RLP.
+
+These classes are included in the standard Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-rlp` (`cava-rlp.jar`).
+
+# Package net.consensys.cava.toml
+
+A parser for Tom's Obvious, Minimal Language (TOML).
+
+A parser and semantic checker for Tom's Obvious, Minimal Language (TOML), as described at https://github.com/toml-lang/toml/.
+
+These classes are included in the standard Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-toml` (cava-toml.jar).
+
+# Package net.consensys.cava.trie
+
+Merkle Trie implementations.
+
+Implementations of the Ethereum Patricia Trie, as described at https://github.com/ethereum/wiki/wiki/Patricia-Tree.
+
+These classes are included in the standard Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-merkle-trie` (`cava-merkle-trie.jar`).
+
+# Package net.consensys.cava.trie.experimental
+
+Merkle Trie implementations using Kotlin coroutines.
+
+# Package net.consensys.cava.units
+
+Classes and utilities for working with 256 bit integers and Ethereum units.
+
+These classes are included in the standard Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-units` (`cava-units.jar`).
+
+# Package net.consensys.cava.units.bigints
+
+Classes and utilities for working with 256 bit integers.
+
+# Package net.consensys.cava.units.ethereum
+
+Classes and utilities for working with Ethereum units.
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..f12c4cbf
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+# Cava: ConsenSys Core Libraries for Java (& Kotlin)
+
+[![Build Status](https://circleci.com/gh/ConsenSys/cava.svg?style=shield&circle-token=440c81af8cae3c059b516a8e375471258d7e0229)](https://circleci.com/gh/ConsenSys/cava)
+[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/consensys/cava/blob/master/LICENSE)
+[![Download](https://api.bintray.com/packages/consensys/consensys/cava/images/download.svg?version=0.1.0) ](https://bintray.com/consensys/consensys/cava/0.1.0)
+
+In the spirit of [Google Guava](https://github.com/google/guava/), Cava is a set of libraries and other tools to aid development of blockchain and other decentralized software in Java and other JVM languages.
+
+It includes a low-level bytes library, serialization and deserialization codecs (e.g. [RLP](https://github.com/ethereum/wiki/wiki/RLP)), various cryptography functions and primatives, and lots of other helpful utilities.
+
+Cava is developed for JDK 1.8 or higher, and depends on various other FOSS libraries, including Guava.
+
+## Getting cava
+
+> Note that these libraries are experimental and are subject to change.
+
+The libraries are published to [ConsenSys bintray repository](https://consensys.bintray.com/consensys/) and linked to JCenter.
+
+You can import all modules using the cava jar.
+
+With Maven:
+```
+
+ * Two {@link Bytes} values are equal is they have contain the exact same bytes. + * + * @param obj The object to test for equality with. + * @return true if this value and {@code obj} are equal. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Bytes)) { + return false; + } + + Bytes other = (Bytes) obj; + 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; + } + + @Override + public int hashCode() { + int result = 1; + for (int i = 0; i < size(); i++) { + result = 31 * result + get(i); + } + return result; + } + + @Override + public String toString() { + return toHexString(); + } +} diff --git a/bytes/src/main/java/net/consensys/cava/bytes/ArrayWrappingBytes.java b/bytes/src/main/java/net/consensys/cava/bytes/ArrayWrappingBytes.java new file mode 100644 index 00000000..2f365630 --- /dev/null +++ b/bytes/src/main/java/net/consensys/cava/bytes/ArrayWrappingBytes.java @@ -0,0 +1,188 @@ +/* + * Copyright 2018, ConsenSys Inc. + * + * Licensed 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. + */ +package net.consensys.cava.bytes; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; + +import java.security.MessageDigest; +import java.util.Arrays; + +import io.vertx.core.buffer.Buffer; + +class ArrayWrappingBytes extends AbstractBytes { + + protected final byte[] bytes; + protected final int offset; + protected final int length; + + ArrayWrappingBytes(byte[] bytes) { + this(bytes, 0, bytes.length); + } + + ArrayWrappingBytes(byte[] bytes, int offset, int length) { + checkArgument(length >= 0, "Invalid negative length"); + if (bytes.length > 0) { + checkElementIndex(offset, bytes.length); + } + checkArgument( + offset + length <= bytes.length, + "Provided length %s is too big: the value has only %s bytes from offset %s", + length, + bytes.length - offset, + offset); + + this.bytes = bytes; + this.offset = offset; + this.length = length; + } + + @Override + public int size() { + return length; + } + + @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(i, size()); + return bytes[offset + i]; + } + + @Override + public Bytes slice(int i, int length) { + if (i == 0 && length == this.length) { + return this; + } + if (length == 0) { + return Bytes.EMPTY; + } + + checkElementIndex(i, this.length); + checkArgument( + i + length <= this.length, + "Provided length %s is too big: the value has size %s and has only %s bytes from %s", + length, + this.length, + this.length - i, + i); + + return length == Bytes32.SIZE ? new ArrayWrappingBytes32(bytes, offset + i) + : new ArrayWrappingBytes(bytes, offset + i, length); + } + + // MUST be overridden by mutable implementations + @Override + public Bytes copy() { + if (offset == 0 && length == bytes.length) { + return this; + } + return new ArrayWrappingBytes(toArray()); + } + + @Override + public MutableBytes mutableCopy() { + return new MutableArrayWrappingBytes(toArray()); + } + + @Override + public int commonPrefixLength(Bytes other) { + if (!(other instanceof ArrayWrappingBytes)) { + return super.commonPrefixLength(other); + } + ArrayWrappingBytes o = (ArrayWrappingBytes) other; + int i = 0; + while (i < length && i < o.length && bytes[offset + i] == o.bytes[o.offset + i]) { + i++; + } + return i; + } + + @Override + public void update(MessageDigest digest) { + digest.update(bytes, offset, length); + } + + @Override + public void copyTo(MutableBytes destination, int destinationOffset) { + if (!(destination instanceof MutableArrayWrappingBytes)) { + super.copyTo(destination, destinationOffset); + return; + } + + int size = size(); + if (size == 0) { + return; + } + + checkElementIndex(destinationOffset, destination.size()); + checkArgument( + destination.size() - destinationOffset >= size, + "Cannot copy %s bytes, destination has only %s bytes from index %s", + size, + destination.size() - destinationOffset, + destinationOffset); + + MutableArrayWrappingBytes d = (MutableArrayWrappingBytes) destination; + System.arraycopy(bytes, offset, d.bytes, d.offset + destinationOffset, size); + } + + @Override + public void appendTo(Buffer buffer) { + buffer.appendBytes(bytes, offset, length); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof ArrayWrappingBytes)) { + return super.equals(obj); + } + ArrayWrappingBytes other = (ArrayWrappingBytes) obj; + if (length != other.length) { + return false; + } + for (int i = 0; i < length; ++i) { + if (bytes[offset + i] != other.bytes[other.offset + i]) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + int result = 1; + int size = size(); + for (int i = 0; i < size; i++) { + result = 31 * result + bytes[offset + i]; + } + return result; + } + + @Override + public byte[] toArray() { + return Arrays.copyOfRange(bytes, offset, offset + length); + } + + @Override + public byte[] toArrayUnsafe() { + if (offset == 0 && length == bytes.length) { + return bytes; + } + return toArray(); + } +} diff --git a/bytes/src/main/java/net/consensys/cava/bytes/ArrayWrappingBytes32.java b/bytes/src/main/java/net/consensys/cava/bytes/ArrayWrappingBytes32.java new file mode 100644 index 00000000..8859e437 --- /dev/null +++ b/bytes/src/main/java/net/consensys/cava/bytes/ArrayWrappingBytes32.java @@ -0,0 +1,56 @@ +/* + * Copyright 2018, ConsenSys Inc. + * + * Licensed 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. + */ +package net.consensys.cava.bytes; + +import static com.google.common.base.Preconditions.checkArgument; + +final class ArrayWrappingBytes32 extends ArrayWrappingBytes implements Bytes32 { + + ArrayWrappingBytes32(byte[] bytes) { + this(checkLength(bytes), 0); + } + + ArrayWrappingBytes32(byte[] bytes, int offset) { + super(checkLength(bytes, offset), offset, SIZE); + } + + // Ensures a proper error message. + private static byte[] checkLength(byte[] bytes) { + checkArgument(bytes.length == SIZE, "Expected %s bytes but got %s", SIZE, bytes.length); + return bytes; + } + + // Ensures a proper error message. + private static byte[] checkLength(byte[] bytes, int offset) { + checkArgument( + bytes.length - offset >= SIZE, + "Expected at least %s bytes from offset %s but got only %s", + SIZE, + offset, + bytes.length - offset); + return bytes; + } + + @Override + public Bytes32 copy() { + if (offset == 0 && length == bytes.length) { + return this; + } + return new ArrayWrappingBytes32(toArray()); + } + + @Override + public MutableBytes32 mutableCopy() { + return new MutableArrayWrappingBytes32(toArray()); + } +} diff --git a/bytes/src/main/java/net/consensys/cava/bytes/BufferWrappingBytes.java b/bytes/src/main/java/net/consensys/cava/bytes/BufferWrappingBytes.java new file mode 100644 index 00000000..46beb633 --- /dev/null +++ b/bytes/src/main/java/net/consensys/cava/bytes/BufferWrappingBytes.java @@ -0,0 +1,126 @@ +/* + * Copyright 2018, ConsenSys Inc. + * + * Licensed 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. + */ +package net.consensys.cava.bytes; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; + +import io.vertx.core.buffer.Buffer; + +class BufferWrappingBytes extends AbstractBytes { + + protected final Buffer buffer; + + BufferWrappingBytes(Buffer buffer) { + this.buffer = buffer; + } + + BufferWrappingBytes(Buffer buffer, int offset, int length) { + checkArgument(length >= 0, "Invalid negative length"); + int bufferLength = buffer.length(); + checkElementIndex(offset, bufferLength + 1); + checkArgument( + offset + length <= bufferLength, + "Provided length %s is too big: the buffer has size %s and has only %s bytes from %s", + length, + bufferLength, + bufferLength - offset, + offset); + + if (offset == 0 && length == bufferLength) { + this.buffer = buffer; + } else { + this.buffer = buffer.slice(offset, offset + length); + } + } + + @Override + public int size() { + return buffer.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 Bytes.EMPTY; + } + + checkElementIndex(i, size); + checkArgument( + i + length <= size, + "Provided length %s is too big: the value has size %s and has only %s bytes from %s", + length, + size, + size - i, + i); + + return new BufferWrappingBytes(buffer.slice(i, i + length)); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof BufferWrappingBytes)) { + return super.equals(obj); + } + BufferWrappingBytes other = (BufferWrappingBytes) obj; + return buffer.equals(other.buffer); + } + + @Override + public int hashCode() { + return buffer.hashCode(); + } + + // MUST be overridden by mutable implementations + @Override + public Bytes copy() { + return Bytes.wrap(toArray()); + } + + @Override + public MutableBytes mutableCopy() { + return MutableBytes.wrap(toArray()); + } + + @Override + public void appendTo(Buffer buffer) { + buffer.appendBuffer(this.buffer); + } + + @Override + public byte[] toArray() { + return buffer.getBytes(); + } +} diff --git a/bytes/src/main/java/net/consensys/cava/bytes/ByteBufWrappingBytes.java b/bytes/src/main/java/net/consensys/cava/bytes/ByteBufWrappingBytes.java new file mode 100644 index 00000000..ec0929d9 --- /dev/null +++ b/bytes/src/main/java/net/consensys/cava/bytes/ByteBufWrappingBytes.java @@ -0,0 +1,130 @@ +/* + * Copyright 2018, ConsenSys Inc. + * + * Licensed 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. + */ +package net.consensys.cava.bytes; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; + +import io.netty.buffer.ByteBuf; +import io.vertx.core.buffer.Buffer; + +class ByteBufWrappingBytes extends AbstractBytes { + + protected final ByteBuf byteBuf; + + ByteBufWrappingBytes(ByteBuf byteBuf) { + this.byteBuf = byteBuf; + } + + ByteBufWrappingBytes(ByteBuf byteBuf, int offset, int length) { + checkArgument(length >= 0, "Invalid negative length"); + int bufferLength = byteBuf.capacity(); + checkElementIndex(offset, bufferLength + 1); + checkArgument( + offset + length <= bufferLength, + "Provided length %s is too big: the buffer has size %s and has only %s bytes from %s", + length, + bufferLength, + bufferLength - offset, + offset); + + if (offset == 0 && length == bufferLength) { + this.byteBuf = byteBuf; + } else { + this.byteBuf = byteBuf.slice(offset, length); + } + } + + @Override + public int size() { + return byteBuf.capacity(); + } + + @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 Bytes.EMPTY; + } + + checkElementIndex(i, size); + checkArgument( + i + length <= size, + "Provided length %s is too big: the value has size %s and has only %s bytes from %s", + length, + size, + size - i, + i); + + return new ByteBufWrappingBytes(byteBuf.slice(i, length)); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof ByteBufWrappingBytes)) { + return super.equals(obj); + } + ByteBufWrappingBytes other = (ByteBufWrappingBytes) obj; + return byteBuf.equals(other.byteBuf); + } + + @Override + public int hashCode() { + return byteBuf.hashCode(); + } + + // MUST be overridden by mutable implementations + @Override + public Bytes copy() { + return Bytes.wrap(toArray()); + } + + @Override + public MutableBytes mutableCopy() { + return MutableBytes.wrap(toArray()); + } + + @Override + public void appendTo(Buffer buffer) { + buffer.appendBuffer(Buffer.buffer(this.byteBuf)); + } + + @Override + public byte[] toArray() { + int size = byteBuf.capacity(); + byte[] array = new byte[size]; + byteBuf.getBytes(0, array); + return array; + } +} diff --git a/bytes/src/main/java/net/consensys/cava/bytes/ByteBufferWrappingBytes.java b/bytes/src/main/java/net/consensys/cava/bytes/ByteBufferWrappingBytes.java new file mode 100644 index 00000000..83d910d2 --- /dev/null +++ b/bytes/src/main/java/net/consensys/cava/bytes/ByteBufferWrappingBytes.java @@ -0,0 +1,124 @@ +/* + * Copyright 2018, ConsenSys Inc. + * + * Licensed 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. + */ +package net.consensys.cava.bytes; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +class ByteBufferWrappingBytes extends AbstractBytes { + + protected final ByteBuffer byteBuffer; + protected final int offset; + protected final int length; + + ByteBufferWrappingBytes(ByteBuffer byteBuffer) { + this(byteBuffer, 0, byteBuffer.limit()); + } + + ByteBufferWrappingBytes(ByteBuffer byteBuffer, int offset, int length) { + checkArgument(length >= 0, "Invalid negative length"); + int bufferLength = byteBuffer.capacity(); + if (bufferLength > 0) { + checkElementIndex(offset, bufferLength); + } + checkArgument( + offset + length <= bufferLength, + "Provided length %s is too big: the value has only %s bytes from offset %s", + length, + bufferLength - offset, + offset); + + this.byteBuffer = byteBuffer; + this.offset = offset; + this.length = length; + } + + @Override + public int size() { + return length; + } + + @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 == this.length) { + return this; + } + if (length == 0) { + return Bytes.EMPTY; + } + + checkElementIndex(i, this.length); + checkArgument( + i + length <= this.length, + "Provided length %s is too big: the value has size %s and has only %s bytes from %s", + length, + this.length, + this.length - i, + i); + + return new ByteBufferWrappingBytes(byteBuffer, offset + i, length); + } + + // MUST be overridden by mutable implementations + @Override + public Bytes copy() { + if (offset == 0 && length == byteBuffer.limit()) { + return this; + } + return new ArrayWrappingBytes(toArray()); + } + + @Override + public MutableBytes mutableCopy() { + return new MutableArrayWrappingBytes(toArray()); + } + + @Override + public byte[] toArray() { + if (!byteBuffer.hasArray()) { + return super.toArray(); + } + int arrayOffset = byteBuffer.arrayOffset(); + return Arrays.copyOfRange(byteBuffer.array(), arrayOffset + offset, arrayOffset + offset + length); + } + + @Override + public byte[] toArrayUnsafe() { + if (!byteBuffer.hasArray()) { + return toArray(); + } + byte[] array = byteBuffer.array(); + if (array.length != length || byteBuffer.arrayOffset() != 0) { + return toArray(); + } + return array; + } +} diff --git a/bytes/src/main/java/net/consensys/cava/bytes/Bytes.java b/bytes/src/main/java/net/consensys/cava/bytes/Bytes.java new file mode 100644 index 00000000..02d68f31 --- /dev/null +++ b/bytes/src/main/java/net/consensys/cava/bytes/Bytes.java @@ -0,0 +1,1144 @@ +/* + * Copyright 2018, ConsenSys Inc. + * + * Licensed 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. + */ +package net.consensys.cava.bytes; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.String.format; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.util.Arrays; + +import io.netty.buffer.ByteBuf; +import io.vertx.core.buffer.Buffer; + +/** + * A value made of bytes. + * + *
+ * This interface makes no thread-safety guarantee, and a {@link Bytes} value is generally not thread safe. However, + * specific implementations may be thread-safe. For instance, the value returned by {@link #copy} is guaranteed to be + * thread-safe as it is immutable. + */ +public interface Bytes { + + /** + * The empty value (with 0 bytes). + */ + Bytes EMPTY = wrap(new byte[0]); + + /** + * 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}. + */ + 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}. + */ + static Bytes wrap(byte[] value, int offset, int length) { + checkNotNull(value); + if (length == 32) { + return new ArrayWrappingBytes32(value, offset); + } + 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. If copying the inputs is desired, use {@link #concatenate(Bytes...)}. + * + * @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. + */ + static Bytes wrap(Bytes... values) { + return ConcatenatedBytes.wrap(values); + } + + /** + * Create a value containing the concatenation of the values provided. + * + * @param values The values to copy and concatenate. + * @return A value containing the result of concatenating the value from {@code values} in their provided order. + * @throws IllegalArgumentException if the result overflows an int. + */ + static Bytes concatenate(Bytes... values) { + if (values.length == 0) { + return EMPTY; + } + + int size; + try { + size = Arrays.stream(values).mapToInt(Bytes::size).reduce(0, Math::addExact); + } catch (ArithmeticException e) { + throw new IllegalArgumentException("Combined length of values is too long (> Integer.MAX_VALUE)"); + } + + MutableBytes result = MutableBytes.create(size); + int offset = 0; + for (Bytes value : values) { + value.copyTo(result, offset); + offset += value.size(); + } + return result; + } + + /** + * 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. + */ + 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()}. + */ + static Bytes wrapBuffer(Buffer buffer, int offset, int size) { + checkNotNull(buffer); + if (size == 0) { + return EMPTY; + } + 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. + */ + 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()}. + */ + static Bytes wrapByteBuf(ByteBuf byteBuf, int offset, int size) { + checkNotNull(byteBuf); + if (size == 0) { + return EMPTY; + } + 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. + */ + 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()}. + */ + static Bytes wrapByteBuffer(ByteBuffer byteBuffer, int offset, int size) { + checkNotNull(byteBuffer); + if (size == 0) { + return EMPTY; + } + 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. + */ + 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. + */ + 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 bytes value corresponding to the provided value interpreted as an unsigned short value. + * + * @param value The value, which must fit an unsigned short. + * @return A 2 bytes value corresponding to {@code v}. + * @throws IllegalArgumentException if {@code v < 0} or {@code v} is too big to fit an unsigned 2-bytes short (that + * is, if {@code v >= (1 << 16)}). + */ + static Bytes ofUnsignedShort(int value) { + 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]; + res[0] = (byte) ((value >> 8) & 0xFF); + res[1] = (byte) (value & 0xFF); + return Bytes.wrap(res); + } + + /** + * Return a 4 bytes value corresponding to the provided value interpreted as an unsigned int value. + * + * @param value The value, which must fit an unsigned int. + * @return A 4 bytes value corresponding to {@code v}. + * @throws IllegalArgumentException if {@code v < 0} or {@code v} is too big to fit an unsigned 4-bytes int (that is, + * if {@code v >= (1L << 32)}). + */ + static Bytes ofUnsignedInt(long value) { + 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]; + res[0] = (byte) ((value >> 24) & 0xFF); + res[1] = (byte) ((value >> 16) & 0xFF); + res[2] = (byte) ((value >> 8) & 0xFF); + res[3] = (byte) ((value) & 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}. + */ + 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 valid hexadecimal representation. + */ + static Bytes fromHexStringLenient(String 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 valid hexadecimal representation, represents + * more bytes than {@code destinationSize} or {@code destinationSize < 0}. + */ + static Bytes fromHexStringLenient(String 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 valid hexadecimal representation, or is of + * an odd length. + */ + static Bytes fromHexString(String 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 valid hexadecimal representation, or is of an + * odd length. + * @throws IllegalArgumentException if {@code str} does not correspond to valid hexadecimal representation, or is of + * an odd length, or represents more bytes than {@code destinationSize} or {@code destinationSize < 0}. + */ + static Bytes fromHexString(String str, int destinationSize) { + checkNotNull(str); + checkArgument(destinationSize >= 0, "Invalid negative destination size %s", destinationSize); + return BytesValues.fromHexString(str, destinationSize, false); + } + + /** @return The number of bytes this value represents. */ + int 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()}. + */ + 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}. + */ + default int getInt(int i) { + 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; + 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); + 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}. + */ + default int intValue() { + int i = size(); + checkArgument(i <= 4, "Value of size %s has more than 4 bytes", size()); + if (i == 0) { + return 0; + } + 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; + } + + /** + * Whether this value contains no bytes. + * + * @return true if the value contains no bytes + */ + default 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}. + */ + default long getLong(int i) { + 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; + 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); + 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}. + */ + default long longValue() { + int i = size(); + checkArgument(i <= 8, "Value of size %s has more than 8 bytes", size()); + if (i == 0) { + return 0; + } + 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; + } + + /** + * 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. + */ + default BigInteger bigIntegerValue() { + if (size() == 0) { + return BigInteger.ZERO; + } + return new BigInteger(toArrayUnsafe()); + } + + /** + * 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. + */ + default BigInteger unsignedBigIntegerValue() { + return new BigInteger(1, toArrayUnsafe()); + } + + /** + * Whether this value has only zero bytes. + * + * @return true if all the bits of this value are zeros. + */ + default 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 + */ + default boolean hasLeadingZero() { + return size() > 0 && (get(0) & 0x80) == 0; + } + + /** + * @return The number of zero bits preceding the highest-order ("leftmost") one-bit, or {@code size() * 8} if all bits + * are zero. + */ + default 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 + */ + default boolean hasLeadingZeroByte() { + return size() > 0 && get(0) == 0; + } + + /** + * @return The number of leading zero bytes of the value. + */ + default int numberOfLeadingZeroBytes() { + int size = size(); + for (int i = 0; i < size; i++) { + if (get(i) != 0) { + return i - 1; + } + } + return size; + } + + /** + * @return The number of bits following and including the highest-order ("leftmost") one-bit, or zero if all bits are + * zero. + */ + default 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; + } + + /** + * Return a bit-wise AND of these bytes and the supplied bytes. + * + * If this value and the supplied value are different lengths, then the shorter will be zero-padded to the left. + * + * @param other The bytes to perform the operation with. + * @return The result of a bit-wise AND. + */ + default Bytes and(Bytes other) { + return and(other, MutableBytes.create(Math.max(size(), other.size()))); + } + + /** + * Calculate a bit-wise AND of these bytes and the supplied bytes. + * + *
+ * If this value or the supplied value are shorter in length than the output vector, then they will be zero-padded to
+ * the left. Likewise, if either this value or the supplied valid is longer in length than the output vector, then
+ * they will be truncated to the left.
+ *
+ * @param other The bytes to perform the operation with.
+ * @param result The mutable output vector for the result.
+ * @param
+ * If this value and the supplied value are different lengths, then the shorter will be zero-padded to the left.
+ *
+ * @param other The bytes to perform the operation with.
+ * @return The result of a bit-wise OR.
+ */
+ default Bytes or(Bytes other) {
+ return or(other, MutableBytes.create(Math.max(size(), other.size())));
+ }
+
+ /**
+ * Calculate a bit-wise OR of these bytes and the supplied bytes.
+ *
+ *
+ * If this value or the supplied value are shorter in length than the output vector, then they will be zero-padded to
+ * the left. Likewise, if either this value or the supplied valid is longer in length than the output vector, then
+ * they will be truncated to the left.
+ *
+ * @param other The bytes to perform the operation with.
+ * @param result The mutable output vector for the result.
+ * @param
+ * If this value and the supplied value are different lengths, then the shorter will be zero-padded to the left.
+ *
+ * @param other The bytes to perform the operation with.
+ * @return The result of a bit-wise XOR.
+ */
+ default Bytes xor(Bytes other) {
+ return or(other, MutableBytes.create(Math.max(size(), other.size())));
+ }
+
+ /**
+ * Calculate a bit-wise XOR of these bytes and the supplied bytes.
+ *
+ *
+ * If this value or the supplied value are shorter in length than the output vector, then they will be zero-padded to
+ * the left. Likewise, if either this value or the supplied valid is longer in length than the output vector, then
+ * they will be truncated to the left.
+ *
+ * @param other The bytes to perform the operation with.
+ * @param result The mutable output vector for the result.
+ * @param
+ * If this value is shorter in length than the output vector, then it will be zero-padded to the left. Likewise, if
+ * this value is longer in length than the output vector, then it will be truncated to the left.
+ *
+ * @param result The mutable output vector for the result.
+ * @param
+ * If this value is shorter in length than the output vector, then it will be zero-padded to the left. Likewise, if
+ * this value is longer in length than the output vector, then it will be truncated to the left (after shifting).
+ *
+ * @param distance The number of bits to shift by.
+ * @param result The mutable output vector for the result.
+ * @param
+ * If this value is shorter in length than the output vector, then it will be zero-padded to the left. Likewise, if
+ * this value is longer in length than the output vector, then it will be truncated to the left.
+ *
+ * @param distance The number of bits to shift by.
+ * @param result The mutable output vector for the result.
+ * @param
+ * 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. Use {@link #copy} on the
+ * returned slice if that is not what you want.
+ *
+ * @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}.
+ */
+ default Bytes slice(int i) {
+ 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. Use {@link #copy} on the
+ * returned slice if that is not what you want.
+ *
+ * @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()} .
+ */
+ Bytes slice(int i, int length);
+
+ /**
+ * Return a value equivalent to this one but guaranteed to 1) be deeply immutable (i.e. the underlying value will be
+ * immutable) and 2) to not retain more bytes than exposed by the value.
+ *
+ * @return A value, equals to this one, but deeply immutable and that doesn't retain any "unreachable" bytes. For
+ * performance reasons, this is allowed to return this value however if it already fit those constraints.
+ */
+ Bytes copy();
+
+ /**
+ * 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.
+ */
+ MutableBytes mutableCopy();
+
+ /**
+ * Copy the bytes of this value to the provided mutable one, which must have the same size.
+ *
+ * @param destination The mutable value to which to copy the bytes to, which must have the same size as this value. If
+ * you want to copy value where size differs, you should use {@link #slice} and/or
+ * {@link MutableBytes#mutableSlice} and apply the copy to the result.
+ * @throws IllegalArgumentException if {@code this.size() != destination.size()}.
+ */
+ default void copyTo(MutableBytes destination) {
+ checkNotNull(destination);
+ checkArgument(
+ destination.size() == size(),
+ "Cannot copy %s bytes to destination of non-equal size %s",
+ size(),
+ destination.size());
+ copyTo(destination, 0);
+ }
+
+ /**
+ * Copy the bytes of this value to the provided mutable one from a particular offset.
+ *
+ *
+ * This is a (potentially slightly more efficient) shortcut for {@code
+ * copyTo(destination.mutableSlice(destinationOffset, this.size()))}.
+ *
+ * @param destination The mutable value to which to copy the bytes to, which must have enough bytes from
+ * {@code destinationOffset} for the copied value.
+ * @param destinationOffset The offset in {@code destination} at which the copy starts.
+ * @throws IllegalArgumentException if the destination doesn't have enough room, that is if {@code
+ * this.size() > (destination.size() - destinationOffset)}.
+ */
+ default void copyTo(MutableBytes destination, int destinationOffset) {
+ checkNotNull(destination);
+
+ // Special casing an empty source or the following checks might throw (even though we have
+ // nothing to copy anyway) and this gets inconvenient for generic methods using copyTo() as
+ // they may have to special case empty values because of this. As an example,
+ // concatenate(EMPTY, EMPTY) would need to be special cased without this.
+ int size = size();
+ if (size == 0) {
+ return;
+ }
+
+ checkElementIndex(destinationOffset, destination.size());
+ checkArgument(
+ destination.size() - destinationOffset >= size,
+ "Cannot copy %s bytes, destination has only %s bytes from index %s",
+ size,
+ destination.size() - destinationOffset,
+ destinationOffset);
+
+ for (int i = 0; i < size; i++) {
+ destination.set(destinationOffset + i, 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.
+ */
+ default void appendTo(Buffer buffer) {
+ checkNotNull(buffer);
+ for (int i = 0; i < size(); i++) {
+ buffer.appendByte(get(i));
+ }
+ }
+
+ /**
+ * 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.
+ */
+ default 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.
+ */
+ default 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.
+ */
+ default Bytes trimLeadingZeros() {
+ int size = size();
+ for (int i = 0; i < size; i++) {
+ if (get(i) != 0) {
+ return slice(i);
+ }
+ }
+ return Bytes.EMPTY;
+ }
+
+ /**
+ * Update the provided message digest with the bytes of this value.
+ *
+ * @param digest The digest to update.
+ */
+ default void update(MessageDigest digest) {
+ checkNotNull(digest);
+ for (int i = 0; i < size(); i++) {
+ digest.update(get(i));
+ }
+ }
+
+ /**
+ * Extract the bytes of this value into a byte array.
+ *
+ * @return A byte array with the same content than this value.
+ */
+ default byte[] toArray() {
+ int size = size();
+ byte[] array = new byte[size];
+ for (int i = 0; i < size; i++) {
+ array[i] = get(i);
+ }
+ return array;
+ }
+
+ /**
+ * Get the bytes represented by this value as byte array.
+ *
+ *
+ * Contrarily to {@link #toArray()}, 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.
+ */
+ default byte[] toArrayUnsafe() {
+ return toArray();
+ }
+
+ /**
+ * Return the hexadecimal string representation of this value.
+ *
+ * @return The hexadecimal representation of this value, starting with "0x".
+ */
+ @Override
+ String toString();
+
+ /**
+ * @return This value represented as hexadecimal, starting with "0x".
+ */
+ default String toHexString() {
+ int size = size();
+ StringBuilder r = new StringBuilder(2 + size * 2);
+ r.append("0x");
+
+ for (int i = 0; i < size; i++) {
+ byte b = get(i);
+ r.append(AbstractBytes.HEX_CODE[b >> 4 & 15]);
+ r.append(AbstractBytes.HEX_CODE[b & 15]);
+ }
+
+ return r.toString();
+ }
+
+ /** @return This value represented as a minimal hexadecimal string (without any leading zero). */
+ default String toShortHexString() {
+ String hex = toString();
+ // Skipping '0x'
+ if (hex.charAt(2) != '0')
+ return hex;
+
+ int i = 3;
+ while (i < hex.length() && hex.charAt(i) == '0') {
+ i++;
+ }
+ return "0x" + hex.substring(i);
+ }
+}
diff --git a/bytes/src/main/java/net/consensys/cava/bytes/Bytes32.java b/bytes/src/main/java/net/consensys/cava/bytes/Bytes32.java
new file mode 100644
index 00000000..31d1f4f6
--- /dev/null
+++ b/bytes/src/main/java/net/consensys/cava/bytes/Bytes32.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2018, ConsenSys Inc.
+ *
+ * Licensed 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.
+ */
+package net.consensys.cava.bytes;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A {@link Bytes} value that is guaranteed to contain exactly 32 bytes.
+ */
+public interface Bytes32 extends Bytes {
+ /** The number of bytes in this value - i.e. 32 */
+ int SIZE = 32;
+
+ /** A {@code Bytes32} containing all zero bytes */
+ Bytes32 ZERO = wrap(new byte[32]);
+
+ /**
+ * 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}.
+ */
+ static Bytes32 wrap(byte[] bytes) {
+ checkNotNull(bytes);
+ checkArgument(bytes.length == SIZE, "Expected %s bytes but got %s", SIZE, bytes.length);
+ return wrap(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 length < 0 || offset + 32 > value.length}.
+ */
+ static Bytes32 wrap(byte[] bytes, int offset) {
+ checkNotNull(bytes);
+ return new ArrayWrappingBytes32(bytes, offset);
+ }
+
+ /**
+ * 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}.
+ */
+ static Bytes32 wrap(Bytes value) {
+ checkNotNull(value);
+ if (value instanceof Bytes32) {
+ return (Bytes32) value;
+ }
+ return DelegatingBytes32.delegateTo(value);
+ }
+
+ /**
+ * 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()}.
+ */
+ static Bytes32 wrap(Bytes value, int offset) {
+ checkNotNull(value);
+ if (value instanceof Bytes32) {
+ return (Bytes32) value;
+ }
+ Bytes slice = value.slice(offset, Bytes32.SIZE);
+ if (slice instanceof Bytes32) {
+ return (Bytes32) slice;
+ }
+ return DelegatingBytes32.delegateTo(slice);
+ }
+
+ /**
+ * Left pad a {@link Bytes} value with zero bytes to create a {@link Bytes32}.
+ *
+ * @param value The bytes value pad.
+ * @return A {@link Bytes32} that exposes the left-padded bytes of {@code value}.
+ * @throws IllegalArgumentException if {@code value.size() > 32}.
+ */
+ static Bytes32 leftPad(Bytes value) {
+ checkNotNull(value);
+ if (value instanceof Bytes32) {
+ return (Bytes32) value;
+ }
+ checkArgument(value.size() <= SIZE, "Expected at most %s bytes but got %s", SIZE, value.size());
+ MutableBytes32 result = MutableBytes32.create();
+ value.copyTo(result, SIZE - value.size());
+ return result;
+ }
+
+ /**
+ * 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 valid hexadecimal representation or contains
+ * more than 32 bytes.
+ */
+ static Bytes32 fromHexStringLenient(String str) {
+ checkNotNull(str);
+ return wrap(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 valid hexadecimal representation, is of an
+ * odd length, or contains more than 32 bytes.
+ */
+ static Bytes32 fromHexString(String str) {
+ checkNotNull(str);
+ return wrap(BytesValues.fromRawHexString(str, SIZE, false));
+ }
+
+ /**
+ * 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 valid hexadecimal representation, is of an
+ * odd length or does not contain exactly 32 bytes.
+ */
+ static Bytes32 fromHexStringStrict(String str) {
+ checkNotNull(str);
+ return wrap(BytesValues.fromRawHexString(str, -1, false));
+ }
+
+ @Override
+ default int size() {
+ return SIZE;
+ }
+
+ /**
+ * Return a bit-wise AND of these bytes and the supplied bytes.
+ *
+ * @param other The bytes to perform the operation with.
+ * @return The result of a bit-wise AND.
+ */
+ default Bytes32 and(Bytes32 other) {
+ return and(other, MutableBytes32.create());
+ }
+
+ /**
+ * Return a bit-wise OR of these bytes and the supplied bytes.
+ *
+ * @param other The bytes to perform the operation with.
+ * @return The result of a bit-wise OR.
+ */
+ default Bytes32 or(Bytes32 other) {
+ return or(other, MutableBytes32.create());
+ }
+
+ /**
+ * Return a bit-wise XOR of these bytes and the supplied bytes.
+ *
+ * @param other The bytes to perform the operation with.
+ * @return The result of a bit-wise XOR.
+ */
+ default Bytes32 xor(Bytes32 other) {
+ return xor(other, MutableBytes32.create());
+ }
+
+ @Override
+ default Bytes32 not() {
+ return not(MutableBytes32.create());
+ }
+
+ @Override
+ default Bytes32 shiftRight(int distance) {
+ return shiftRight(distance, MutableBytes32.create());
+ }
+
+ @Override
+ default Bytes32 shiftLeft(int distance) {
+ return shiftLeft(distance, MutableBytes32.create());
+ }
+
+ @Override
+ Bytes32 copy();
+
+ @Override
+ MutableBytes32 mutableCopy();
+}
diff --git a/bytes/src/main/java/net/consensys/cava/bytes/BytesValues.java b/bytes/src/main/java/net/consensys/cava/bytes/BytesValues.java
new file mode 100644
index 00000000..d81e6f22
--- /dev/null
+++ b/bytes/src/main/java/net/consensys/cava/bytes/BytesValues.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2018, ConsenSys Inc.
+ *
+ * Licensed 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.
+ */
+package net.consensys.cava.bytes;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+final class BytesValues {
+ private BytesValues() {}
+
+ static final int MAX_UNSIGNED_SHORT = (1 << 16) - 1;
+ static final long MAX_UNSIGNED_INT = (1L << 32) - 1;
+
+ static Bytes fromHexString(String str, int destSize, boolean lenient) {
+ return Bytes.wrap(fromRawHexString(str, destSize, lenient));
+ }
+
+ static byte[] fromRawHexString(String str, int destSize, boolean lenient) {
+ String hex = str;
+ if (str.startsWith("0x")) {
+ hex = str.substring(2);
+ }
+
+ int len = hex.length();
+ int idxShift = 0;
+ if (len % 2 != 0) {
+ if (!lenient) {
+ throw new IllegalArgumentException("Invalid odd-length hex binary representation '" + str + "'");
+ }
+
+ hex = "0" + hex;
+ len += 1;
+ idxShift = 1;
+ }
+
+ int size = len / 2;
+ if (destSize < 0) {
+ destSize = size;
+ } else {
+ checkArgument(
+ size <= destSize,
+ "Hex value %s is too big: expected at most %s bytes but got %s",
+ str,
+ destSize,
+ size);
+ }
+
+ byte[] out = new byte[destSize];
+
+ int destOffset = (destSize - size);
+ for (int i = 0; i < len; i += 2) {
+ int h = hexToBin(hex.charAt(i));
+ int l = hexToBin(hex.charAt(i + 1));
+ if (h == -1) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Illegal character '%c' found at index %d in hex binary representation '%s'",
+ hex.charAt(i),
+ i - idxShift,
+ str));
+ }
+ if (l == -1) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Illegal character '%c' found at index %d in hex binary representation '%s'",
+ hex.charAt(i + 1),
+ i + 1 - idxShift,
+ str));
+ }
+
+ out[destOffset + (i / 2)] = (byte) (h * 16 + l);
+ }
+ return out;
+ }
+
+ private static int hexToBin(char ch) {
+ if ('0' <= ch && ch <= '9') {
+ return ch - 48;
+ } else if ('A' <= ch && ch <= 'F') {
+ return ch - 65 + 10;
+ } else {
+ return 'a' <= ch && ch <= 'f' ? ch - 97 + 10 : -1;
+ }
+ }
+}
diff --git a/bytes/src/main/java/net/consensys/cava/bytes/ConcatenatedBytes.java b/bytes/src/main/java/net/consensys/cava/bytes/ConcatenatedBytes.java
new file mode 100644
index 00000000..310be19f
--- /dev/null
+++ b/bytes/src/main/java/net/consensys/cava/bytes/ConcatenatedBytes.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2018, ConsenSys Inc.
+ *
+ * Licensed 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.
+ */
+package net.consensys.cava.bytes;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkElementIndex;
+
+final class ConcatenatedBytes extends AbstractBytes {
+
+ private final Bytes[] values;
+ private final int size;
+
+ private ConcatenatedBytes(Bytes[] values, int totalSize) {
+ this.values = values;
+ this.size = totalSize;
+ }
+
+ static Bytes wrap(Bytes... values) {
+ if (values.length == 0) {
+ return EMPTY;
+ }
+ if (values.length == 1) {
+ return values[0];
+ }
+
+ 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 Bytes.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) {
+ Bytes[] subvalues = ((ConcatenatedBytes) value).values;
+ System.arraycopy(subvalues, 0, concatenated, i, subvalues.length);
+ i += subvalues.length;
+ } else if (value.size() != 0) {
+ concatenated[i++] = value;
+ }
+ }
+ return new ConcatenatedBytes(concatenated, totalSize);
+ }
+
+ @Override
+ public int size() {
+ return size;
+ }
+
+ @Override
+ public byte get(int i) {
+ checkElementIndex(i, size);
+ 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 i, final int length) {
+ if (i == 0 && length == size) {
+ return this;
+ }
+ if (length == 0) {
+ return Bytes.EMPTY;
+ }
+
+ checkElementIndex(i, size);
+ checkArgument(
+ (i + length) <= size,
+ "Provided length %s is too large: the value has size %s and has only %s bytes from %s",
+ length,
+ size,
+ size - i,
+ i);
+
+ int j = 0;
+ int vSize;
+ while (true) {
+ vSize = values[j].size();
+ if (i < vSize) {
+ break;
+ }
+ i -= vSize;
+ ++j;
+ }
+
+ if ((i + length) < vSize) {
+ return values[j].slice(i, length);
+ }
+
+ int remaining = length - (vSize - i);
+ Bytes firstValue = this.values[j].slice(i);
+ int firstOffset = j;
+
+ while (remaining > 0) {
+ if (++j >= this.values.length) {
+ throw new IllegalStateException("element sizes do not match total size");
+ }
+ vSize = this.values[j].size();
+ if (length < vSize) {
+ break;
+ }
+ remaining -= vSize;
+ }
+
+ Bytes[] combined = new Bytes[j - firstOffset + 1];
+ combined[0] = firstValue;
+ if (remaining > 0) {
+ if (combined.length > 2) {
+ System.arraycopy(this.values, firstOffset + 1, combined, 1, combined.length - 2);
+ }
+ combined[combined.length - 1] = this.values[j].slice(0, remaining);
+ } else if (combined.length > 1) {
+ System.arraycopy(this.values, firstOffset + 1, combined, 1, combined.length - 1);
+ }
+ return new ConcatenatedBytes(combined, length);
+ }
+
+ @Override
+ public Bytes copy() {
+ if (size == 0) {
+ return Bytes.EMPTY;
+ }
+ MutableBytes result = MutableBytes.create(size);
+ copyToUnchecked(result, 0);
+ return result;
+ }
+
+ @Override
+ public MutableBytes mutableCopy() {
+ if (size == 0) {
+ return MutableBytes.EMPTY;
+ }
+ MutableBytes result = MutableBytes.create(size);
+ copyToUnchecked(result, 0);
+ return result;
+ }
+
+ @Override
+ public void copyTo(MutableBytes destination, int destinationOffset) {
+ if (size == 0) {
+ return;
+ }
+
+ checkElementIndex(destinationOffset, destination.size());
+ checkArgument(
+ destination.size() - destinationOffset >= size,
+ "Cannot copy %s bytes, destination has only %s bytes from index %s",
+ size,
+ destination.size() - destinationOffset,
+ destinationOffset);
+
+ copyToUnchecked(destination, destinationOffset);
+ }
+
+ @Override
+ public byte[] toArray() {
+ if (size == 0) {
+ return new byte[0];
+ }
+
+ MutableBytes result = MutableBytes.create(size);
+ copyToUnchecked(result, 0);
+ return result.toArrayUnsafe();
+ }
+
+ private void copyToUnchecked(MutableBytes destination, int destinationOffset) {
+ int offset = 0;
+ for (Bytes value : values) {
+ int vSize = value.size();
+ if ((offset + vSize) > size) {
+ throw new IllegalStateException("element sizes do not match total size");
+ }
+ value.copyTo(destination, destinationOffset);
+ offset += vSize;
+ destinationOffset += vSize;
+ }
+ }
+}
diff --git a/bytes/src/main/java/net/consensys/cava/bytes/DelegatingBytes32.java b/bytes/src/main/java/net/consensys/cava/bytes/DelegatingBytes32.java
new file mode 100644
index 00000000..652a34c8
--- /dev/null
+++ b/bytes/src/main/java/net/consensys/cava/bytes/DelegatingBytes32.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2018, ConsenSys Inc.
+ *
+ * Licensed 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.
+ */
+package net.consensys.cava.bytes;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.math.BigInteger;
+import java.security.MessageDigest;
+
+import io.vertx.core.buffer.Buffer;
+
+final class DelegatingBytes32 implements Bytes32 {
+
+ private final Bytes delegate;
+
+ private DelegatingBytes32(Bytes delegate) {
+ this.delegate = delegate;
+ }
+
+ static Bytes32 delegateTo(Bytes value) {
+ checkArgument(value.size() == SIZE, "Expected %s bytes but got %s", SIZE, value.size());
+ return new DelegatingBytes32(value);
+ }
+
+ @Override
+ public int size() {
+ return Bytes32.SIZE;
+ }
+
+ @Override
+ public byte get(int i) {
+ return delegate.get(i);
+ }
+
+ @Override
+ public int getInt(int i) {
+ return delegate.getInt(i);
+ }
+
+ @Override
+ public int intValue() {
+ return delegate.intValue();
+ }
+
+ @Override
+ public long getLong(int i) {
+ return delegate.getLong(i);
+ }
+
+ @Override
+ public long longValue() {
+ return delegate.longValue();
+ }
+
+ @Override
+ public BigInteger bigIntegerValue() {
+ return delegate.bigIntegerValue();
+ }
+
+ @Override
+ public BigInteger unsignedBigIntegerValue() {
+ return delegate.unsignedBigIntegerValue();
+ }
+
+ @Override
+ public boolean isZero() {
+ return delegate.isZero();
+ }
+
+ @Override
+ public int numberOfLeadingZeros() {
+ return delegate.numberOfLeadingZeros();
+ }
+
+ @Override
+ public int numberOfLeadingZeroBytes() {
+ return delegate.numberOfLeadingZeroBytes();
+ }
+
+ @Override
+ public boolean hasLeadingZeroByte() {
+ return delegate.hasLeadingZeroByte();
+ }
+
+ @Override
+ public boolean hasLeadingZero() {
+ return delegate.hasLeadingZero();
+ }
+
+ @Override
+ public int bitLength() {
+ return delegate.bitLength();
+ }
+
+ @Override
+ public Bytes and(Bytes other) {
+ return delegate.and(other);
+ }
+
+ @Override
+ public
+ * 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}.
+ */
+ static MutableBytes wrap(byte[] value, int offset, int length) {
+ checkNotNull(value);
+ if (length == 32) {
+ return new MutableArrayWrappingBytes32(value, offset);
+ }
+ return new MutableArrayWrappingBytes(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.
+ */
+ static MutableBytes wrapBuffer(Buffer buffer) {
+ checkNotNull(buffer);
+ if (buffer.length() == 0) {
+ return EMPTY;
+ }
+ return new MutableBufferWrappingBytes(buffer);
+ }
+
+ /**
+ * 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 size 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()}.
+ */
+ static MutableBytes wrapBuffer(Buffer buffer, int offset, int size) {
+ checkNotNull(buffer);
+ if (size == 0) {
+ return EMPTY;
+ }
+ return new MutableBufferWrappingBytes(buffer, offset, size);
+ }
+
+ /**
+ * 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.
+ */
+ static MutableBytes wrapByteBuf(ByteBuf byteBuf) {
+ checkNotNull(byteBuf);
+ if (byteBuf.capacity() == 0) {
+ return EMPTY;
+ }
+ return new MutableByteBufWrappingBytes(byteBuf);
+ }
+
+ /**
+ * 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 size 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()}.
+ */
+ static MutableBytes wrapByteBuf(ByteBuf byteBuf, int offset, int size) {
+ checkNotNull(byteBuf);
+ if (size == 0) {
+ return EMPTY;
+ }
+ return new MutableByteBufWrappingBytes(byteBuf, offset, size);
+ }
+
+ /**
+ * 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.
+ */
+ static MutableBytes wrapByteBuffer(ByteBuffer byteBuffer) {
+ checkNotNull(byteBuffer);
+ if (byteBuffer.limit() == 0) {
+ return EMPTY;
+ }
+ return new MutableByteBufferWrappingBytes(byteBuffer);
+ }
+
+ /**
+ * 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 size 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()}.
+ */
+ static MutableBytes wrapByteBuffer(ByteBuffer byteBuffer, int offset, int size) {
+ checkNotNull(byteBuffer);
+ if (size == 0) {
+ return EMPTY;
+ }
+ return new MutableByteBufferWrappingBytes(byteBuffer, offset, size);
+ }
+
+ /**
+ * Set a byte in this value.
+ *
+ * @param i 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()}.
+ */
+ void set(int i, byte b);
+
+ /**
+ * Set the 4 bytes starting at the specified index to the specified integer value.
+ *
+ * @param i The index, which must less than or equal to {@code size() - 4}.
+ * @param value The integer value.
+ * @throws IndexOutOfBoundsException if {@code i < 0} or {@code i > size() - 4}.
+ */
+ default void setInt(int i, int value) {
+ int size = size();
+ checkElementIndex(i, size);
+ if (i > (size - 4)) {
+ throw new IndexOutOfBoundsException(
+ format("Value of size %s has not enough bytes to write a 4 bytes int from index %s", size, i));
+ }
+
+ set(i++, (byte) (value >>> 24));
+ set(i++, (byte) ((value >>> 16) & 0xFF));
+ set(i++, (byte) ((value >>> 8) & 0xFF));
+ set(i, (byte) (value & 0xFF));
+ }
+
+ /**
+ * Set the 8 bytes starting at the specified index to the specified long value.
+ *
+ * @param i The index, which must less than or equal to {@code size() - 8}.
+ * @param value The long value.
+ * @throws IndexOutOfBoundsException if {@code i < 0} or {@code i > size() - 8}.
+ */
+ default void setLong(int i, long value) {
+ int size = size();
+ checkElementIndex(i, size);
+ if (i > (size - 8)) {
+ throw new IndexOutOfBoundsException(
+ format("Value of size %s has not enough bytes to write a 8 bytes long from index %s", size, i));
+ }
+
+ set(i++, (byte) (value >>> 56));
+ set(i++, (byte) ((value >>> 48) & 0xFF));
+ set(i++, (byte) ((value >>> 40) & 0xFF));
+ set(i++, (byte) ((value >>> 32) & 0xFF));
+ set(i++, (byte) ((value >>> 24) & 0xFF));
+ set(i++, (byte) ((value >>> 16) & 0xFF));
+ set(i++, (byte) ((value >>> 8) & 0xFF));
+ set(i, (byte) (value & 0xFF));
+ }
+
+ /**
+ * Create a mutable slice of the bytes of this value.
+ *
+ *
+ * Note: the resulting slice is only a view over the original value. Holding a reference to the returned slice may
+ * hold more memory than the slide represents. Use {@link #copy} on the returned slice to avoid this.
+ *
+ * @param i The start index for the slice.
+ * @param length The length of the resulting value.
+ * @return A new mutable view over the bytes of this value from index {@code i} (included) to index {@code i + length}
+ * (excluded).
+ * @throws IllegalArgumentException if {@code length < 0}.
+ * @throws IndexOutOfBoundsException if {@code i < 0} or {i >= size()} or {i + length > size()} .
+ */
+ MutableBytes mutableSlice(int i, int length);
+
+ /**
+ * Fill all the bytes of this value with the specified byte.
+ *
+ * @param b The byte to use to fill the value.
+ */
+ default void fill(byte b) {
+ int size = size();
+ for (int i = 0; i < size; i++) {
+ set(i, b);
+ }
+ }
+
+ /**
+ * Set all bytes in this value to 0.
+ */
+ default void clear() {
+ fill((byte) 0);
+ }
+}
diff --git a/bytes/src/main/java/net/consensys/cava/bytes/MutableBytes32.java b/bytes/src/main/java/net/consensys/cava/bytes/MutableBytes32.java
new file mode 100644
index 00000000..5aa1a92e
--- /dev/null
+++ b/bytes/src/main/java/net/consensys/cava/bytes/MutableBytes32.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2018, ConsenSys Inc.
+ *
+ * Licensed 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.
+ */
+package net.consensys.cava.bytes;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A mutable {@link Bytes32}, that is a mutable {@link Bytes} value of exactly 32 bytes.
+ */
+public interface MutableBytes32 extends MutableBytes, Bytes32 {
+
+ /**
+ * Create a new mutable 32 bytes value.
+ *
+ * @return A newly allocated {@link MutableBytes} value.
+ */
+ static MutableBytes32 create() {
+ return new MutableArrayWrappingBytes32(new byte[SIZE]);
+ }
+
+ /**
+ * Wrap a 32 bytes array as a mutable 32 bytes value.
+ *
+ * @param value The value to wrap.
+ * @return A {@link MutableBytes32} wrapping {@code value}.
+ * @throws IllegalArgumentException if {@code value.length != 32}.
+ */
+ static MutableBytes32 wrap(byte[] value) {
+ checkNotNull(value);
+ return new MutableArrayWrappingBytes32(value);
+ }
+
+ /**
+ * Wrap a the provided array as a {@link MutableBytes32}.
+ *
+ *
+ * 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[i]}.
+ * @return A {@link MutableBytes32} 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 length < 0 || offset + 32 > value.length}.
+ */
+ static MutableBytes32 wrap(byte[] value, int offset) {
+ checkNotNull(value);
+ return new MutableArrayWrappingBytes32(value, offset);
+ }
+
+ /**
+ * Wrap a the provided value, which must be of size 32, as a {@link MutableBytes32}.
+ *
+ *
+ * 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 MutableBytes32} that exposes the bytes of {@code value}.
+ * @throws IllegalArgumentException if {@code value.size() != 32}.
+ */
+ static MutableBytes32 wrap(MutableBytes value) {
+ checkNotNull(value);
+ if (value instanceof MutableBytes32) {
+ return (MutableBytes32) value;
+ }
+ return DelegatingMutableBytes32.delegateTo(value);
+ }
+
+ /**
+ * Wrap a slice/sub-part of the provided value as a {@link MutableBytes32}.
+ *
+ *
+ * Note that the value is not copied, 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()}.
+ */
+ static MutableBytes32 wrap(MutableBytes value, int offset) {
+ checkNotNull(value);
+ if (value instanceof MutableBytes32) {
+ return (MutableBytes32) value;
+ }
+ MutableBytes slice = value.mutableSlice(offset, Bytes32.SIZE);
+ if (slice instanceof MutableBytes32) {
+ return (MutableBytes32) slice;
+ }
+ return DelegatingMutableBytes32.delegateTo(slice);
+ }
+}
diff --git a/bytes/src/main/java/net/consensys/cava/bytes/package-info.java b/bytes/src/main/java/net/consensys/cava/bytes/package-info.java
new file mode 100644
index 00000000..3707b77a
--- /dev/null
+++ b/bytes/src/main/java/net/consensys/cava/bytes/package-info.java
@@ -0,0 +1,11 @@
+/**
+ * Classes and utilities for working with byte arrays.
+ *
+ *
+ * These classes are included in the standard Cava distribution, or separately when using the gradle dependency
+ * 'net.consensys.cava:cava-bytes' (cava-bytes.jar).
+ */
+@ParametersAreNonnullByDefault
+package net.consensys.cava.bytes;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/bytes/src/test/java/net/consensys/cava/bytes/BufferBytesTest.java b/bytes/src/test/java/net/consensys/cava/bytes/BufferBytesTest.java
new file mode 100644
index 00000000..1fb885be
--- /dev/null
+++ b/bytes/src/test/java/net/consensys/cava/bytes/BufferBytesTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018, ConsenSys Inc.
+ *
+ * Licensed 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.
+ */
+package net.consensys.cava.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.wrapBuffer(Buffer.buffer(new byte[size]));
+ }
+
+ @Override
+ Bytes w(byte[] bytes) {
+ return Bytes.wrapBuffer(Buffer.buffer(Bytes.of(bytes).toArray()));
+ }
+
+ @Override
+ Bytes of(int... bytes) {
+ return Bytes.wrapBuffer(Buffer.buffer(Bytes.of(bytes).toArray()));
+ }
+}
diff --git a/bytes/src/test/java/net/consensys/cava/bytes/ByteBufBytesTest.java b/bytes/src/test/java/net/consensys/cava/bytes/ByteBufBytesTest.java
new file mode 100644
index 00000000..3aa4e1d8
--- /dev/null
+++ b/bytes/src/test/java/net/consensys/cava/bytes/ByteBufBytesTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018, ConsenSys Inc.
+ *
+ * Licensed 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.
+ */
+package net.consensys.cava.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.wrapByteBuf(Unpooled.copiedBuffer(new byte[size]));
+ }
+
+ @Override
+ Bytes w(byte[] bytes) {
+ return Bytes.wrapByteBuf(Unpooled.copiedBuffer(Bytes.of(bytes).toArray()));
+ }
+
+ @Override
+ Bytes of(int... bytes) {
+ return Bytes.wrapByteBuf(Unpooled.copiedBuffer(Bytes.of(bytes).toArray()));
+ }
+}
diff --git a/bytes/src/test/java/net/consensys/cava/bytes/ByteBufferBytesTest.java b/bytes/src/test/java/net/consensys/cava/bytes/ByteBufferBytesTest.java
new file mode 100644
index 00000000..5aac46b9
--- /dev/null
+++ b/bytes/src/test/java/net/consensys/cava/bytes/ByteBufferBytesTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018, ConsenSys Inc.
+ *
+ * Licensed 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.
+ */
+package net.consensys.cava.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.wrapByteBuffer(ByteBuffer.allocate(size));
+ }
+
+ @Override
+ Bytes w(byte[] bytes) {
+ return Bytes.wrapByteBuffer(ByteBuffer.wrap(Bytes.of(bytes).toArray()));
+ }
+
+ @Override
+ Bytes of(int... bytes) {
+ return Bytes.wrapByteBuffer(ByteBuffer.wrap(Bytes.of(bytes).toArray()));
+ }
+}
diff --git a/bytes/src/test/java/net/consensys/cava/bytes/Bytes32Test.java b/bytes/src/test/java/net/consensys/cava/bytes/Bytes32Test.java
new file mode 100644
index 00000000..4859d027
--- /dev/null
+++ b/bytes/src/test/java/net/consensys/cava/bytes/Bytes32Test.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2018, ConsenSys Inc.
+ *
+ * Licensed 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.
+ */
+package net.consensys.cava.bytes;
+
+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 failsWhenWrappingArraySmallerThan32() {
+ Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes32.wrap(new byte[31]));
+ assertEquals("Expected 32 bytes but got 31", exception.getMessage());
+ }
+
+ @Test
+ void failsWhenWrappingArrayLargerThan32() {
+ Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes32.wrap(new byte[33]));
+ assertEquals("Expected 32 bytes but got 33", exception.getMessage());
+ }
+
+ @Test
+ void leftPadAValueToBytes32() {
+ Bytes32 b32 = Bytes32.leftPad(Bytes.of(1, 2, 3));
+ 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 failsWhenLeftPaddingValueLargerThan32() {
+ Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes32.leftPad(MutableBytes.create(33)));
+ assertEquals("Expected at most 32 bytes but got 33", exception.getMessage());
+ }
+}
diff --git a/bytes/src/test/java/net/consensys/cava/bytes/BytesTest.java b/bytes/src/test/java/net/consensys/cava/bytes/BytesTest.java
new file mode 100644
index 00000000..1ad04ece
--- /dev/null
+++ b/bytes/src/test/java/net/consensys/cava/bytes/BytesTest.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright 2018, ConsenSys Inc.
+ *
+ * Licensed 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.
+ */
+package net.consensys.cava.bytes;
+
+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.assertThrows;
+
+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;
+
+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(Object arr) {
+ byte[] bytes = (byte[]) arr;
+ Bytes value = Bytes.wrap(bytes);
+ assertEquals(bytes.length, value.size());
+ assertArrayEquals(value.toArray(), bytes);
+ }
+
+ private static Stream
+ * Note that the given function is run directly on the context and should not block.
+ *
+ * @param vertx The vertx context.
+ * @param action The action to execute.
+ * @return A completion.
+ */
+ static AsyncCompletion runOnContext(Vertx vertx, Runnable action) {
+ requireNonNull(action);
+ CompletableAsyncCompletion completion = AsyncCompletion.incomplete();
+ vertx.runOnContext(ev -> {
+ try {
+ action.run();
+ completion.complete();
+ } catch (Throwable ex) {
+ completion.completeExceptionally(ex);
+ }
+ });
+ return completion;
+ }
+
+ /**
+ * Returns a completion that completes after the given blocking action executes asynchronously on
+ * {@link ForkJoinPool#commonPool()}.
+ *
+ * @param action The blocking action to execute.
+ * @return A completion.
+ */
+ static AsyncCompletion executeBlocking(Runnable action) {
+ requireNonNull(action);
+ CompletableAsyncCompletion completion = AsyncCompletion.incomplete();
+ ForkJoinPool.commonPool().execute(() -> {
+ try {
+ action.run();
+ completion.complete();
+ } catch (Throwable ex) {
+ completion.completeExceptionally(ex);
+ }
+ });
+ return completion;
+ }
+
+ /**
+ * Returns a completion that completes after the given blocking action executes asynchronously on an {@link Executor}.
+ *
+ * @param executor The executor.
+ * @param action The blocking action to execute.
+ * @return A completion.
+ */
+ static AsyncCompletion executeBlocking(Executor executor, Runnable action) {
+ requireNonNull(action);
+ CompletableAsyncCompletion completion = AsyncCompletion.incomplete();
+ executor.execute(() -> {
+ try {
+ action.run();
+ completion.complete();
+ } catch (Throwable ex) {
+ completion.completeExceptionally(ex);
+ }
+ });
+ return completion;
+ }
+
+ /**
+ * Returns a completion that completes after the given blocking action executes asynchronously on a vertx context.
+ *
+ * @param vertx The vertx context.
+ * @param action The blocking action to execute.
+ * @return A completion.
+ */
+ static AsyncCompletion executeBlocking(Vertx vertx, Runnable action) {
+ requireNonNull(action);
+ CompletableAsyncCompletion completion = AsyncCompletion.incomplete();
+ vertx.executeBlocking(future -> {
+ action.run();
+ future.complete();
+ }, false, res -> {
+ if (res.succeeded()) {
+ completion.complete();
+ } else {
+ completion.completeExceptionally(res.cause());
+ }
+ });
+ return completion;
+ }
+
+ /**
+ * Returns a completion that completes after the given blocking action executes asynchronously on a vertx executor.
+ *
+ * @param executor A vertx executor.
+ * @param action The blocking action to execute.
+ * @return A completion.
+ */
+ static AsyncCompletion executeBlocking(WorkerExecutor executor, Runnable action) {
+ requireNonNull(action);
+ CompletableAsyncCompletion completion = AsyncCompletion.incomplete();
+ executor.executeBlocking(future -> {
+ action.run();
+ future.complete();
+ }, false, res -> {
+ if (res.succeeded()) {
+ completion.complete();
+ } else {
+ completion.completeExceptionally(res.cause());
+ }
+ });
+ return completion;
+ }
+
+ /**
+ * Returns true if completed normally, completed exceptionally or cancelled.
+ *
+ * @return true if completed.
+ */
+ boolean isDone();
+
+ /**
+ * Returns true if completed exceptionally or cancelled.
+ *
+ * @return true if completed exceptionally or cancelled.
+ */
+ boolean isCompletedExceptionally();
+
+ /**
+ * Attempt to cancel execution of this task.
+ *
+ *
+ * This attempt will fail if the task has already completed, has already been cancelled, or could not be cancelled for
+ * some other reason. If successful, and this task has not started when {@code cancel} is called, this task should
+ * never run.
+ *
+ *
+ * After this method returns, subsequent calls to {@link #isDone()} will always return true. Subsequent calls
+ * to {@link #isCancelled()} will always return true if this method returned true.
+ *
+ * @return true if this completion transitioned to a cancelled state.
+ */
+ boolean cancel();
+
+ /**
+ * Returns true if this task was cancelled before it completed normally.
+ *
+ * @return true if completed.
+ */
+ boolean isCancelled();
+
+ /**
+ * Waits if necessary for the computation to complete.
+ *
+ * @throws CompletionException If the computation threw an exception.
+ * @throws InterruptedException If the current thread was interrupted while waiting.
+ */
+ void join() throws CompletionException, InterruptedException;
+
+ /**
+ * Waits if necessary for at most the given time for the computation to complete.
+ *
+ * @param timeout The maximum time to wait.
+ * @param unit The time unit of the timeout argument.
+ * @throws CompletionException If the computation threw an exception.
+ * @throws TimeoutException If the wait timed out.
+ * @throws InterruptedException If the current thread was interrupted while waiting.
+ */
+ void join(long timeout, TimeUnit unit) throws CompletionException, TimeoutException, InterruptedException;
+
+ /**
+ * Returns a new completion that, when this completion completes normally, completes with the same value or exception
+ * as the result returned after executing the given function.
+ *
+ * @param fn The function returning a new result.
+ * @param The type of the returned result's value.
+ * @return A new result.
+ */
+ AsyncResult then(Supplier extends AsyncResult> fn);
+
+ /**
+ * Returns a new result that, when this completion completes normally, completes with the same value or exception as
+ * the completion returned after executing the given function on the vertx context.
+ *
+ * @param vertx The vertx context.
+ * @param fn The function returning a new result.
+ * @param The type of the returned result's value.
+ * @return A new result.
+ */
+ AsyncResult thenSchedule(Vertx vertx, Supplier extends AsyncResult> fn);
+
+ /**
+ * Returns a new completion that, when this completion completes normally, completes after given action is executed.
+ *
+ * @param runnable Te action to perform before completing the returned {@link AsyncCompletion}.
+ * @return A completion.
+ */
+ AsyncCompletion thenRun(Runnable runnable);
+
+ /**
+ * Returns a new completion that, when this completion completes normally, completes after the given action is
+ * executed on the vertx context.
+ *
+ * @param vertx The vertx context.
+ * @param runnable The action to execute on the vertx context before completing the returned completion.
+ * @return A completion.
+ */
+ AsyncCompletion thenScheduleRun(Vertx vertx, Runnable runnable);
+
+ /**
+ * Returns a new completion that, when this completion completes normally, completes after the given blocking action
+ * is executed on the vertx context.
+ *
+ * @param vertx The vertx context.
+ * @param runnable The action to execute on the vertx context before completing the returned completion.
+ * @return A completion.
+ */
+ AsyncCompletion thenScheduleBlockingRun(Vertx vertx, Runnable runnable);
+
+ /**
+ * Returns a new completion that, when this completion completes normally, completes after the given blocking action
+ * is executed on the vertx executor.
+ *
+ * @param executor The vertx executor.
+ * @param runnable The action to execute on the vertx context before completing the returned completion.
+ * @return A completion.
+ */
+ AsyncCompletion thenScheduleBlockingRun(WorkerExecutor executor, Runnable runnable);
+
+ /**
+ * When this result completes normally, invokes the given function with the resulting value and obtain a new
+ * {@link AsyncCompletion}.
+ *
+ * @param fn The function returning a new completion.
+ * @return A completion.
+ */
+ AsyncCompletion thenCompose(Supplier extends AsyncCompletion> fn);
+
+ /**
+ * Returns a completion that, when this result completes normally, completes with the value obtained after executing
+ * the supplied function.
+ *
+ * @param supplier The function to use to compute the value of the returned result.
+ * @param The function's return type.
+ * @return A new result.
+ */
+ AsyncResult thenSupply(Supplier extends U> supplier);
+
+ /**
+ * Returns a completion that, when this result completes normally, completes with the value obtained after executing
+ * the supplied function on the vertx context.
+ *
+ * @param vertx The vertx context.
+ * @param supplier The function to use to compute the value of the returned result.
+ * @param The function's return type.
+ * @return A new result.
+ */
+ AsyncResult thenSupply(Vertx vertx, Supplier extends U> supplier);
+
+ /**
+ * Returns a completion that, when this completion and the supplied result both complete normally, completes after
+ * executing the supplied function with the value from the supplied result as an argument.
+ *
+ * @param other The other result.
+ * @param consumer The function to execute.
+ * @param The type of the other's value.
+ * @return A new result.
+ */
+ AsyncCompletion thenConsume(AsyncResult extends U> other, Consumer super U> consumer);
+
+ /**
+ * Returns a result that, when this completion and the other result both complete normally, completes with the value
+ * obtained from executing the supplied function with the value from the other result as an argument.
+ *
+ * @param other The other result.
+ * @param fn The function to execute.
+ * @param The type of the other's value.
+ * @param
+ * The exception supplied to the function will be {@code null} if this completion completes successfully.
+ *
+ * @param consumer The action to execute.
+ * @return A new result.
+ */
+ AsyncCompletion whenComplete(Consumer super Throwable> consumer);
+
+ /**
+ * Returns a new result that, when this result completes either normally or exceptionally, completes with the value
+ * obtained from executing the supplied function with this result's exception (if any) as an argument.
+ *
+ * The exception supplied to the function will be {@code null} if this completion completes successfully.
+ *
+ * @param fn The function to execute.
+ * @param The type of the value returned from the function.
+ * @return A new result.
+ */
+ AsyncResult handle(Function super Throwable, ? extends U> fn);
+
+ /**
+ * Returns a new completion that completes successfully, after executing the given function with this completion's
+ * exception (if any).
+ *
+ * The exception supplied to the function will be {@code null} if this completion completes successfully.
+ *
+ * @param consumer The action to execute.
+ * @return A new result.
+ */
+ AsyncCompletion accept(Consumer super Throwable> consumer);
+}
diff --git a/concurrent/src/main/java/net/consensys/cava/concurrent/AsyncResult.java b/concurrent/src/main/java/net/consensys/cava/concurrent/AsyncResult.java
new file mode 100644
index 00000000..6a99ac2d
--- /dev/null
+++ b/concurrent/src/main/java/net/consensys/cava/concurrent/AsyncResult.java
@@ -0,0 +1,515 @@
+/*
+ * Copyright 2018, ConsenSys Inc.
+ *
+ * Licensed 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.
+ */
+package net.consensys.cava.concurrent;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.*;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.*;
+import java.util.stream.Stream;
+
+import io.vertx.core.Vertx;
+import io.vertx.core.WorkerExecutor;
+
+/**
+ * A result that will be available at a future time.
+ *
+ * @param
+ * Note that the given function is run directly on the context and should not block.
+ *
+ * @param vertx The vertx context.
+ * @param fn The function returning a result.
+ * @param
+ * This attempt will fail if the task has already completed, has already been cancelled, or could not be cancelled for
+ * some other reason. If successful, and this task has not started when {@code cancel} is called, this task should
+ * never run.
+ *
+ *
+ * After this method returns, subsequent calls to {@link #isDone()} will always return true. Subsequent calls
+ * to {@link #isCancelled()} will always return true if this method returned true.
+ *
+ * @return true if this result transitioned to a cancelled state.
+ */
+ boolean cancel();
+
+ /**
+ * Returns true if this task was cancelled before it completed normally.
+ *
+ * @return true if completed.
+ */
+ boolean isCancelled();
+
+ /**
+ * Waits if necessary for the computation to complete, and then retrieves its result.
+ *
+ * @return The computed result.
+ * @throws CompletionException If the computation threw an exception.
+ * @throws InterruptedException If the current thread was interrupted while waiting.
+ */
+ T get() throws CompletionException, InterruptedException;
+
+ /**
+ * Waits if necessary for at most the given time for the computation to complete, and then retrieves its result.
+ *
+ * @param timeout The maximum time to wait.
+ * @param unit The time unit of the timeout argument.
+ * @return The computed result.
+ * @throws CompletionException If the computation threw an exception.
+ * @throws TimeoutException If the wait timed out.
+ * @throws InterruptedException If the current thread was interrupted while waiting.
+ */
+ T get(long timeout, TimeUnit unit) throws CompletionException, TimeoutException, InterruptedException;
+
+ /**
+ * Returns a new result that, when this result completes normally, completes with the same value or exception as the
+ * result returned after executing the given function with this results value as an argument.
+ *
+ * @param fn The function returning a new result.
+ * @param The type of the returned result's value.
+ * @return A new result.
+ */
+ AsyncResult then(Function super T, ? extends AsyncResult> fn);
+
+ /**
+ * Returns a new result that, when this result completes normally, completes with the same value or exception as the
+ * completion returned after executing the given function on the vertx context with this results value as an argument.
+ *
+ * @param vertx The vertx context.
+ * @param fn The function returning a new result.
+ * @param The type of the returned result's value.
+ * @return A new result.
+ */
+ AsyncResult thenSchedule(Vertx vertx, Function super T, ? extends AsyncResult> fn);
+
+ /**
+ * When this result completes normally, invokes the given function with the resulting value and obtains a new
+ * {@link AsyncCompletion}.
+ *
+ * @param fn The function returning a new completion.
+ * @return A completion.
+ */
+ AsyncCompletion thenCompose(Function super T, ? extends AsyncCompletion> fn);
+
+ /**
+ * Returns a new completion that, when this result completes normally, completes after given action is executed.
+ *
+ * @param runnable The action to execute before completing the returned completion.
+ * @return A completion.
+ */
+ AsyncCompletion thenRun(Runnable runnable);
+
+ /**
+ * Returns a new completion that, when this result completes normally, completes after the given action is executed on
+ * the vertx context.
+ *
+ * @param vertx The vertx context.
+ * @param runnable The action to execute on the vertx context before completing the returned completion.
+ * @return A completion.
+ */
+ AsyncCompletion thenScheduleRun(Vertx vertx, Runnable runnable);
+
+ /**
+ * Returns a new completion that, when this result completes normally, completes after the given blocking action is
+ * executed on the vertx context.
+ *
+ * @param vertx The vertx context.
+ * @param runnable The action to execute on the vertx context before completing the returned completion.
+ * @return A completion.
+ */
+ AsyncCompletion thenScheduleBlockingRun(Vertx vertx, Runnable runnable);
+
+ /**
+ * Returns a new completion that, when this result completes normally, completes after the given blocking action is
+ * executed on the vertx executor.
+ *
+ * @param executor The vertx executor.
+ * @param runnable The action to execute on the vertx context before completing the returned completion.
+ * @return A completion.
+ */
+ AsyncCompletion thenScheduleBlockingRun(WorkerExecutor executor, Runnable runnable);
+
+ /**
+ * Returns a result that, when this result completes normally, completes with the value obtained from executing the
+ * supplied function with this result's value as an argument.
+ *
+ * @param fn The function to use to compute the value of the returned result.
+ * @param The function's return type.
+ * @return A new result.
+ */
+ AsyncResult thenApply(Function super T, ? extends U> fn);
+
+ /**
+ * Returns a result that, when this result completes normally, completes with the value obtained from executing the
+ * supplied function on the vertx context with this result's value as an argument.
+ *
+ * @param vertx The vertx context.
+ * @param fn The function to use to compute the value of the returned result.
+ * @param The function's return type.
+ * @return A new result.
+ */
+ AsyncResult thenScheduleApply(Vertx vertx, Function super T, ? extends U> fn);
+
+ /**
+ * Returns a result that, when this result completes normally, completes with the value obtained from executing the
+ * supplied blocking function on the vertx context with this result's value as an argument.
+ *
+ * @param vertx The vertx context.
+ * @param fn The function to use to compute the value of the returned result.
+ * @param The function's return type.
+ * @return A new result.
+ */
+ AsyncResult thenScheduleBlockingApply(Vertx vertx, Function super T, ? extends U> fn);
+
+ /**
+ * Returns a result that, when this result completes normally, completes with the value obtained from executing the
+ * supplied blocking function on the vertx executor with this result's value as an argument.
+ *
+ * @param executor The vertx executor.
+ * @param fn The function to use to compute the value of the returned result.
+ * @param The function's return type.
+ * @return A new result.
+ */
+ AsyncResult thenScheduleBlockingApply(WorkerExecutor executor, Function super T, ? extends U> fn);
+
+ /**
+ * Returns a completion that, when this result completes normally, completes after executing the supplied consumer
+ * with this result's value as an argument.
+ *
+ * @param consumer The consumer for the value of this result.
+ * @return A completion.
+ */
+ AsyncCompletion thenAccept(Consumer super T> consumer);
+
+ /**
+ * Returns a completion that, when this result and the other result both complete normally, completes after executing
+ * the supplied consumer with both this result's value and the value from the other result as arguments.
+ *
+ * @param other The other result.
+ * @param consumer The consumer for both values.
+ * @param The type of the other's value.
+ * @return A completion.
+ */
+ AsyncCompletion thenAcceptBoth(AsyncResult extends U> other, BiConsumer super T, ? super U> consumer);
+
+ /**
+ * Returns a result that, when this result and the other result both complete normally, completes with the value
+ * obtained from executing the supplied function with both this result's value and the value from the other result as
+ * arguments.
+ *
+ * @param other The other result.
+ * @param fn The function to execute.
+ * @param The type of the other's value.
+ * @param
+ * Either the value or the exception supplied to the action will be {@code null}.
+ *
+ * @param action The action to execute.
+ * @return A new result.
+ */
+ AsyncResult
+ * Either the value or the exception supplied to the function will be {@code null}.
+ *
+ * @param fn The function to execute.
+ * @param The type of the value returned from the function.
+ * @return A new result.
+ */
+ AsyncResult handle(BiFunction super T, Throwable, ? extends U> fn);
+
+ /**
+ * Returns a new completion that, when this result completes either normally or exceptionally, completes after
+ * executing the supplied function with this result's value and exception as arguments.
+ *
+ * Either the value or the exception supplied to the function will be {@code null}.
+ *
+ * @param consumer The consumer to execute.
+ * @return A completion.
+ */
+ AsyncCompletion accept(BiConsumer super T, Throwable> consumer);
+}
diff --git a/concurrent/src/main/java/net/consensys/cava/concurrent/AtomicSlotMap.java b/concurrent/src/main/java/net/consensys/cava/concurrent/AtomicSlotMap.java
new file mode 100644
index 00000000..53f97b3f
--- /dev/null
+++ b/concurrent/src/main/java/net/consensys/cava/concurrent/AtomicSlotMap.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2018, ConsenSys Inc.
+ *
+ * Licensed 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.
+ */
+package net.consensys.cava.concurrent;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import com.google.common.collect.DiscreteDomain;
+
+/**
+ * An atomic map that locates available keys within a {@link DiscreteDomain}.
+ *
+ *
+ * This is an atomic map that will allocate key slots based on availability. It will attempt to keep the range compact
+ * by filling slots as they become available.
+ *
+ * This implementation should be used with small sets, as addition is an O(N) operation.
+ *
+ * @param
+ * These classes are included in the standard Cava distribution, or separately when using the gradle dependency
+ * 'net.consensys.cava:cava-concurrent' (cava-concurrent.jar).
+ */
+@ParametersAreNonnullByDefault
+package net.consensys.cava.concurrent;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/concurrent/src/main/kotlin/net/consensys/cava/concurrent/coroutines/experimental/AsyncCompletion.kt b/concurrent/src/main/kotlin/net/consensys/cava/concurrent/coroutines/experimental/AsyncCompletion.kt
new file mode 100644
index 00000000..c698e26f
--- /dev/null
+++ b/concurrent/src/main/kotlin/net/consensys/cava/concurrent/coroutines/experimental/AsyncCompletion.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2018, ConsenSys Inc.
+ *
+ * Licensed 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.
+ */
+package net.consensys.cava.concurrent.coroutines.experimental
+
+import kotlinx.coroutines.experimental.CancellableContinuation
+import kotlinx.coroutines.experimental.CommonPool
+import kotlinx.coroutines.experimental.CompletableDeferred
+import kotlinx.coroutines.experimental.CoroutineDispatcher
+import kotlinx.coroutines.experimental.CoroutineScope
+import kotlinx.coroutines.experimental.CoroutineStart
+import kotlinx.coroutines.experimental.DefaultDispatcher
+import kotlinx.coroutines.experimental.Deferred
+import kotlinx.coroutines.experimental.Job
+import kotlinx.coroutines.experimental.newCoroutineContext
+import kotlinx.coroutines.experimental.suspendCancellableCoroutine
+import net.consensys.cava.concurrent.AsyncCompletion
+import net.consensys.cava.concurrent.CompletableAsyncCompletion
+import java.util.concurrent.CancellationException
+import java.util.concurrent.CompletionException
+import java.util.function.Consumer
+import kotlin.coroutines.experimental.Continuation
+import kotlin.coroutines.experimental.ContinuationInterceptor
+import kotlin.coroutines.experimental.CoroutineContext
+
+/**
+ * Starts new coroutine and returns its result as an [AsyncCompletion].
+ *
+ * This coroutine builder uses [CommonPool] context by default and is conceptually similar to
+ * [AsyncCompletion.executeBlocking].
+ *
+ * The running coroutine is cancelled when the [AsyncCompletion] is cancelled or otherwise completed.
+ *
+ * The [context] for the new coroutine can be explicitly specified. See [CoroutineDispatcher] for the standard context
+ * implementations that are provided by `kotlinx.coroutines`. The [context][CoroutineScope.coroutineContext] of the
+ * parent coroutine from its [scope][CoroutineScope] may be used, in which case the [Job] of the resulting coroutine is
+ * a child of the job of the parent coroutine. The parent job may be also explicitly specified using [parent]
+ * parameter.
+ *
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is
+ * used.
+ *
+ * By default, the coroutine is immediately scheduled for execution. Other options can be specified via `start`
+ * parameter. See [CoroutineStart] for details. A value of [CoroutineStart.LAZY] is not supported (since
+ * [AsyncCompletion] does not provide the corresponding capability) and produces [IllegalArgumentException].
+ *
+ * See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
+ *
+ * @param context context of the coroutine. The default value is [DefaultDispatcher].
+ * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
+ * @param parent explicitly specifies the parent job, overrides job from the [context] (if any).
+ * @param block the coroutine code.
+ */
+fun asyncCompletion(
+ context: CoroutineContext = DefaultDispatcher,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ parent: Job? = null,
+ block: suspend CoroutineScope.() -> Unit
+): AsyncCompletion {
+ require(!start.isLazy) { "$start start is not supported" }
+ val newContext = newCoroutineContext(context, parent)
+ val job = Job(newContext[Job])
+ val coroutine = AsyncCompletionCoroutine(newContext + job)
+ job.invokeOnCompletion { coroutine.completion.cancel() }
+ coroutine.completion.whenComplete { exception -> job.cancel(exception) }
+ start(block, receiver = coroutine, completion = coroutine) // use the specified start strategy
+ return coroutine.completion
+}
+
+private class AsyncCompletionCoroutine(
+ override val context: CoroutineContext,
+ val completion: CompletableAsyncCompletion = AsyncCompletion.incomplete()
+) : Continuation> combine(Collection extends AsyncResult extends T>> rs) {
+ return combine(rs.stream());
+ }
+
+ /**
+ * Returns a result that completes when all of the given results complete. If any results complete exceptionally, then
+ * the resulting completion also completes exceptionally.
+ *
+ * @param
> combine(Stream extends AsyncResult extends T>> rs) {
+ Stream