pax_global_header00006660000000000000000000000064134175077210014521gustar00rootroot0000000000000052 comment=5300bcf6a23c0de6c658fb5650767eabac0fe684 cava-0.6.0/000077500000000000000000000000001341750772100124365ustar00rootroot00000000000000cava-0.6.0/.circleci/000077500000000000000000000000001341750772100142715ustar00rootroot00000000000000cava-0.6.0/.circleci/config.yml000066400000000000000000000103611341750772100162620ustar00rootroot00000000000000version: 2 jobs: build: docker: - image: circleci/openjdk:11-jdk-sid working_directory: ~/repo environment: TERM: dumb JAVA_TOOL_OPTIONS: -Xmx768m 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" BINTRAY_DEPLOY=true ./gradlew deploy else echo "Start dry run deployment" ./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" }} cava-0.6.0/.editorconfig000066400000000000000000000001441341750772100151120ustar00rootroot00000000000000[*.{kt,kts}] indent_size=2 continuation_indent_size=2 insert_final_newline=true max_line_length=120 cava-0.6.0/.gitattributes000066400000000000000000000000461341750772100153310ustar00rootroot00000000000000* text eol=lf *.jar -text *.bat -text cava-0.6.0/.gitignore000066400000000000000000000004061341750772100144260ustar00rootroot00000000000000*.bak *.swp *.tmp *~.nib *.iml *.launch *.swp *.tokens .classpath .externalToolBuilders/ .gradle/ .idea/* !.idea/codeStyles/ .loadpath .metadata .prefs .project .recommenders/ .settings .springBeans .vertx bin/ classes/ local.properties target/ tmp/ build/ out/ cava-0.6.0/.gitmodules000066400000000000000000000002351341750772100146130ustar00rootroot00000000000000[submodule "eth-reference-tests/src/test/resources/tests"] path = eth-reference-tests/src/test/resources/tests url = https://github.com/ethereum/tests.git cava-0.6.0/.idea/000077500000000000000000000000001341750772100134165ustar00rootroot00000000000000cava-0.6.0/.idea/codeStyles/000077500000000000000000000000001341750772100155345ustar00rootroot00000000000000cava-0.6.0/.idea/codeStyles/Project.xml000066400000000000000000000013421341750772100176640ustar00rootroot00000000000000 cava-0.6.0/.idea/codeStyles/codeStyleConfig.xml000066400000000000000000000002161341750772100213360ustar00rootroot00000000000000 cava-0.6.0/CONTRIBUTING.md000066400000000000000000000066341341750772100147000ustar00rootroot00000000000000# 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. cava-0.6.0/LICENSE000066400000000000000000000261351341750772100134520ustar00rootroot00000000000000 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. cava-0.6.0/PACKAGES.md000066400000000000000000000137551341750772100141510ustar00rootroot00000000000000# 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 complete 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 complete Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-concurrent` (`cava-concurrent.jar`). # Package net.consensys.cava.concurrent.coroutines 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.config A general-purpose library for managing configuration data. These classes are included in the complete Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-config` (`cava-config.jar`). # Package net.consensys.cava.crypto Classes and utilities for working with cryptography. These classes are included in the complete 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 depend upon the JNR-FFI library, which is not automatically included when using the complete Cava distribution. 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.devp2p Kotlin coroutine based implementation of the Ethereum ÐΞVp2p protocol. These classes are included in the complete Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-devp2p` (`cava-devp2p.jar`). # Package net.consensys.cava.eth Classes and utilities for working in the Ethereum domain. These classes are included in the complete Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-eth` (`cava-eth.jar`). # Package net.consensys.cava.io Classes and utilities for handling file and network IO. These classes are included in the complete 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 complete Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-junit` (`cava-junit.jar`). # Package net.consensys.cava.kademlia An implementation of the kademlia distributed hash (routing) table. These classes are included in the complete Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-kademlia` (`cava-kademlia.jar`). # Package net.consensys.cava.kv Classes and utilities for working with key/value stores. These classes are included in the complete 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 complete Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-net` (`cava-net.jar`). # Package net.consensys.cava.net.coroutines Classes and utilities for coroutine based networking. These classes are included in the complete Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-net-coroutines` (`cava-net-coroutines.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 complete 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 complete 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 complete Cava distribution, or separately when using the gradle dependency `net.consensys.cava:cava-merkle-trie` (`cava-merkle-trie.jar`). # Package net.consensys.cava.trie 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 complete 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. cava-0.6.0/README.md000066400000000000000000000046041341750772100137210ustar00rootroot00000000000000# 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.5.0) ](https://bintray.com/consensys/consensys/cava/0.5.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/), synced to JCenter and Maven Central. You can import all modules using the cava jar. With Maven: ```xml net.consensys.cava cava 0.5.0 ``` With Gradle: `compile 'net.consensys.cava:cava:0.5.0'` [PACKAGES.md](PACKAGES.md) contains the list of modules and instructions to import them separately. ## Build Instructions To build, clone this repo and run with `./gradlew` like so: ```sh git clone --recursive https://github.com/ConsenSys/cava cd cava ./gradlew ``` After a successful build, libraries will be available in `build/libs`. ## Links - [GitHub project](https://github.com/ConsenSys/cava) - [Online Kotlin documentation](https://consensys.github.io/cava/docs/kotlin/0.5.0/cava) - [Online Java documentation](https://consensys.github.io/cava/docs/java/0.5.0) - [Issue tracker: Report a defect or feature request](https://github.com/ConsenSys/cava/issues/new) - [StackOverflow: Ask "how-to" and "why-didn't-it-work" questions](https://stackoverflow.com/questions/ask?tags=cava+java) - [cava-discuss: For open-ended questions and discussion](http://groups.google.com/group/cava-discuss) cava-0.6.0/build.gradle000066400000000000000000000336741341750772100147320ustar00rootroot00000000000000import java.util.regex.Pattern import net.ltgt.gradle.errorprone.CheckSeverity buildscript { repositories { maven { url 'https://consensys.bintray.com/consensys/' } jcenter() } } plugins { id 'com.diffplug.gradle.spotless' version '3.16.0' id 'net.ltgt.errorprone' version '0.6' id 'io.spring.dependency-management' version '1.0.6.RELEASE' id 'com.github.hierynomus.license' version '0.15.0' id 'com.jfrog.bintray' version '1.8.3' id 'org.jetbrains.kotlin.jvm' version '1.3.11' id 'org.jetbrains.dokka' version '0.9.17' } description = 'A set of libraries and other tools to aid development of blockchain and other decentralized software in Java and other JVM languages' ////// // Sanity checks if (!file("${rootDir}/eth-reference-tests/src/test/resources/tests/README.md").exists()) { throw new GradleException("eth-reference-tests/src/test/resources/tests/README.md missing: please clone submodules (git submodule update --init --recursive)") } ////// // Version numbering def versionNumber = '0.6.0' def buildVersion = versionNumber + buildTag() static String buildTag() { if (System.getenv('BUILD_RELEASE') == 'true') { return '' } if (System.getenv('CIRCLECI')) { def buildNumber = System.getenv('CIRCLE_SHA1').take(4).toUpperCase() + String.format('%02X', System.getenv('CIRCLE_BUILD_NUM').toInteger() % 256, 16) return '-' + buildNumber + '-snapshot' } return '-dev' } ////// // Default tasks and build aliases defaultTasks 'checkLicenses', 'spotlessCheck', 'jar', 'test', ':javadoc' def buildAliases = ['dev': [ 'spotlessApply', 'checkLicenses', ':jar', 'test', ':javadoc' ]] def expandedTaskList = [] gradle.startParameter.taskNames.each { expandedTaskList << (buildAliases[it] ? buildAliases[it] : it) } gradle.startParameter.taskNames = expandedTaskList.flatten() ////// // Gradle script formatting spotless { groovyGradle { target '**/*.gradle' greclipse().configFile(rootProject.file('gradle/greclipse-gradle-consensys-style.properties')) endWithNewline() } } subprojects { ////// // Source formatting apply plugin: 'com.diffplug.gradle.spotless' spotless { java { target project.fileTree(project.projectDir) { include '**/*.java' exclude '**/generated-src/**/*.*' } removeUnusedImports() licenseHeaderFile rootProject.file('gradle/spotless.license.java') eclipse().configFile(rootProject.file('gradle/eclipse-java-consensys-style.xml')) importOrder 'net.consensys', 'java', '' endWithNewline() } kotlin { licenseHeaderFile rootProject.file('gradle/spotless.license.java') ktlint().userData(['indent_size': '2', 'continuation_indent_size' : '2', 'max_line_length': '120']) endWithNewline() } } ////// // Parallel build execution tasks.withType(Test) { // If GRADLE_MAX_TEST_FORKS is not set, use half the available processors maxParallelForks = (System.getenv('GRADLE_MAX_TEST_FORKS') ?: (Runtime.runtime.availableProcessors().intdiv(2) ?: 1)).toInteger() } tasks.withType(JavaCompile) { options.fork = true options.incremental = true } task allDependencies(type: DependencyReportTask) {} } ////// // Top-level target for deploy (bintrayUpload depends on it) task deploy() {} configurations.archives.artifacts.removeAll { PublishArtifact publishArtifact -> (publishArtifact.type == 'jar' && publishArtifact.name == 'cava')} ////// // Project defaults allprojects { apply plugin: 'java-library' apply plugin: 'kotlin' apply plugin: 'io.spring.dependency-management' apply plugin: 'jacoco' apply plugin: 'net.ltgt.errorprone' apply plugin: 'com.jfrog.bintray' apply plugin: 'maven-publish' apply plugin: 'org.jetbrains.dokka' apply plugin: 'signing' apply from: "${rootDir}/dependency-versions.gradle" apply from: "${rootDir}/gradle/check-licenses.gradle" version = buildVersion repositories { jcenter() } ////// // Compiler arguments sourceCompatibility = '1.8' targetCompatibility = '1.8' jacoco { toolVersion = '0.8.2' } dependencies { errorprone 'com.google.errorprone:error_prone_core' if (JavaVersion.current().isJava8()) { errorproneJavac("com.google.errorprone:javac") } } tasks.withType(JavaCompile) { options.compilerArgs += [ '-Xlint:unchecked', '-Xlint:cast', '-Xlint:rawtypes', '-Xlint:overloads', '-Xlint:divzero', '-Xlint:finally', '-Xlint:static', '-Werror' ] options.errorprone { excludedPaths '.*/generated-src/.*' check('FutureReturnValueIgnored', CheckSeverity.OFF) check('UnnecessaryParentheses', CheckSeverity.OFF) disableWarningsInGeneratedCode = true } } tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { jvmTarget = "1.8" allWarningsAsErrors = true freeCompilerArgs = [ '-Xjsr305=strict', '-Xjvm-default=enable', '-Xuse-experimental=kotlin.Experimental' ] } } ////// // Use JUnit5 for testing test { useJUnitPlatform() { includeEngines 'spek', 'junit-jupiter' } } ////// // Documentation dokka { outputFormat = 'html' outputDirectory = "$buildDir/docs/dokka" jdkVersion = 8 includeNonPublic = false def relativePath = rootDir.toPath().relativize(projectDir.toPath()).toString() linkMapping { dir = projectDir.toString() url = "https://github.com/consensys/cava/blob/master/$relativePath" suffix = "#L" } } ////// // Artifact locations jar { destinationDir = file("${rootProject.buildDir}/libs") } task sourcesJar(type: Jar, dependsOn: classes) { destinationDir = file("${rootProject.buildDir}/src") classifier = 'sources' from sourceSets.main.allSource } task javadocJar(type: Jar, dependsOn: javadoc) { destinationDir = file("${rootProject.buildDir}/docs") classifier = 'javadoc' from javadoc.destinationDir } task dokkaJar(type: Jar, dependsOn: dokka) { destinationDir = file("${rootProject.buildDir}/docs") classifier = 'dokka' from dokka.outputDirectory } ////// // Packaging and deployment tasks.withType(Jar) { if (rootProject == project) { baseName = project.name } else { baseName = rootProject.name + '-' + project.name } manifest { attributes('Implementation-Title': baseName, 'Implementation-Version': project.version) } } if (project.name != 'eth-reference-tests') { artifacts { if (project != rootProject) { archives jar archives sourcesJar } archives dokkaJar } signing { useGpgCmd() sign configurations.archives } publishing { publications { MavenDeployment(MavenPublication) { publication -> if (project != rootProject) { from components.java artifact sourcesJar { classifier 'sources' } } artifact dokkaJar { classifier 'javadoc' } groupId 'net.consensys.cava' artifactId project.jar.baseName version project.version pom { name = project.jar.baseName afterEvaluate { description = project.description } url = 'https://github.com/ConsenSys/cava' licenses { license { name = "The Apache License, Version 2.0" url = "http://www.apache.org/licenses/LICENSE-2.0.txt" } } scm { connection = 'scm:https://github.com/ConsenSys/cava.git' developerConnection = 'scm:git@github.com:ConsenSys/cava.git' url = 'https://github.com/ConsenSys/cava' } developers { developer { name = 'Chris Leishman' email = 'chris@leishman.org' organization = 'ConsenSys' organizationUrl = 'https://www.consensys.net' } developer { name = 'Antoine Toulme' email = 'antoine@lunar-ocean.com' organization = 'ConsenSys' organizationUrl = 'https://www.consensys.net' } } } pom.withXml { // use inline versions rather than pom dependency management asNode().remove(asNode().dependencyManagement[0]) def dependenciesNode = asNode().appendNode('dependencies') def addDependencyNode = { dep, optional -> def dependencyNode = dependenciesNode.appendNode('dependency') if (dep instanceof ProjectDependency) { dependencyNode.appendNode('groupId', 'net.consensys.cava') dependencyNode.appendNode('artifactId', rootProject.name + '-' + dep.name) dependencyNode.appendNode('version', dep.version) } else { dependencyNode.appendNode('groupId', dep.group) dependencyNode.appendNode('artifactId', dep.name) if (dep.version != null) { dependencyNode.appendNode('version', dep.version) } else { def version = dependencyManagement.managedVersions["$dep.group:$dep.name"] dependencyNode.appendNode('version', version) } } if (optional) { dependencyNode.appendNode('optional', 'true') } def ers = dep.excludeRules if (!ers.empty) { def exclusionsNode = dependencyNode.appendNode('exclusions') ers.each { er -> def exclusionNode = exclusionsNode.appendNode('exclusion') exclusionNode.appendNode('groupId', er.group) exclusionNode.appendNode('artifactId', er.module) } } } configurations.compile.allDependencies.each { dep -> addDependencyNode(dep, false) } configurations.compileOnly.allDependencies.each { dep -> addDependencyNode(dep, true) } if (System.getenv('ENABLE_SIGNING') == 'true') { def pomFile = file("${project.buildDir}/generated-pom.xml") writeTo(pomFile) def pomAscFile = signing.sign(pomFile).signatureFiles[0] artifact(pomAscFile) { classifier = null extension = 'pom.asc' } } } if (System.getenv('ENABLE_SIGNING') == 'true') { // create the signed artifacts tasks.signArchives.signatureFiles.each { artifact(it) { def matcher = it.file =~ /-(sources|javadoc)\.jar\.asc$/ if (matcher.find()) { classifier = matcher.group(1) } else { classifier = null } extension = 'jar.asc' } } } } } } tasks.withType(Sign) { onlyIf { System.getenv('ENABLE_SIGNING') == 'true' } } model { tasks.generatePomFileForMavenDeploymentPublication { destination = file("$buildDir/generated-pom.xml") } tasks.publishMavenDeploymentPublicationToMavenLocal { dependsOn project.tasks.signArchives } } def artifactIdMatcher = Pattern.compile("(.*)-\\d.*") bintray { user = System.getenv('BINTRAY_USER') key = System.getenv('BINTRAY_KEY') publications = ['MavenDeployment'] filesSpec { project.extensions.getByType(PublishingExtension).publications.all { publication -> publication.getArtifacts().all { def ascFile = new File(it.file.getParentFile(), it.file.getName() + '.asc') if (ascFile.exists()) { def matcher = artifactIdMatcher.matcher(it.file.getName()) matcher.find() def artifactId = matcher.group(1) from ascFile.getAbsolutePath() into publication.groupId.replaceAll('\\.', '/') + '/' + artifactId + '/' + publication.version + '/' } } } } dryRun = !(System.getenv('BINTRAY_DEPLOY') == 'true') publish = true pkg { repo = 'consensys' name = 'cava' userOrg = 'consensys' licenses = ['Apache-2.0'] version { name = project.version desc = 'Cava distribution' released = new Date() vcsTag = project.version } } } deploy.dependsOn bintrayUpload } } ////// // Configure root project as a virtual package that depends on all components dependencies { subprojects.each { p -> switch (p.name) { case 'eth-reference-tests': // ignore break case 'crypto': compile(p) { exclude group: 'com.github.jnr', module: 'jnr-ffi' } break default: compile p break } } } jar { enabled = false } javadoc { subprojects.each { source += it.javadoc.source classpath += it.javadoc.classpath } } dokka { moduleName = rootProject.name subprojects.each { dependsOn it.classes it.sourceSets.main.output.each { d -> if (d.exists()) { classpath += d } } } sourceDirs = files(subprojects.collect { return [ new File(it.projectDir, '/src/main/kotlin'), new File(it.projectDir, '/src/main/java') ] }) linkMapping { dir = rootDir.toString() url = "https://github.com/consensys/cava/blob/master" suffix = "#L" } includes = ['PACKAGES.md'] externalDocumentationLink { url = new URL("https://docs.oracle.com/javase/8/docs/api/") } externalDocumentationLink { url = new URL('https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/') } } dokkaJar { baseName = rootProject.name manifest { attributes('Implementation-Title': baseName, 'Implementation-Version': project.version) } } cava-0.6.0/bytes/000077500000000000000000000000001341750772100135645ustar00rootroot00000000000000cava-0.6.0/bytes/build.gradle000066400000000000000000000005451341750772100160470ustar00rootroot00000000000000description = 'Classes and utilities for working with byte arrays.' dependencies { compile 'com.google.guava:guava' compileOnly 'io.vertx:vertx-core' testCompile 'io.vertx:vertx-core' testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/bytes/src/000077500000000000000000000000001341750772100143535ustar00rootroot00000000000000cava-0.6.0/bytes/src/main/000077500000000000000000000000001341750772100152775ustar00rootroot00000000000000cava-0.6.0/bytes/src/main/java/000077500000000000000000000000001341750772100162205ustar00rootroot00000000000000cava-0.6.0/bytes/src/main/java/net/000077500000000000000000000000001341750772100170065ustar00rootroot00000000000000cava-0.6.0/bytes/src/main/java/net/consensys/000077500000000000000000000000001341750772100210325ustar00rootroot00000000000000cava-0.6.0/bytes/src/main/java/net/consensys/cava/000077500000000000000000000000001341750772100217445ustar00rootroot00000000000000cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/000077500000000000000000000000001341750772100230725ustar00rootroot00000000000000cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/AbstractBytes.java000066400000000000000000000034321341750772100265110ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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; /** * An abstract {@link Bytes} value that provides implementations of {@link #equals(Object)}, {@link #hashCode()} and * {@link #toString()}. */ public abstract class AbstractBytes implements Bytes { static final char[] HEX_CODE = "0123456789ABCDEF".toCharArray(); /** * Compare this value and the provided one for equality. * *

* Two {@link Bytes} values are equal is they have contain the exact same bytes. * * @param obj The object to test for equality with. * @return {@code true} if this value and {@code obj} are equal. */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof Bytes)) { 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(); } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/ArrayWrappingBytes.java000066400000000000000000000120071341750772100275320ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.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(ByteBuffer byteBuffer) { byteBuffer.put(bytes, offset, length); } @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(); } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/ArrayWrappingBytes32.java000066400000000000000000000033261341750772100277030ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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()); } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/BufferWrappingBytes.java000066400000000000000000000053031341750772100276660ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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)); } // 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(); } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/ByteBufWrappingBytes.java000066400000000000000000000055351341750772100300240ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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)); } // 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; } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/ByteBufferWrappingBytes.java000066400000000000000000000065501341750772100305170ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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 void appendTo(ByteBuffer byteBuffer) { byteBuffer.put(this.byteBuffer); } @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; } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/Bytes.java000066400000000000000000001203051341750772100250240ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.io.IOException; import java.math.BigInteger; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.ReadOnlyBufferException; import java.security.MessageDigest; import java.security.SecureRandom; import java.util.Arrays; import java.util.Random; 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 a valid hexadecimal representation. */ static Bytes fromHexStringLenient(CharSequence str) { checkNotNull(str); return BytesValues.fromHexString(str, -1, true); } /** * Parse a hexadecimal string into a {@link Bytes} value of the provided size. * *

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

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

* This method requires that {@code str} have an even length. * * @param str The hexadecimal string to parse, which may or may not start with "0x". * @param destinationSize The size of the returned value, which must be big enough to hold the bytes represented by * {@code str}. If it is strictly bigger those bytes from {@code str}, the returned value will be left padded * with zeros. * @return A value of size {@code destinationSize} corresponding to {@code str} potentially left-padded. * @throws IllegalArgumentException if {@code str} does correspond to a valid hexadecimal representation, or is of an * odd length. * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation, or is of * an odd length, or represents more bytes than {@code destinationSize} or {@code destinationSize < 0}. */ static Bytes fromHexString(CharSequence str, int destinationSize) { checkNotNull(str); checkArgument(destinationSize >= 0, "Invalid negative destination size %s", destinationSize); return BytesValues.fromHexString(str, destinationSize, false); } /** * Generate random bytes. * * @param size The number of bytes to generate. * @return A value containing the desired number of random bytes. */ static Bytes random(int size) { return random(size, new SecureRandom()); } /** * Generate random bytes. * * @param size The number of bytes to generate. * @param generator The generator for random bytes. * @return A value containing the desired number of random bytes. */ static Bytes random(int size, Random generator) { byte[] array = new byte[size]; generator.nextBytes(array); return Bytes.wrap(array); } /** @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 toInt() { 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 toLong() { 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 toBigInteger() { 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 toUnsignedBigInteger() { return new BigInteger(1, toArrayUnsafe()); } /** * Whether this value has only zero bytes. * * @return {@code 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; } } 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 The {@link MutableBytes} value type. * @return The {@code result} output vector. */ default T and(Bytes other, T result) { checkNotNull(other); checkNotNull(result); int rSize = result.size(); int offsetSelf = rSize - size(); int offsetOther = rSize - other.size(); for (int i = 0; i < rSize; i++) { byte b1 = (i < offsetSelf) ? 0x00 : get(i - offsetSelf); byte b2 = (i < offsetOther) ? 0x00 : other.get(i - offsetOther); result.set(i, (byte) (b1 & b2)); } return result; } /** * Return a bit-wise OR 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 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 The {@link MutableBytes} value type. * @return The {@code result} output vector. */ default T or(Bytes other, T result) { checkNotNull(other); checkNotNull(result); int rSize = result.size(); int offsetSelf = rSize - size(); int offsetOther = rSize - other.size(); for (int i = 0; i < rSize; i++) { byte b1 = (i < offsetSelf) ? 0x00 : get(i - offsetSelf); byte b2 = (i < offsetOther) ? 0x00 : other.get(i - offsetOther); result.set(i, (byte) (b1 | b2)); } return result; } /** * Return a bit-wise XOR 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 XOR. */ default Bytes xor(Bytes other) { return xor(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 The {@link MutableBytes} value type. * @return The {@code result} output vector. */ default T xor(Bytes other, T result) { checkNotNull(other); checkNotNull(result); int rSize = result.size(); int offsetSelf = rSize - size(); int offsetOther = rSize - other.size(); for (int i = 0; i < rSize; i++) { byte b1 = (i < offsetSelf) ? 0x00 : get(i - offsetSelf); byte b2 = (i < offsetOther) ? 0x00 : other.get(i - offsetOther); result.set(i, (byte) (b1 ^ b2)); } return result; } /** * Return a bit-wise NOT of these bytes. * * @return The result of a bit-wise NOT. */ default Bytes not() { return not(MutableBytes.create(size())); } /** * Calculate a bit-wise NOT of these bytes. * *

* 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 The {@link MutableBytes} value type. * @return The {@code result} output vector. */ default T not(T result) { checkNotNull(result); int rSize = result.size(); int offsetSelf = rSize - size(); for (int i = 0; i < rSize; i++) { byte b1 = (i < offsetSelf) ? 0x00 : get(i - offsetSelf); result.set(i, (byte) ~b1); } return result; } /** * Shift all bits in this value to the right. * * @param distance The number of bits to shift by. * @return A value containing the shifted bits. */ default Bytes shiftRight(int distance) { return shiftRight(distance, MutableBytes.create(size())); } /** * Shift all bits in this value to the right. * *

* 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 The {@link MutableBytes} value type. * @return The {@code result} output vector. */ default T shiftRight(int distance, T result) { checkNotNull(result); int rSize = result.size(); int offsetSelf = rSize - size(); int d = distance / 8; int s = distance % 8; int resIdx = rSize - 1; for (int i = rSize - 1 - d; i >= 0; i--) { byte res; if (i < offsetSelf) { res = 0; } else { int selfIdx = i - offsetSelf; int leftSide = (get(selfIdx) & 0xFF) >>> s; int rightSide = (selfIdx == 0) ? 0 : get(selfIdx - 1) << (8 - s); res = (byte) (leftSide | rightSide); } result.set(resIdx--, res); } for (; resIdx >= 0; resIdx--) { result.set(resIdx, (byte) 0); } return result; } /** * Shift all bits in this value to the left. * * @param distance The number of bits to shift by. * @return A value containing the shifted bits. */ default Bytes shiftLeft(int distance) { return shiftLeft(distance, MutableBytes.create(size())); } /** * Shift all bits in this value to the left. * *

* 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 The {@link MutableBytes} value type. * @return The {@code result} output vector. */ default T shiftLeft(int distance, T result) { checkNotNull(result); int size = size(); int rSize = result.size(); int offsetSelf = rSize - size; int d = distance / 8; int s = distance % 8; int resIdx = 0; for (int i = d; i < rSize; i++) { byte res; if (i < offsetSelf) { res = 0; } else { int selfIdx = i - offsetSelf; int leftSide = get(selfIdx) << s; int rightSide = (selfIdx == size - 1) ? 0 : (get(selfIdx + 1) & 0xFF) >>> (8 - s); res = (byte) (leftSide | rightSide); } result.set(resIdx++, res); } for (; resIdx < rSize; resIdx++) { result.set(resIdx, (byte) 0); } return result; } /** * 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. * @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) { if (i == 0) { return this; } int size = size(); if (i >= size) { return EMPTY; } return slice(i, size - i); } /** * Create a new value representing (a view of) a slice of the bytes of this value. * *

* Please note that the resulting slice is only a view and as such maintains a link to the underlying full value. So * holding a reference to the returned slice may hold more memory than the slide represents. 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 {@link ByteBuffer}. * * @param byteBuffer The {@link ByteBuffer} to which to append this value. * @throws BufferOverflowException If the writer attempts to write more than the provided buffer can hold. * @throws ReadOnlyBufferException If the provided buffer is read-only. */ default void appendTo(ByteBuffer byteBuffer) { checkNotNull(byteBuffer); for (int i = 0; i < size(); i++) { byteBuffer.put(get(i)); } } /** * Append the bytes of this value to the provided Vert.x {@link Buffer}. * *

* Note that since a Vert.x {@link Buffer} will grow as necessary, this method never fails. * * @param buffer The {@link Buffer} to which to append this value. */ default void appendTo(Buffer buffer) { checkNotNull(buffer); for (int i = 0; i < size(); i++) { buffer.appendByte(get(i)); } } /** * Append this value as a sequence of hexadecimal characters. * * @param appendable The appendable * @param The appendable type. * @return The appendable. * @throws IOException If an IO error occurs. */ default T appendHexTo(T appendable) throws IOException { int size = size(); for (int i = 0; i < size; i++) { byte b = get(i); appendable.append(AbstractBytes.HEX_CODE[b >> 4 & 15]); appendable.append(AbstractBytes.HEX_CODE[b & 15]); } return appendable; } /** * 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); digest.update(toArrayUnsafe()); } /** * 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() { try { return appendHexTo(new StringBuilder("0x")).toString(); } catch (IOException e) { // not thrown throw new RuntimeException(e); } } /** @return This value represented as a minimal hexadecimal string (without any leading zero). */ default String toShortHexString() { StringBuilder hex; try { hex = appendHexTo(new StringBuilder()); } catch (IOException e) { // not thrown throw new RuntimeException(e); } int i = 0; while (i < hex.length() && hex.charAt(i) == '0') { i++; } return "0x" + hex.substring(i); } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/Bytes32.java000066400000000000000000000221071341750772100251720ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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; import java.security.SecureRandom; import java.util.Random; /** * 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 a valid hexadecimal representation or * contains more than 32 bytes. */ static Bytes32 fromHexStringLenient(CharSequence 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 a valid hexadecimal representation, is of an * odd length, or contains more than 32 bytes. */ static Bytes32 fromHexString(CharSequence str) { checkNotNull(str); return wrap(BytesValues.fromRawHexString(str, SIZE, false)); } /** * Generate random bytes. * * @return A value containing random bytes. */ static Bytes32 random() { return random(new SecureRandom()); } /** * Generate random bytes. * * @param generator The generator for random bytes. * @return A value containing random bytes. */ static Bytes32 random(Random generator) { byte[] array = new byte[32]; generator.nextBytes(array); return wrap(array); } /** * Parse a hexadecimal string into a {@link Bytes32}. * *

* This method is extra strict in that {@code str} must of an even length and the provided representation must have * exactly 32 bytes. * * @param str The hexadecimal string to parse, which may or may not start with "0x". * @return The value corresponding to {@code str}. * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation, is of an * odd length or does not contain exactly 32 bytes. */ static Bytes32 fromHexStringStrict(CharSequence 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(); } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/BytesValues.java000066400000000000000000000053031341750772100262040ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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(CharSequence str, int destSize, boolean lenient) { return Bytes.wrap(fromRawHexString(str, destSize, lenient)); } static byte[] fromRawHexString(CharSequence str, int destSize, boolean lenient) { int len = str.length(); CharSequence hex = str; if (len >= 2 && str.charAt(0) == '0' && str.charAt(1) == 'x') { hex = str.subSequence(2, len); len -= 2; } int idxShift = 0; if (len % 2 != 0) { if (!lenient) { throw new IllegalArgumentException("Invalid odd-length hex binary representation"); } hex = "0" + hex; len += 1; idxShift = 1; } int size = len / 2; if (destSize < 0) { destSize = size; } else { checkArgument(size <= destSize, "Hex value is too large: expected at most %s bytes but got %s", destSize, size); } byte[] out = new byte[destSize]; int destOffset = (destSize - size); for (int i = 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", hex.charAt(i), i - idxShift)); } if (l == -1) { throw new IllegalArgumentException( String.format( "Illegal character '%c' found at index %d in hex binary representation", hex.charAt(i + 1), i + 1 - idxShift)); } 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; } } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/ConcatenatedBytes.java000066400000000000000000000131171341750772100273370ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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; } } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/DelegatingBytes32.java000066400000000000000000000116341341750772100271610ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.nio.ByteBuffer; 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 toInt() { return delegate.toInt(); } @Override public long getLong(int i) { return delegate.getLong(i); } @Override public long toLong() { return delegate.toLong(); } @Override public BigInteger toBigInteger() { return delegate.toBigInteger(); } @Override public BigInteger toUnsignedBigInteger() { return delegate.toUnsignedBigInteger(); } @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 T and(Bytes other, T result) { return delegate.and(other, result); } @Override public Bytes or(Bytes other) { return delegate.or(other); } @Override public T or(Bytes other, T result) { return delegate.or(other, result); } @Override public Bytes xor(Bytes other) { return delegate.xor(other); } @Override public T xor(Bytes other, T result) { return delegate.xor(other, result); } @Override public T not(T result) { return delegate.not(result); } @Override public T shiftRight(int distance, T result) { return delegate.shiftRight(distance, result); } @Override public T shiftLeft(int distance, T result) { return delegate.shiftLeft(distance, result); } @Override public Bytes slice(int index) { return delegate.slice(index); } @Override public Bytes slice(int index, int length) { return delegate.slice(index, length); } @Override public Bytes32 copy() { return Bytes32.wrap(toArray()); } @Override public MutableBytes32 mutableCopy() { return MutableBytes32.wrap(toArray()); } @Override public void copyTo(MutableBytes destination) { delegate.copyTo(destination); } @Override public void copyTo(MutableBytes destination, int destinationOffset) { delegate.copyTo(destination, destinationOffset); } @Override public void appendTo(ByteBuffer byteBuffer) { delegate.appendTo(byteBuffer); } @Override public void appendTo(Buffer buffer) { delegate.appendTo(buffer); } @Override public int commonPrefixLength(Bytes other) { return delegate.commonPrefixLength(other); } @Override public Bytes commonPrefix(Bytes other) { return delegate.commonPrefix(other); } @Override public void update(MessageDigest digest) { delegate.update(digest); } @Override public byte[] toArray() { return delegate.toArray(); } @Override public byte[] toArrayUnsafe() { return delegate.toArrayUnsafe(); } @Override public String toString() { return delegate.toString(); } @Override public String toHexString() { return delegate.toHexString(); } @Override public String toShortHexString() { return delegate.toShortHexString(); } @Override public boolean equals(Object obj) { return delegate.equals(obj); } @Override public int hashCode() { return delegate.hashCode(); } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/DelegatingMutableBytes32.java000066400000000000000000000127431341750772100304750ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.nio.ByteBuffer; import java.security.MessageDigest; import io.vertx.core.buffer.Buffer; final class DelegatingMutableBytes32 implements MutableBytes32 { private final MutableBytes delegate; private DelegatingMutableBytes32(MutableBytes delegate) { this.delegate = delegate; } static MutableBytes32 delegateTo(MutableBytes value) { checkArgument(value.size() == SIZE, "Expected %s bytes but got %s", SIZE, value.size()); return new DelegatingMutableBytes32(value); } @Override public void set(int i, byte b) { delegate.set(i, b); } @Override public void setInt(int i, int value) { delegate.setInt(i, value); } @Override public void setLong(int i, long value) { delegate.setLong(i, value); } @Override public MutableBytes mutableSlice(int i, int length) { return delegate.mutableSlice(i, length); } @Override public void fill(byte b) { delegate.fill(b); } @Override public void clear() { delegate.clear(); } @Override public int size() { return delegate.size(); } @Override public byte get(int i) { return delegate.get(i); } @Override public int getInt(int i) { return delegate.getInt(i); } @Override public int toInt() { return delegate.toInt(); } @Override public long getLong(int i) { return delegate.getLong(i); } @Override public long toLong() { return delegate.toLong(); } @Override public BigInteger toBigInteger() { return delegate.toBigInteger(); } @Override public BigInteger toUnsignedBigInteger() { return delegate.toUnsignedBigInteger(); } @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 T and(Bytes other, T result) { return delegate.and(other, result); } @Override public Bytes or(Bytes other) { return delegate.or(other); } @Override public T or(Bytes other, T result) { return delegate.or(other, result); } @Override public Bytes xor(Bytes other) { return delegate.xor(other); } @Override public T xor(Bytes other, T result) { return delegate.xor(other, result); } @Override public T not(T result) { return delegate.not(result); } @Override public T shiftRight(int distance, T result) { return delegate.shiftRight(distance, result); } @Override public T shiftLeft(int distance, T result) { return delegate.shiftLeft(distance, result); } @Override public Bytes slice(int index) { return delegate.slice(index); } @Override public Bytes slice(int index, int length) { return delegate.slice(index, length); } @Override public Bytes32 copy() { return Bytes32.wrap(delegate.toArray()); } @Override public MutableBytes32 mutableCopy() { return MutableBytes32.wrap(delegate.toArray()); } @Override public void copyTo(MutableBytes destination) { delegate.copyTo(destination); } @Override public void copyTo(MutableBytes destination, int destinationOffset) { delegate.copyTo(destination, destinationOffset); } @Override public void appendTo(ByteBuffer byteBuffer) { delegate.appendTo(byteBuffer); } @Override public void appendTo(Buffer buffer) { delegate.appendTo(buffer); } @Override public int commonPrefixLength(Bytes other) { return delegate.commonPrefixLength(other); } @Override public Bytes commonPrefix(Bytes other) { return delegate.commonPrefix(other); } @Override public void update(MessageDigest digest) { delegate.update(digest); } @Override public byte[] toArray() { return delegate.toArray(); } @Override public byte[] toArrayUnsafe() { return delegate.toArrayUnsafe(); } @Override public String toString() { return delegate.toString(); } @Override public String toHexString() { return delegate.toHexString(); } @Override public String toShortHexString() { return delegate.toShortHexString(); } @Override public boolean equals(Object obj) { return delegate.equals(obj); } @Override public int hashCode() { return delegate.hashCode(); } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/MutableArrayWrappingBytes.java000066400000000000000000000040351341750772100310460ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.util.Arrays; class MutableArrayWrappingBytes extends ArrayWrappingBytes implements MutableBytes { MutableArrayWrappingBytes(byte[] bytes) { super(bytes); } MutableArrayWrappingBytes(byte[] bytes, int offset, int length) { super(bytes, offset, length); } @Override public void set(int i, byte b) { // Check bounds because while the array access would throw, the error message would be confusing // for the caller. checkElementIndex(i, size()); this.bytes[offset + i] = b; } @Override public MutableBytes mutableSlice(int i, int length) { if (i == 0 && length == this.length) return this; if (length == 0) return MutableBytes.EMPTY; checkElementIndex(i, this.length); checkArgument( i + length <= this.length, "Specified length %s is too large: the value has size %s and has only %s bytes from %s", length, this.length, this.length - i, i); return length == Bytes32.SIZE ? new MutableArrayWrappingBytes32(bytes, offset + i) : new MutableArrayWrappingBytes(bytes, offset + i, length); } @Override public void fill(byte b) { Arrays.fill(bytes, offset, offset + length, b); } @Override public Bytes copy() { return new ArrayWrappingBytes(toArray()); } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/MutableArrayWrappingBytes32.java000066400000000000000000000021041341750772100312060ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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; final class MutableArrayWrappingBytes32 extends MutableArrayWrappingBytes implements MutableBytes32 { MutableArrayWrappingBytes32(byte[] bytes) { this(bytes, 0); } MutableArrayWrappingBytes32(byte[] bytes, int offset) { super(bytes, offset, SIZE); } @Override public Bytes32 copy() { return new ArrayWrappingBytes32(toArray()); } @Override public MutableBytes32 mutableCopy() { return new MutableArrayWrappingBytes32(toArray()); } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/MutableBufferWrappingBytes.java000066400000000000000000000037441341750772100312070ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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; final class MutableBufferWrappingBytes extends BufferWrappingBytes implements MutableBytes { MutableBufferWrappingBytes(Buffer buffer) { super(buffer); } MutableBufferWrappingBytes(Buffer buffer, int offset, int length) { super(buffer, offset, length); } @Override public void set(int i, byte b) { buffer.setByte(i, b); } @Override public void setInt(int i, int value) { buffer.setInt(i, value); } @Override public void setLong(int i, long value) { buffer.setLong(i, value); } @Override public MutableBytes mutableSlice(int i, int length) { int size = size(); if (i == 0 && length == size) { return this; } if (length == 0) { return MutableBytes.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 MutableBufferWrappingBytes(buffer.slice(i, i + length)); } @Override public Bytes copy() { return Bytes.wrap(toArray()); } @Override public MutableBytes mutableCopy() { return MutableBytes.wrap(toArray()); } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/MutableByteBufWrappingBytes.java000066400000000000000000000040741341750772100313330ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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; final class MutableByteBufWrappingBytes extends ByteBufWrappingBytes implements MutableBytes { MutableByteBufWrappingBytes(ByteBuf buffer) { super(buffer); } MutableByteBufWrappingBytes(ByteBuf buffer, int offset, int length) { super(buffer, offset, length); } @Override public void clear() { byteBuf.setZero(0, byteBuf.capacity()); } @Override public void set(int i, byte b) { byteBuf.setByte(i, b); } @Override public void setInt(int i, int value) { byteBuf.setInt(i, value); } @Override public void setLong(int i, long value) { byteBuf.setLong(i, value); } @Override public MutableBytes mutableSlice(int i, int length) { int size = size(); if (i == 0 && length == size) { return this; } if (length == 0) { return MutableBytes.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 MutableByteBufWrappingBytes(byteBuf.slice(i, length)); } @Override public Bytes copy() { return Bytes.wrap(toArray()); } @Override public MutableBytes mutableCopy() { return MutableBytes.wrap(toArray()); } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/MutableByteBufferWrappingBytes.java000066400000000000000000000037471341750772100320360ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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; public class MutableByteBufferWrappingBytes extends ByteBufferWrappingBytes implements MutableBytes { MutableByteBufferWrappingBytes(ByteBuffer byteBuffer) { super(byteBuffer); } MutableByteBufferWrappingBytes(ByteBuffer byteBuffer, int offset, int length) { super(byteBuffer, offset, length); } @Override public void setInt(int i, int value) { byteBuffer.putInt(offset + i, value); } @Override public void setLong(int i, long value) { byteBuffer.putLong(offset + i, value); } @Override public void set(int i, byte b) { byteBuffer.put(offset + i, b); } @Override public MutableBytes mutableSlice(int i, int length) { if (i == 0 && length == this.length) { return this; } if (length == 0) { return MutableBytes.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 MutableByteBufferWrappingBytes(byteBuffer, offset + i, length); } @Override public Bytes copy() { return new ArrayWrappingBytes(toArray()); } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/MutableBytes.java000066400000000000000000000245421341750772100263440ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.checkElementIndex; import static com.google.common.base.Preconditions.checkNotNull; import static java.lang.String.format; import java.nio.ByteBuffer; import io.netty.buffer.ByteBuf; import io.vertx.core.buffer.Buffer; /** * A mutable {@link Bytes} value. */ public interface MutableBytes extends Bytes { /** * The empty value (with 0 bytes). */ MutableBytes EMPTY = wrap(new byte[0]); /** * Create a new mutable byte value. * * @param size The size of the returned value. * @return A {@link MutableBytes} value. */ static MutableBytes create(int size) { if (size == 32) { return MutableBytes32.create(); } return new MutableArrayWrappingBytes(new byte[size]); } /** * Wrap a byte array in a {@link MutableBytes} value. * * @param value The value to wrap. * @return A {@link MutableBytes} value wrapping {@code value}. */ static MutableBytes wrap(byte[] value) { checkNotNull(value); return new MutableArrayWrappingBytes(value); } /** * Wrap a slice of a byte array as a {@link MutableBytes} value. * *

* Note that value is not copied and thus any future update to {@code value} within the slice will be reflected in the * returned value. * * @param value The value to wrap. * @param offset The index (inclusive) in {@code value} of the first byte exposed by the returned value. In other * words, you will have {@code wrap(value, o, l).get(0) == value[o]}. * @param length The length of the resulting value. * @return A {@link Bytes} value that expose the bytes of {@code value} from {@code offset} (inclusive) to * {@code offset + length} (exclusive). * @throws IndexOutOfBoundsException if {@code offset < 0 || (value.length > 0 && offset >= * value.length)}. * @throws IllegalArgumentException if {@code length < 0 || offset + length > value.length}. */ 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); } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/MutableBytes32.java000066400000000000000000000104201341750772100264770ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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); } } cava-0.6.0/bytes/src/main/java/net/consensys/cava/bytes/package-info.java000066400000000000000000000005471341750772100262670ustar00rootroot00000000000000/** * 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; cava-0.6.0/bytes/src/test/000077500000000000000000000000001341750772100153325ustar00rootroot00000000000000cava-0.6.0/bytes/src/test/java/000077500000000000000000000000001341750772100162535ustar00rootroot00000000000000cava-0.6.0/bytes/src/test/java/net/000077500000000000000000000000001341750772100170415ustar00rootroot00000000000000cava-0.6.0/bytes/src/test/java/net/consensys/000077500000000000000000000000001341750772100210655ustar00rootroot00000000000000cava-0.6.0/bytes/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100217775ustar00rootroot00000000000000cava-0.6.0/bytes/src/test/java/net/consensys/cava/bytes/000077500000000000000000000000001341750772100231255ustar00rootroot00000000000000cava-0.6.0/bytes/src/test/java/net/consensys/cava/bytes/BufferBytesTest.java000066400000000000000000000022301341750772100270450ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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())); } } cava-0.6.0/bytes/src/test/java/net/consensys/cava/bytes/ByteBufBytesTest.java000066400000000000000000000022721341750772100272020ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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())); } } cava-0.6.0/bytes/src/test/java/net/consensys/cava/bytes/ByteBufferBytesTest.java000066400000000000000000000022461341750772100277000ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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())); } } cava-0.6.0/bytes/src/test/java/net/consensys/cava/bytes/Bytes32Test.java000066400000000000000000000035341341750772100260700ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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()); } } cava-0.6.0/bytes/src/test/java/net/consensys/cava/bytes/BytesTest.java000066400000000000000000000346571341750772100257350ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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 wrapProvider() { return Stream.of( Arguments.of(new Object[] {new byte[10]}), Arguments.of(new Object[] {new byte[] {1}}), Arguments.of(new Object[] {new byte[] {1, 2, 3, 4}}), Arguments.of(new Object[] {new byte[] {-1, 127, -128}})); } @Test void wrapNull() { assertThrows(NullPointerException.class, () -> Bytes.wrap((byte[]) null)); } /** * Checks that modifying a wrapped array modifies the value itself. */ @Test void wrapReflectsUpdates() { byte[] bytes = new byte[] {1, 2, 3, 4, 5}; Bytes value = Bytes.wrap(bytes); assertEquals(bytes.length, value.size()); assertArrayEquals(value.toArray(), bytes); bytes[1] = 127; bytes[3] = 127; assertEquals(bytes.length, value.size()); assertArrayEquals(value.toArray(), bytes); } @Test void wrapSliceEmpty() { assertEquals(Bytes.EMPTY, Bytes.wrap(new byte[0], 0, 0)); assertEquals(Bytes.EMPTY, Bytes.wrap(new byte[] {1, 2, 3}, 0, 0)); assertEquals(Bytes.EMPTY, Bytes.wrap(new byte[] {1, 2, 3}, 2, 0)); } @ParameterizedTest @MethodSource("wrapSliceProvider") void wrapSlice(Object arr, int offset, int length) { assertWrapSlice((byte[]) arr, offset, length); } private static Stream wrapSliceProvider() { return Stream.of( Arguments.of(new byte[] {1, 2, 3, 4}, 0, 4), Arguments.of(new byte[] {1, 2, 3, 4}, 0, 2), Arguments.of(new byte[] {1, 2, 3, 4}, 2, 1), Arguments.of(new byte[] {1, 2, 3, 4}, 2, 2)); } private void assertWrapSlice(byte[] bytes, int offset, int length) { Bytes value = Bytes.wrap(bytes, offset, length); assertEquals(length, value.size()); assertArrayEquals(value.toArray(), Arrays.copyOfRange(bytes, offset, offset + length)); } @Test void wrapSliceNull() { assertThrows(NullPointerException.class, () -> Bytes.wrap(null, 0, 2)); } @Test void wrapSliceNegativeOffset() { assertThrows(IndexOutOfBoundsException.class, () -> assertWrapSlice(new byte[] {1, 2, 3, 4}, -1, 4)); } @Test void wrapSliceOutOfBoundOffset() { assertThrows(IndexOutOfBoundsException.class, () -> assertWrapSlice(new byte[] {1, 2, 3, 4}, 5, 1)); } @Test void wrapSliceNegativeLength() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> assertWrapSlice(new byte[] {1, 2, 3, 4}, 0, -2)); assertEquals("Invalid negative length", exception.getMessage()); } @Test void wrapSliceTooBig() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> assertWrapSlice(new byte[] {1, 2, 3, 4}, 2, 3)); assertEquals("Provided length 3 is too big: the value has only 2 bytes from offset 2", exception.getMessage()); } /** * Checks that modifying a wrapped array modifies the value itself, but only if within the wrapped slice. */ @Test void wrapSliceReflectsUpdates() { byte[] bytes = new byte[] {1, 2, 3, 4, 5}; assertWrapSlice(bytes, 2, 2); bytes[2] = 127; bytes[3] = 127; assertWrapSlice(bytes, 2, 2); Bytes wrapped = Bytes.wrap(bytes, 2, 2); Bytes copy = wrapped.copy(); // Modify the bytes outside of the wrapped slice and check this doesn't affect the value (that // it is still equal to the copy from before the updates) bytes[0] = 127; assertEquals(copy, wrapped); // Sanity check for copy(): modify within the wrapped slice and check the copy differs now. bytes[2] = 42; assertNotEquals(copy, wrapped); } @Test void ofBytes() { assertArrayEquals(Bytes.of().toArray(), new byte[] {}); assertArrayEquals(Bytes.of((byte) 1, (byte) 2).toArray(), new byte[] {1, 2}); assertArrayEquals(Bytes.of((byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5).toArray(), new byte[] {1, 2, 3, 4, 5}); assertArrayEquals(Bytes.of((byte) -1, (byte) 2, (byte) -3).toArray(), new byte[] {-1, 2, -3}); } @Test void ofInts() { assertArrayEquals(Bytes.of(1, 2).toArray(), new byte[] {1, 2}); assertArrayEquals(Bytes.of(1, 2, 3, 4, 5).toArray(), new byte[] {1, 2, 3, 4, 5}); assertArrayEquals(Bytes.of(0xff, 0x7f, 0x80).toArray(), new byte[] {-1, 127, -128}); } @Test void ofIntsTooBig() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.of(2, 3, 256)); assertEquals("3th value 256 does not fit a byte", exception.getMessage()); } @Test void ofIntsTooLow() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.of(2, -1, 3)); assertEquals("2th value -1 does not fit a byte", exception.getMessage()); } @Test void minimalBytes() { assertEquals(h("0x"), Bytes.minimalBytes(0)); assertEquals(h("0x01"), Bytes.minimalBytes(1)); assertEquals(h("0x04"), Bytes.minimalBytes(4)); assertEquals(h("0x10"), Bytes.minimalBytes(16)); assertEquals(h("0xFF"), Bytes.minimalBytes(255)); assertEquals(h("0x0100"), Bytes.minimalBytes(256)); assertEquals(h("0x0200"), Bytes.minimalBytes(512)); assertEquals(h("0x010000"), Bytes.minimalBytes(1L << 16)); assertEquals(h("0x01000000"), Bytes.minimalBytes(1L << 24)); assertEquals(h("0x0100000000"), Bytes.minimalBytes(1L << 32)); assertEquals(h("0x010000000000"), Bytes.minimalBytes(1L << 40)); assertEquals(h("0x01000000000000"), Bytes.minimalBytes(1L << 48)); assertEquals(h("0x0100000000000000"), Bytes.minimalBytes(1L << 56)); assertEquals(h("0xFFFFFFFFFFFFFFFF"), Bytes.minimalBytes(-1L)); } @Test void ofUnsignedShort() { assertEquals(h("0x0000"), Bytes.ofUnsignedShort(0)); assertEquals(h("0x0001"), Bytes.ofUnsignedShort(1)); assertEquals(h("0x0100"), Bytes.ofUnsignedShort(256)); assertEquals(h("0xFFFF"), Bytes.ofUnsignedShort(65535)); } @Test void ofUnsignedShortNegative() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.ofUnsignedShort(-1)); assertEquals( "Value -1 cannot be represented as an unsigned short (it is negative or too big)", exception.getMessage()); } @Test void ofUnsignedShortTooBig() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.ofUnsignedShort(65536)); assertEquals( "Value 65536 cannot be represented as an unsigned short (it is negative or too big)", exception.getMessage()); } @Test void asUnsignedBigIntegerConstants() { assertEquals(bi("0"), Bytes.EMPTY.toUnsignedBigInteger()); assertEquals(bi("1"), Bytes.of(1).toUnsignedBigInteger()); } @Test void asSignedBigIntegerConstants() { assertEquals(bi("0"), Bytes.EMPTY.toBigInteger()); assertEquals(bi("1"), Bytes.of(1).toBigInteger()); } @Test void fromHexStringLenient() { assertEquals(Bytes.of(), Bytes.fromHexStringLenient("")); assertEquals(Bytes.of(), Bytes.fromHexStringLenient("0x")); assertEquals(Bytes.of(0), Bytes.fromHexStringLenient("0")); assertEquals(Bytes.of(0), Bytes.fromHexStringLenient("0x0")); assertEquals(Bytes.of(0), Bytes.fromHexStringLenient("00")); assertEquals(Bytes.of(0), Bytes.fromHexStringLenient("0x00")); assertEquals(Bytes.of(1), Bytes.fromHexStringLenient("0x1")); assertEquals(Bytes.of(1), Bytes.fromHexStringLenient("0x01")); assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("1FF2A")); assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1FF2A")); assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1ff2a")); assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1fF2a")); assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("01FF2A")); assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x01FF2A")); assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x01ff2A")); } @Test void fromHexStringLenientInvalidInput() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexStringLenient("foo")); assertEquals("Illegal character 'o' found at index 1 in hex binary representation", exception.getMessage()); } @Test void fromHexStringLenientLeftPadding() { assertEquals(Bytes.of(), Bytes.fromHexStringLenient("", 0)); assertEquals(Bytes.of(0), Bytes.fromHexStringLenient("", 1)); assertEquals(Bytes.of(0, 0), Bytes.fromHexStringLenient("", 2)); assertEquals(Bytes.of(0, 0), Bytes.fromHexStringLenient("0x", 2)); assertEquals(Bytes.of(0, 0, 0), Bytes.fromHexStringLenient("0", 3)); assertEquals(Bytes.of(0, 0, 0), Bytes.fromHexStringLenient("0x0", 3)); assertEquals(Bytes.of(0, 0, 0), Bytes.fromHexStringLenient("00", 3)); assertEquals(Bytes.of(0, 0, 0), Bytes.fromHexStringLenient("0x00", 3)); assertEquals(Bytes.of(0, 0, 1), Bytes.fromHexStringLenient("0x1", 3)); assertEquals(Bytes.of(0, 0, 1), Bytes.fromHexStringLenient("0x01", 3)); assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("1FF2A", 3)); assertEquals(Bytes.of(0x00, 0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1FF2A", 4)); assertEquals(Bytes.of(0x00, 0x00, 0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1ff2a", 5)); assertEquals(Bytes.of(0x00, 0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x1fF2a", 4)); assertEquals(Bytes.of(0x00, 0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("01FF2A", 4)); assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x01FF2A", 3)); assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexStringLenient("0x01ff2A", 3)); } @Test void fromHexStringLenientLeftPaddingInvalidInput() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexStringLenient("foo", 10)); assertEquals("Illegal character 'o' found at index 1 in hex binary representation", exception.getMessage()); } @Test void fromHexStringLenientLeftPaddingInvalidSize() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexStringLenient("0x001F34", 2)); assertEquals("Hex value is too large: expected at most 2 bytes but got 3", exception.getMessage()); } @Test void fromHexString() { assertEquals(Bytes.of(), Bytes.fromHexString("0x")); assertEquals(Bytes.of(0), Bytes.fromHexString("00")); assertEquals(Bytes.of(0), Bytes.fromHexString("0x00")); assertEquals(Bytes.of(1), Bytes.fromHexString("0x01")); assertEquals(Bytes.of(1, 0xff, 0x2a), Bytes.fromHexString("01FF2A")); assertEquals(Bytes.of(1, 0xff, 0x2a), Bytes.fromHexString("0x01FF2A")); assertEquals(Bytes.of(1, 0xff, 0x2a), Bytes.fromHexString("0x01ff2a")); assertEquals(Bytes.of(1, 0xff, 0x2a), Bytes.fromHexString("0x01fF2a")); } @Test void fromHexStringInvalidInput() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexString("fooo")); assertEquals("Illegal character 'o' found at index 1 in hex binary representation", exception.getMessage()); } @Test void fromHexStringNotLenient() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexString("0x100")); assertEquals("Invalid odd-length hex binary representation", exception.getMessage()); } @Test void fromHexStringLeftPadding() { assertEquals(Bytes.of(), Bytes.fromHexString("0x", 0)); assertEquals(Bytes.of(0, 0), Bytes.fromHexString("0x", 2)); assertEquals(Bytes.of(0, 0, 0, 0), Bytes.fromHexString("0x", 4)); assertEquals(Bytes.of(0, 0), Bytes.fromHexString("00", 2)); assertEquals(Bytes.of(0, 0), Bytes.fromHexString("0x00", 2)); assertEquals(Bytes.of(0, 0, 1), Bytes.fromHexString("0x01", 3)); assertEquals(Bytes.of(0x00, 0x01, 0xff, 0x2a), Bytes.fromHexString("01FF2A", 4)); assertEquals(Bytes.of(0x01, 0xff, 0x2a), Bytes.fromHexString("0x01FF2A", 3)); assertEquals(Bytes.of(0x00, 0x00, 0x01, 0xff, 0x2a), Bytes.fromHexString("0x01ff2a", 5)); assertEquals(Bytes.of(0x00, 0x00, 0x01, 0xff, 0x2a), Bytes.fromHexString("0x01fF2a", 5)); } @Test void fromHexStringLeftPaddingInvalidInput() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexString("fooo", 4)); assertEquals("Illegal character 'o' found at index 1 in hex binary representation", exception.getMessage()); } @Test void fromHexStringLeftPaddingNotLenient() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexString("0x100", 4)); assertEquals("Invalid odd-length hex binary representation", exception.getMessage()); } @Test void fromHexStringLeftPaddingInvalidSize() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> Bytes.fromHexStringLenient("0x001F34", 2)); assertEquals("Hex value is too large: expected at most 2 bytes but got 3", exception.getMessage()); } } cava-0.6.0/bytes/src/test/java/net/consensys/cava/bytes/CommonBytesTests.java000066400000000000000000000513561341750772100272640ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; import java.util.function.Function; import io.netty.buffer.Unpooled; import io.vertx.core.buffer.Buffer; import org.junit.jupiter.api.Test; abstract class CommonBytesTests { abstract Bytes h(String hex); abstract MutableBytes m(int size); abstract Bytes w(byte[] bytes); abstract Bytes of(int... bytes); BigInteger bi(String decimal) { return new BigInteger(decimal); } @Test void asUnsignedBigInteger() { // Make sure things are interpreted unsigned. assertEquals(bi("255"), h("0xFF").toUnsignedBigInteger()); // Try 2^100 + Long.MAX_VALUE, as an easy to define a big not too special big integer. BigInteger expected = BigInteger.valueOf(2).pow(100).add(BigInteger.valueOf(Long.MAX_VALUE)); // 2^100 is a one followed by 100 zeros, that's 12 bytes of zeros (=96) plus 4 more zeros (so // 0x10 == 16). MutableBytes v = m(13); v.set(0, (byte) 16); v.setLong(v.size() - 8, Long.MAX_VALUE); assertEquals(expected, v.toUnsignedBigInteger()); } @Test void testAsSignedBigInteger() { // Make sure things are interpreted signed. assertEquals(bi("-1"), h("0xFF").toBigInteger()); // Try 2^100 + Long.MAX_VALUE, as an easy to define a big but not too special big integer. BigInteger expected = BigInteger.valueOf(2).pow(100).add(BigInteger.valueOf(Long.MAX_VALUE)); // 2^100 is a one followed by 100 zeros, that's 12 bytes of zeros (=96) plus 4 more zeros (so // 0x10 == 16). MutableBytes v = m(13); v.set(0, (byte) 16); v.setLong(v.size() - 8, Long.MAX_VALUE); assertEquals(expected, v.toBigInteger()); // And for a large negative one, we use -(2^100 + Long.MAX_VALUE), which is: // 2^100 + Long.MAX_VALUE = 0x10(4 bytes of 0)7F( 7 bytes of 1) // inverse = 0xEF(4 bytes of 1)80( 7 bytes of 0) // +1 = 0xEF(4 bytes of 1)80(6 bytes of 0)01 expected = expected.negate(); v = m(13); v.set(0, (byte) 0xEF); for (int i = 1; i < 5; i++) { v.set(i, (byte) 0xFF); } v.set(5, (byte) 0x80); // 6 bytes of 0 v.set(12, (byte) 1); assertEquals(expected, v.toBigInteger()); } @Test void testSize() { assertEquals(0, w(new byte[0]).size()); assertEquals(1, w(new byte[1]).size()); assertEquals(10, w(new byte[10]).size()); } @Test void testGet() { Bytes v = w(new byte[] {1, 2, 3, 4}); assertEquals((int) (byte) 1, (int) v.get(0)); assertEquals((int) (byte) 2, (int) v.get(1)); assertEquals((int) (byte) 3, (int) v.get(2)); assertEquals((int) (byte) 4, (int) v.get(3)); } @Test void testGetNegativeIndex() { assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).get(-1)); } @Test void testGetOutOfBound() { assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).get(4)); } @Test void testGetInt() { Bytes value = w(new byte[] {0, 0, 1, 0, -1, -1, -1, -1}); // 0x00000100 = 256 assertEquals(256, value.getInt(0)); // 0x000100FF = 65536 + 255 = 65791 assertEquals(65791, value.getInt(1)); // 0x0100FFFF = 16777216 (2^24) + (65536 - 1) = 16842751 assertEquals(16842751, value.getInt(2)); // 0xFFFFFFFF = -1 assertEquals(-1, value.getInt(4)); } @Test void testGetIntNegativeIndex() { assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).getInt(-1)); } @Test void testGetIntOutOfBound() { assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).getInt(4)); } @Test void testGetIntNotEnoughBytes() { assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).getInt(1)); } @Test void testAsInt() { assertEquals(0, Bytes.EMPTY.toInt()); Bytes value1 = w(new byte[] {0, 0, 1, 0}); // 0x00000100 = 256 assertEquals(256, value1.toInt()); assertEquals(256, value1.slice(2).toInt()); Bytes value2 = w(new byte[] {0, 1, 0, -1}); // 0x000100FF = 65536 + 255 = 65791 assertEquals(65791, value2.toInt()); assertEquals(65791, value2.slice(1).toInt()); Bytes value3 = w(new byte[] {1, 0, -1, -1}); // 0x0100FFFF = 16777216 (2^24) + (65536 - 1) = 16842751 assertEquals(16842751, value3.toInt()); Bytes value4 = w(new byte[] {-1, -1, -1, -1}); // 0xFFFFFFFF = -1 assertEquals(-1, value4.toInt()); } @Test void testAsIntTooManyBytes() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> w(new byte[] {1, 2, 3, 4, 5}).toInt()); assertEquals("Value of size 5 has more than 4 bytes", exception.getMessage()); } @Test void testGetLong() { Bytes value1 = w(new byte[] {0, 0, 1, 0, -1, -1, -1, -1, 0, 0}); // 0x00000100FFFFFFFF = (2^40) + (2^32) - 1 = 1103806595071 assertEquals(1103806595071L, value1.getLong(0)); // 0x 000100FFFFFFFF00 = (2^48) + (2^40) - 1 - 255 = 282574488338176 assertEquals(282574488338176L, value1.getLong(1)); Bytes value2 = w(new byte[] {-1, -1, -1, -1, -1, -1, -1, -1}); assertEquals(-1L, value2.getLong(0)); } @Test void testGetLongNegativeIndex() { assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}).getLong(-1)); } @Test void testGetLongOutOfBound() { assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}).getLong(8)); } @Test void testGetLongNotEnoughBytes() { assertThrows(IndexOutOfBoundsException.class, () -> w(new byte[] {1, 2, 3, 4}).getLong(0)); } @Test void testAsLong() { assertEquals(0, Bytes.EMPTY.toLong()); Bytes value1 = w(new byte[] {0, 0, 1, 0, -1, -1, -1, -1}); // 0x00000100FFFFFFFF = (2^40) + (2^32) - 1 = 1103806595071 assertEquals(1103806595071L, value1.toLong()); assertEquals(1103806595071L, value1.slice(2).toLong()); Bytes value2 = w(new byte[] {0, 1, 0, -1, -1, -1, -1, 0}); // 0x000100FFFFFFFF00 = (2^48) + (2^40) - 1 - 255 = 282574488338176 assertEquals(282574488338176L, value2.toLong()); assertEquals(282574488338176L, value2.slice(1).toLong()); Bytes value3 = w(new byte[] {-1, -1, -1, -1, -1, -1, -1, -1}); assertEquals(-1L, value3.toLong()); } @Test void testAsLongTooManyBytes() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> w(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9}).toLong()); assertEquals("Value of size 9 has more than 8 bytes", exception.getMessage()); } @Test void testSlice() { assertEquals(h("0x"), h("0x0123456789").slice(0, 0)); assertEquals(h("0x"), h("0x0123456789").slice(2, 0)); assertEquals(h("0x01"), h("0x0123456789").slice(0, 1)); assertEquals(h("0x0123"), h("0x0123456789").slice(0, 2)); assertEquals(h("0x4567"), h("0x0123456789").slice(2, 2)); assertEquals(h("0x23456789"), h("0x0123456789").slice(1, 4)); } @Test void testSliceNegativeOffset() { assertThrows(IndexOutOfBoundsException.class, () -> h("0x012345").slice(-1, 2)); } @Test void testSliceOffsetOutOfBound() { assertThrows(IndexOutOfBoundsException.class, () -> h("0x012345").slice(3, 2)); } @Test void testSliceTooLong() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> h("0x012345").slice(1, 3)); assertEquals( "Provided length 3 is too big: the value has size 3 and has only 2 bytes from 1", exception.getMessage()); } @Test void testMutableCopy() { Bytes v = h("0x012345"); MutableBytes mutableCopy = v.mutableCopy(); // Initially, copy must be equal. assertEquals(mutableCopy, v); // Upon modification, original should not have been modified. mutableCopy.set(0, (byte) -1); assertNotEquals(mutableCopy, v); assertEquals(h("0x012345"), v); assertEquals(h("0xFF2345"), mutableCopy); } @Test void testCopyTo() { MutableBytes dest; // The follow does nothing, but simply making sure it doesn't throw. dest = MutableBytes.EMPTY; Bytes.EMPTY.copyTo(dest); assertEquals(Bytes.EMPTY, dest); dest = MutableBytes.create(1); of(1).copyTo(dest); assertEquals(h("0x01"), dest); dest = MutableBytes.create(1); of(10).copyTo(dest); assertEquals(h("0x0A"), dest); dest = MutableBytes.create(2); of(0xff, 0x03).copyTo(dest); assertEquals(h("0xFF03"), dest); dest = MutableBytes.create(4); of(0xff, 0x03).copyTo(dest.mutableSlice(1, 2)); assertEquals(h("0x00FF0300"), dest); } @Test void testCopyToTooSmall() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> of(1, 2, 3).copyTo(MutableBytes.create(2))); assertEquals("Cannot copy 3 bytes to destination of non-equal size 2", exception.getMessage()); } @Test void testCopyToTooBig() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> of(1, 2, 3).copyTo(MutableBytes.create(4))); assertEquals("Cannot copy 3 bytes to destination of non-equal size 4", exception.getMessage()); } @Test void testCopyToWithOffset() { MutableBytes dest; dest = MutableBytes.wrap(new byte[] {1, 2, 3}); Bytes.EMPTY.copyTo(dest, 0); assertEquals(h("0x010203"), dest); dest = MutableBytes.wrap(new byte[] {1, 2, 3}); of(1).copyTo(dest, 1); assertEquals(h("0x010103"), dest); dest = MutableBytes.wrap(new byte[] {1, 2, 3}); of(2).copyTo(dest, 0); assertEquals(h("0x020203"), dest); dest = MutableBytes.wrap(new byte[] {1, 2, 3}); of(1, 1).copyTo(dest, 1); assertEquals(h("0x010101"), dest); dest = MutableBytes.create(4); of(0xff, 0x03).copyTo(dest, 1); assertEquals(h("0x00FF0300"), dest); } @Test void testCopyToWithOffsetTooSmall() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> of(1, 2, 3).copyTo(MutableBytes.create(4), 2)); assertEquals("Cannot copy 3 bytes, destination has only 2 bytes from index 2", exception.getMessage()); } @Test void testCopyToWithNegativeOffset() { assertThrows(IndexOutOfBoundsException.class, () -> of(1, 2, 3).copyTo(MutableBytes.create(10), -1)); } @Test void testCopyToWithOutOfBoundIndex() { assertThrows(IndexOutOfBoundsException.class, () -> of(1, 2, 3).copyTo(MutableBytes.create(10), 10)); } @Test void testAppendTo() { testAppendTo(Bytes.EMPTY, Buffer.buffer(), Bytes.EMPTY); testAppendTo(Bytes.EMPTY, Buffer.buffer(h("0x1234").toArrayUnsafe()), h("0x1234")); testAppendTo(h("0x1234"), Buffer.buffer(), h("0x1234")); testAppendTo(h("0x5678"), Buffer.buffer(h("0x1234").toArrayUnsafe()), h("0x12345678")); } private void testAppendTo(Bytes toAppend, Buffer buffer, Bytes expected) { toAppend.appendTo(buffer); assertEquals(expected, Bytes.wrap(buffer.getBytes())); } @Test void testIsZero() { assertTrue(Bytes.EMPTY.isZero()); assertTrue(Bytes.of(0).isZero()); assertTrue(Bytes.of(0, 0, 0).isZero()); assertFalse(Bytes.of(1).isZero()); assertFalse(Bytes.of(1, 0, 0).isZero()); assertFalse(Bytes.of(0, 0, 1).isZero()); assertFalse(Bytes.of(0, 0, 1, 0, 0).isZero()); } @Test void testIsEmpty() { assertTrue(Bytes.EMPTY.isEmpty()); assertFalse(Bytes.of(0).isEmpty()); assertFalse(Bytes.of(0, 0, 0).isEmpty()); assertFalse(Bytes.of(1).isEmpty()); } @Test void findsCommonPrefix() { Bytes v = Bytes.of(1, 2, 3, 4, 5, 6, 7); Bytes o = Bytes.of(1, 2, 3, 4, 4, 3, 2); assertEquals(4, v.commonPrefixLength(o)); assertEquals(Bytes.of(1, 2, 3, 4), v.commonPrefix(o)); } @Test void findsCommonPrefixOfShorter() { Bytes v = Bytes.of(1, 2, 3, 4, 5, 6, 7); Bytes o = Bytes.of(1, 2, 3, 4); assertEquals(4, v.commonPrefixLength(o)); assertEquals(Bytes.of(1, 2, 3, 4), v.commonPrefix(o)); } @Test void findsCommonPrefixOfLonger() { Bytes v = Bytes.of(1, 2, 3, 4); Bytes o = Bytes.of(1, 2, 3, 4, 4, 3, 2); assertEquals(4, v.commonPrefixLength(o)); assertEquals(Bytes.of(1, 2, 3, 4), v.commonPrefix(o)); } @Test void findsCommonPrefixOfSliced() { Bytes v = Bytes.of(1, 2, 3, 4).slice(2, 2); Bytes o = Bytes.of(3, 4, 3, 3, 2).slice(3, 2); assertEquals(1, v.commonPrefixLength(o)); assertEquals(Bytes.of(3), v.commonPrefix(o)); } @Test void testTrimLeadingZeroes() { assertEquals(h("0x"), h("0x").trimLeadingZeros()); assertEquals(h("0x"), h("0x00").trimLeadingZeros()); assertEquals(h("0x"), h("0x00000000").trimLeadingZeros()); assertEquals(h("0x01"), h("0x01").trimLeadingZeros()); assertEquals(h("0x01"), h("0x00000001").trimLeadingZeros()); assertEquals(h("0x3010"), h("0x3010").trimLeadingZeros()); assertEquals(h("0x3010"), h("0x00003010").trimLeadingZeros()); assertEquals(h("0xFFFFFFFF"), h("0xFFFFFFFF").trimLeadingZeros()); assertEquals(h("0xFFFFFFFF"), h("0x000000000000FFFFFFFF").trimLeadingZeros()); } @Test void slideToEnd() { assertEquals(Bytes.of(1, 2, 3, 4), Bytes.of(1, 2, 3, 4).slice(0)); assertEquals(Bytes.of(2, 3, 4), Bytes.of(1, 2, 3, 4).slice(1)); assertEquals(Bytes.of(3, 4), Bytes.of(1, 2, 3, 4).slice(2)); assertEquals(Bytes.of(4), Bytes.of(1, 2, 3, 4).slice(3)); } @Test void slicePastEndReturnsEmpty() { assertEquals(Bytes.EMPTY, Bytes.of(1, 2, 3, 4).slice(4)); assertEquals(Bytes.EMPTY, Bytes.of(1, 2, 3, 4).slice(5)); } @Test void testUpdate() throws NoSuchAlgorithmException { // Digest the same byte array in 4 ways: // 1) directly from the array // 2) after wrapped using the update() method // 3) after wrapped and copied using the update() method // 4) after wrapped but getting the byte manually // and check all compute the same digest. MessageDigest md1 = MessageDigest.getInstance("SHA-1"); MessageDigest md2 = MessageDigest.getInstance("SHA-1"); MessageDigest md3 = MessageDigest.getInstance("SHA-1"); MessageDigest md4 = MessageDigest.getInstance("SHA-1"); byte[] toDigest = new BigInteger("12324029423415041783577517238472017314").toByteArray(); Bytes wrapped = w(toDigest); byte[] digest1 = md1.digest(toDigest); wrapped.update(md2); byte[] digest2 = md2.digest(); wrapped.copy().update(md3); byte[] digest3 = md3.digest(); for (int i = 0; i < wrapped.size(); i++) md4.update(wrapped.get(i)); byte[] digest4 = md4.digest(); assertArrayEquals(digest2, digest1); assertArrayEquals(digest3, digest1); assertArrayEquals(digest4, digest1); } @Test void testArrayExtraction() { // extractArray() and getArrayUnsafe() have essentially the same contract... testArrayExtraction(Bytes::toArray); testArrayExtraction(Bytes::toArrayUnsafe); // But on top of the basic, extractArray() guarantees modifying the returned array is safe from // impacting the original value (not that getArrayUnsafe makes no guarantees here one way or // another, so there is nothing to test). byte[] orig = new byte[] {1, 2, 3, 4}; Bytes value = w(orig); byte[] extracted = value.toArray(); assertArrayEquals(orig, extracted); Arrays.fill(extracted, (byte) -1); assertArrayEquals(extracted, new byte[] {-1, -1, -1, -1}); assertArrayEquals(orig, new byte[] {1, 2, 3, 4}); assertEquals(of(1, 2, 3, 4), value); } private void testArrayExtraction(Function extractor) { byte[] bytes = new byte[0]; assertArrayEquals(extractor.apply(Bytes.EMPTY), bytes); byte[][] toTest = new byte[][] {new byte[] {1}, new byte[] {1, 2, 3, 4, 5, 6}, new byte[] {-1, -1, 0, -1}}; for (byte[] array : toTest) { assertArrayEquals(extractor.apply(w(array)), array); } // Test slightly more complex interactions assertArrayEquals(extractor.apply(w(new byte[] {1, 2, 3, 4, 5}).slice(2, 2)), new byte[] {3, 4}); assertArrayEquals(extractor.apply(w(new byte[] {1, 2, 3, 4, 5}).slice(2, 0)), new byte[] {}); } @Test void testToString() { assertEquals("0x", Bytes.EMPTY.toString()); assertEquals("0x01", of(1).toString()); assertEquals("0x0AFF03", of(0x0a, 0xff, 0x03).toString()); } @Test void testHasLeadingZeroByte() { assertFalse(Bytes.fromHexString("0x").hasLeadingZeroByte()); assertTrue(Bytes.fromHexString("0x0012").hasLeadingZeroByte()); assertFalse(Bytes.fromHexString("0x120012").hasLeadingZeroByte()); } @Test void testNumberOfLeadingZeroBytes() { assertEquals(0, Bytes.fromHexString("0x12").numberOfLeadingZeroBytes()); assertEquals(1, Bytes.fromHexString("0x0012").numberOfLeadingZeroBytes()); assertEquals(2, Bytes.fromHexString("0x000012").numberOfLeadingZeroBytes()); assertEquals(0, Bytes.fromHexString("0x").numberOfLeadingZeroBytes()); assertEquals(1, Bytes.fromHexString("0x00").numberOfLeadingZeroBytes()); assertEquals(2, Bytes.fromHexString("0x0000").numberOfLeadingZeroBytes()); assertEquals(3, Bytes.fromHexString("0x000000").numberOfLeadingZeroBytes()); } @Test void testHasLeadingZeroBit() { assertFalse(Bytes.fromHexString("0x").hasLeadingZero()); assertTrue(Bytes.fromHexString("0x01").hasLeadingZero()); assertFalse(Bytes.fromHexString("0xFF0012").hasLeadingZero()); } @Test void testEquals() { SecureRandom random = new SecureRandom(); byte[] key = new byte[32]; random.nextBytes(key); Bytes b = w(key); Bytes b2 = w(key); assertEquals(b.hashCode(), b2.hashCode()); } @Test void testEqualsWithOffset() { SecureRandom random = new SecureRandom(); byte[] key = new byte[32]; random.nextBytes(key); Bytes b = w(key).slice(16, 4); Bytes b2 = w(key).slice(16, 8).slice(0, 4); assertEquals(b, b2); } @Test void testHashCode() { SecureRandom random = new SecureRandom(); byte[] key = new byte[32]; random.nextBytes(key); Bytes b = w(key); Bytes b2 = w(key); assertEquals(b.hashCode(), b2.hashCode()); } @Test void testHashCodeWithOffset() { SecureRandom random = new SecureRandom(); byte[] key = new byte[32]; random.nextBytes(key); Bytes b = w(key).slice(16, 16); Bytes b2 = w(key).slice(16, 16); assertEquals(b.hashCode(), b2.hashCode()); } @Test void testHashCodeWithByteBufferWrappingBytes() { SecureRandom random = new SecureRandom(); byte[] key = new byte[32]; random.nextBytes(key); Bytes b = w(key); Bytes other = Bytes.wrapByteBuffer(ByteBuffer.wrap(key)); assertEquals(b.hashCode(), other.hashCode()); } @Test void testEqualsWithByteBufferWrappingBytes() { SecureRandom random = new SecureRandom(); byte[] key = new byte[32]; random.nextBytes(key); Bytes b = w(key); Bytes other = Bytes.wrapByteBuffer(ByteBuffer.wrap(key)); assertEquals(b, other); } @Test void testHashCodeWithBufferWrappingBytes() { SecureRandom random = new SecureRandom(); byte[] key = new byte[32]; random.nextBytes(key); Bytes b = w(key); Bytes other = Bytes.wrapBuffer(Buffer.buffer(key)); assertEquals(b.hashCode(), other.hashCode()); } @Test void testEqualsWithBufferWrappingBytes() { SecureRandom random = new SecureRandom(); byte[] key = new byte[32]; random.nextBytes(key); Bytes b = w(key); Bytes other = Bytes.wrapBuffer(Buffer.buffer(key)); assertEquals(b, other); } @Test void testHashCodeWithByteBufWrappingBytes() { SecureRandom random = new SecureRandom(); byte[] key = new byte[32]; random.nextBytes(key); Bytes b = w(key); Bytes other = Bytes.wrapByteBuf(Unpooled.copiedBuffer(key)); assertEquals(b.hashCode(), other.hashCode()); } @Test void testEqualsWithByteBufWrappingBytes() { SecureRandom random = new SecureRandom(); byte[] key = new byte[32]; random.nextBytes(key); Bytes b = w(key); Bytes other = Bytes.wrapByteBuf(Unpooled.copiedBuffer(key)); assertEquals(b, other); } } cava-0.6.0/bytes/src/test/java/net/consensys/cava/bytes/ConcatenatedBytesTest.java000066400000000000000000000075111341750772100302330ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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 net.consensys.cava.bytes.Bytes.fromHexString; import static net.consensys.cava.bytes.Bytes.wrap; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; 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 ConcatenatedBytesTest { @ParameterizedTest @MethodSource("concatenatedWrapProvider") void concatenatedWrap(Object arr1, Object arr2) { byte[] first = (byte[]) arr1; byte[] second = (byte[]) arr2; byte[] res = wrap(wrap(first), wrap(second)).toArray(); assertArrayEquals(Arrays.copyOfRange(res, 0, first.length), first); assertArrayEquals(Arrays.copyOfRange(res, first.length, res.length), second); } private static Stream concatenatedWrapProvider() { return Stream.of( Arguments.of(new byte[] {}, new byte[] {}), Arguments.of(new byte[] {}, new byte[] {1, 2, 3}), Arguments.of(new byte[] {1, 2, 3}, new byte[] {}), Arguments.of(new byte[] {1, 2, 3}, new byte[] {4, 5})); } @Test void testConcatenatedWrapReflectsUpdates() { byte[] first = new byte[] {1, 2, 3}; byte[] second = new byte[] {4, 5}; byte[] expected1 = new byte[] {1, 2, 3, 4, 5}; Bytes res = wrap(wrap(first), wrap(second)); assertArrayEquals(res.toArray(), expected1); first[1] = 42; second[0] = 42; byte[] expected2 = new byte[] {1, 42, 3, 42, 5}; assertArrayEquals(res.toArray(), expected2); } @Test void shouldReadConcatenatedValue() { Bytes bytes = wrap(fromHexString("0x01234567"), fromHexString("0x89ABCDEF")); assertEquals(8, bytes.size()); assertEquals("0x0123456789ABCDEF", bytes.toHexString()); } @Test void shouldSliceConcatenatedValue() { Bytes bytes = wrap( fromHexString("0x01234567"), fromHexString("0x89ABCDEF"), fromHexString("0x01234567"), fromHexString("0x89ABCDEF")); assertEquals("0x", bytes.slice(4, 0).toHexString()); assertEquals("0x0123456789ABCDEF0123456789ABCDEF", bytes.slice(0, 16).toHexString()); assertEquals("0x01234567", bytes.slice(0, 4).toHexString()); assertEquals("0x0123", bytes.slice(0, 2).toHexString()); assertEquals("0x6789", bytes.slice(3, 2).toHexString()); assertEquals("0x89ABCDEF", bytes.slice(4, 4).toHexString()); assertEquals("0xABCD", bytes.slice(5, 2).toHexString()); assertEquals("0xEF012345", bytes.slice(7, 4).toHexString()); assertEquals("0x01234567", bytes.slice(8, 4).toHexString()); assertEquals("0x456789ABCDEF", bytes.slice(10, 6).toHexString()); assertEquals("0x89ABCDEF", bytes.slice(12, 4).toHexString()); } @Test void shouldReadDeepConcatenatedValue() { Bytes bytes = wrap( wrap(fromHexString("0x01234567"), fromHexString("0x89ABCDEF")), wrap(fromHexString("0x01234567"), fromHexString("0x89ABCDEF")), fromHexString("0x01234567"), fromHexString("0x89ABCDEF")); assertEquals(24, bytes.size()); assertEquals("0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF", bytes.toHexString()); } } cava-0.6.0/concurrent-coroutines/000077500000000000000000000000001341750772100170105ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/build.gradle000066400000000000000000000005361341750772100212730ustar00rootroot00000000000000description = 'Kotlin coroutine extensions for cava concurrency primitives.' dependencies { compile project(':concurrent') compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core' testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/concurrent-coroutines/src/000077500000000000000000000000001341750772100175775ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/main/000077500000000000000000000000001341750772100205235ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/main/kotlin/000077500000000000000000000000001341750772100220235ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/main/kotlin/net/000077500000000000000000000000001341750772100226115ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/main/kotlin/net/consensys/000077500000000000000000000000001341750772100246355ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/main/kotlin/net/consensys/cava/000077500000000000000000000000001341750772100255475ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/main/kotlin/net/consensys/cava/concurrent/000077500000000000000000000000001341750772100277315ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/main/kotlin/net/consensys/cava/concurrent/coroutines/000077500000000000000000000000001341750772100321235ustar00rootroot00000000000000AsyncCompletion.kt000066400000000000000000000175541341750772100355270ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/main/kotlin/net/consensys/cava/concurrent/coroutines/* * Copyright 2018 ConsenSys AG. * * 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 import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.ObsoleteCoroutinesApi import kotlinx.coroutines.newCoroutineContext import kotlinx.coroutines.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.Continuation import kotlin.coroutines.ContinuationInterceptor import kotlin.coroutines.CoroutineContext import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException /** * Starts new co-routine and returns its result as an implementation of [AsyncCompletion]. * The running co-routine is cancelled when the resulting future is cancelled or otherwise completed. * * Co-routine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] * argument. If the context does not have any dispatcher nor any other [ContinuationInterceptor], then * [Dispatchers.Default] is used. The parent job is inherited from a [CoroutineScope] as well, but it can also be * overridden with corresponding [coroutineContext] element. * * By default, the co-routine 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 * `AsyncResult` framework does not provide the corresponding capability) and produces [IllegalArgumentException]. * * See [newCoroutineContext][CoroutineScope.newCoroutineContext] for a description of debugging facilities that are * available for newly created co-routine. * * @param context Additional to [CoroutineScope.coroutineContext] context of the coroutine. * @param start Co-routine start option. The default value is [CoroutineStart.DEFAULT]. * @param block The co-routine code. */ @UseExperimental(InternalCoroutinesApi::class, ObsoleteCoroutinesApi::class, ExperimentalCoroutinesApi::class) fun CoroutineScope.asyncCompletion( context: CoroutineContext = Dispatchers.Default, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): AsyncCompletion { require(!start.isLazy) { "$start start is not supported" } val newContext = this.newCoroutineContext(context) val job = Job(newContext[Job]) val coroutine = AsyncCompletionCoroutine(newContext + job) job.invokeOnCompletion { coroutine.asynCompletion.cancel() } coroutine.asynCompletion.whenComplete { job.cancel() } start(block, receiver = coroutine, completion = coroutine) // use the specified start strategy return coroutine.asynCompletion } private class AsyncCompletionCoroutine( override val context: CoroutineContext, val asynCompletion: CompletableAsyncCompletion = AsyncCompletion.incomplete() ) : Continuation, CoroutineScope { override val coroutineContext: CoroutineContext get() = context override fun resumeWith(result: Result) { result .onSuccess { asynCompletion.complete() } .onFailure { asynCompletion.completeExceptionally(it) } } } /** * Converts this deferred value to a [AsyncCompletion]. * The deferred value is cancelled when the returned [AsyncCompletion] is cancelled or otherwise completed. */ @UseExperimental(ObsoleteCoroutinesApi::class) fun Deferred.asAsyncCompletion(): AsyncCompletion { val asyncCompletion = AsyncCompletion.incomplete() asyncCompletion.whenComplete { cancel() } invokeOnCompletion { try { asyncCompletion.complete() } catch (exception: Exception) { asyncCompletion.completeExceptionally(exception) } } return asyncCompletion } /** * Converts this job to a [AsyncCompletion]. * The job is cancelled when the returned [AsyncCompletion] is cancelled or otherwise completed. */ @UseExperimental(ObsoleteCoroutinesApi::class) fun Job.asAsyncCompletion(): AsyncCompletion { val asyncCompletion = AsyncCompletion.incomplete() asyncCompletion.whenComplete { cancel() } invokeOnCompletion { try { asyncCompletion.complete() } catch (exception: Exception) { asyncCompletion.completeExceptionally(exception) } } return asyncCompletion } /** * Converts this [AsyncCompletion] to an instance of [Deferred]. * The [AsyncCompletion] is cancelled when the resulting deferred is cancelled. */ @UseExperimental(ObsoleteCoroutinesApi::class) fun AsyncCompletion.asDeferred(): Deferred { // Fast path if already completed if (isDone) { return try { CompletableDeferred(join()) } catch (e: Throwable) { // unwrap original cause from CompletionException val original = (e as? CompletionException)?.cause ?: e CompletableDeferred().also { it.completeExceptionally(original) } } } val result = CompletableDeferred() whenComplete { exception -> if (exception == null) { result.complete(Unit) } else { result.completeExceptionally(exception) } } result.invokeOnCompletion { this.cancel() } return result } /** * Awaits for completion of the [AsyncCompletion] without blocking a thread. * * This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this * suspending function is waiting, this function stops waiting for the [AsyncCompletion] and immediately resumes with * [CancellationException]. * * Note, that [AsyncCompletion] does not support prompt removal of listeners, so on cancellation of this wait a few * small objects will remain in the [AsyncCompletion] stack of completion actions until it completes itself. However, * care is taken to clear the reference to the waiting coroutine itself, so that its memory can be released even if the * [AsyncCompletion] never completes. */ suspend fun AsyncCompletion.await() { // fast path when CompletableFuture is already done (does not suspend) if (isDone) { try { return join() } catch (e: CompletionException) { throw e.cause ?: e // unwrap original cause from CompletionException } } // slow path -- suspend return suspendCancellableCoroutine { cont: CancellableContinuation -> val consumer = ContinuationConsumer(cont) whenComplete(consumer) cont.invokeOnCancellation { consumer.cont = null // shall clear reference to continuation } } } private class ContinuationConsumer( @Volatile @JvmField var cont: Continuation? ) : Consumer { override fun accept(exception: Throwable?) { val cont = this.cont ?: return // atomically read current value unless null if (exception == null) { // the future has been completed normally cont.resume(Unit) } else { // the future has completed with an exception, unwrap it to provide consistent view of .await() result and to propagate only original exception cont.resumeWithException((exception as? CompletionException)?.cause ?: exception) } } } AsyncResult.kt000066400000000000000000000166331341750772100346710ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/main/kotlin/net/consensys/cava/concurrent/coroutines/* * Copyright 2018 ConsenSys AG. * * 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 import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.ObsoleteCoroutinesApi import kotlinx.coroutines.newCoroutineContext import kotlinx.coroutines.suspendCancellableCoroutine import net.consensys.cava.concurrent.AsyncResult import net.consensys.cava.concurrent.CompletableAsyncResult import java.util.concurrent.CancellationException import java.util.concurrent.CompletionException import java.util.function.BiConsumer import kotlin.Result import kotlin.coroutines.Continuation import kotlin.coroutines.CoroutineContext import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException /** * Starts new co-routine and returns its result as an implementation of [AsyncResult]. * The running co-outine is cancelled when the resulting future is cancelled or otherwise completed. * * Co-routine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] * argument. If the context does not have any dispatcher nor any other [ContinuationInterceptor], then * [Dispatchers.Default] is used. The parent job is inherited from a [CoroutineScope] as well, but it can also be * overridden with corresponding [coroutineContext] element. * * By default, the co-routine 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 * `AsyncResult` framework does not provide the corresponding capability) and produces [IllegalArgumentException]. * * See [newCoroutineContext][CoroutineScope.newCoroutineContext] for a description of debugging facilities that are * available for newly created co-routine. * * @param context Additional to [CoroutineScope.coroutineContext] context of the coroutine. * @param start Co-routine start option. The default value is [CoroutineStart.DEFAULT]. * @param block The co-routine code. */ @UseExperimental(InternalCoroutinesApi::class, ObsoleteCoroutinesApi::class, ExperimentalCoroutinesApi::class) fun CoroutineScope.asyncResult( context: CoroutineContext = Dispatchers.Default, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T ): AsyncResult { require(!start.isLazy) { "$start start is not supported" } val newContext = this.newCoroutineContext(context) val job = Job(newContext[Job]) val coroutine = AsyncResultCoroutine(newContext + job) job.invokeOnCompletion { coroutine.asyncResult.cancel() } coroutine.asyncResult.whenComplete { _, _ -> job.cancel() } start(block, receiver = coroutine, completion = coroutine) // use the specified start strategy return coroutine.asyncResult } private class AsyncResultCoroutine( override val context: CoroutineContext, val asyncResult: CompletableAsyncResult = AsyncResult.incomplete() ) : Continuation, CoroutineScope { override val coroutineContext: CoroutineContext get() = context override fun resumeWith(result: Result) { result .onSuccess { asyncResult.complete(it) } .onFailure { asyncResult.completeExceptionally(it) } } } /** * Converts this deferred value to an [AsyncResult]. * The deferred value is cancelled when the returned [AsyncResult] is cancelled or otherwise completed. */ @UseExperimental(ExperimentalCoroutinesApi::class, ObsoleteCoroutinesApi::class) fun Deferred.asAsyncResult(): AsyncResult { val asyncResult = AsyncResult.incomplete() asyncResult.whenComplete { _, _ -> cancel() } invokeOnCompletion { try { asyncResult.complete(getCompleted()) } catch (exception: Exception) { asyncResult.completeExceptionally(exception) } } return asyncResult } /** * Converts this [AsyncResult] to an instance of [Deferred]. * The [AsyncResult] is cancelled when the resulting deferred is cancelled. */ @UseExperimental(ObsoleteCoroutinesApi::class) fun AsyncResult.asDeferred(): Deferred { // Fast path if already completed if (isDone) { return try { @Suppress("UNCHECKED_CAST") CompletableDeferred(get() as T) } catch (e: Throwable) { // unwrap original cause from CompletionException val original = (e as? CompletionException)?.cause ?: e CompletableDeferred().also { it.completeExceptionally(original) } } } val result = CompletableDeferred() whenComplete { value, exception -> if (exception == null) { result.complete(value) } else { result.completeExceptionally(exception) } } result.invokeOnCompletion { this.cancel() } return result } /** * Awaits for completion of the [AsyncResult] without blocking a thread. * * This suspending function is cancellable. * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function * stops waiting for the [AsyncResult] and immediately resumes with [CancellationException]. * * Note, that [AsyncResult] does not support prompt removal of listeners, so on cancellation of this wait * a few small objects will remain in the [AsyncResult] stack of completion actions until it completes itself. * However, care is taken to clear the reference to the waiting coroutine itself, so that its memory can be * released even if the [AsyncResult] never completes. */ suspend fun AsyncResult.await(): T { // fast path when CompletableFuture is already done (does not suspend) if (isDone) { try { @Suppress("UNCHECKED_CAST") return get() as T } catch (e: CompletionException) { throw e.cause ?: e // unwrap original cause from CompletionException } } // slow path -- suspend return suspendCancellableCoroutine { cont: CancellableContinuation -> val consumer = ContinuationBiConsumer(cont) whenComplete(consumer) cont.invokeOnCancellation { consumer.cont = null // shall clear reference to continuation } } } private class ContinuationBiConsumer( @Volatile @JvmField var cont: Continuation? ) : BiConsumer { @Suppress("UNCHECKED_CAST") override fun accept(result: T?, exception: Throwable?) { val cont = this.cont ?: return // atomically read current value unless null if (exception == null) { // the future has been completed normally cont.resume(result as T) } else { // the future has completed with an exception, unwrap it to provide consistent view of .await() result and to propagate only original exception cont.resumeWithException((exception as? CompletionException)?.cause ?: exception) } } } CoroutineLatch.kt000066400000000000000000000064751341750772100353430ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/main/kotlin/net/consensys/cava/concurrent/coroutines/* * Copyright 2018 ConsenSys AG. * * 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 import kotlinx.coroutines.suspendCancellableCoroutine import java.util.concurrent.atomic.AtomicInteger import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException /** * A co-routine synchronization aid that allows co-routines to wait until a set of operations being performed * has completed. * * The latch is initialized with a given count. If the latch count is greater than zero, the `await()` method will * suspend until the count reaches zero due to invocations of the `countDown()` method, at which point all suspended * co-routines will be resumed. * * Unlike the Java `CountDownLatch`, this latch allows the count to be increased via invocation of the `countUp()` * method. Increasing the count from zero will result in calls to `await()` suspending again. Note that the count may * be negative, requiring multiple calls to `countUp()` before calls to `await()` suspend. * * @param initial The initial count of the latch, which may be positive, zero, or negative. * @constructor A latch. */ class CoroutineLatch(initial: Int) { private val atomicCount = AtomicInteger(initial) private var waitingCoroutines = mutableListOf>() /** * The current latch count. */ val count: Int get() = atomicCount.get() /** * Indicates if the latch is open (`count <= 0`). */ val isOpen: Boolean get() = atomicCount.get() <= 0 /** * Decrease the latch count, potentially opening the latch and awakening suspending co-routines. * * @return `true` if the latch was opened as a result of this invocation. */ fun countDown(): Boolean { var toAwaken: List>? = null synchronized(this) { if (atomicCount.decrementAndGet() == 0) { toAwaken = waitingCoroutines waitingCoroutines = mutableListOf() } } toAwaken?.forEach { it.resume(Unit) } return toAwaken != null } /** * Increase the latch count, potentially closing the latch. * * @return `true` if the latch was closed as a result of this invocation. */ fun countUp(): Boolean = atomicCount.incrementAndGet() == 1 /** * Await the latch opening. If already open, return without suspending. */ suspend fun await() { if (atomicCount.get() <= 0) { return } suspendCancellableCoroutine { cont: Continuation -> try { var suspended: Boolean synchronized(this) { suspended = atomicCount.get() > 0 if (suspended) { waitingCoroutines.add(cont) } } if (!suspended) { cont.resume(Unit) } } catch (e: Throwable) { cont.resumeWithException(e) } } } } Retryable.kt000066400000000000000000000112751341750772100343430ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/main/kotlin/net/consensys/cava/concurrent/coroutines/* * Copyright 2019 ConsenSys AG. * * 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 import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.withTimeoutOrNull /** * Retry a suspending block until a non-null result is obtained. * * @param retryDelay the delay between each attempt * @param block the suspending block to be executed * @return the first non-null result */ suspend fun CoroutineScope.retry( retryDelay: Long, block: suspend (Int) -> R? ): R = retry({ retryDelay }, block)!! /** * Retry a suspending block until a non-null result is obtained. * * @param retryDelay the delay between each attempt * @param maxRetries the maximum number of attempts * @param block the suspending block to be executed * @return the first non-null result, or `null` if all attempts fail */ suspend fun CoroutineScope.retry( retryDelay: Long, maxRetries: Int, block: suspend (Int) -> R? ): R? = retry({ i -> if (i > maxRetries) null else retryDelay }, block) /** * Retry a suspending block until a non-null result is obtained. * * @param retryDelay a function returning the delay that should follow each attempt, or `null` if no further attempts * should be made * @param block the suspending block to be executed * @return the first non-null result, or `null` if all attempts fail */ @UseExperimental(ExperimentalCoroutinesApi::class) suspend fun CoroutineScope.retry( retryDelay: (Int) -> Long?, block: suspend (Int) -> R? ): R? { val jobs = mutableSetOf() val result = CompletableDeferred() result.invokeOnCompletion { jobs.forEach { job -> job.cancel() } } var stopped = false var i = 1 while (true) { val attempt = i val delayTime = retryDelay(attempt) ?: break val deferred = async { block(attempt) } deferred.invokeOnCompletion { e -> try { jobs.remove(deferred) if (e is CancellationException) { // ignore return@invokeOnCompletion } if (e != null) { result.completeExceptionally(e) } else { deferred.getCompleted()?.let { r -> result.complete(r) } if (stopped && jobs.isEmpty()) { result.complete(null) } } } catch (e: Throwable) { result.completeExceptionally(e) } } jobs.add(deferred) val r = withTimeoutOrNull(delayTime) { result.await() } if (r != null) { return r } ++i } stopped = true if (jobs.isEmpty()) { return null } return result.await() } /** * Cancel and retry a suspending block until a non-null result is obtained. * * @param timeout the delay before re-attempting * @param block the suspending block to be executed * @return the first non-null result */ suspend fun timeoutAndRetry( timeout: Long, block: suspend (Int) -> R? ): R = timeoutAndRetry({ timeout }, block)!! /** * Cancel and retry a suspending block until a non-null result is obtained. * * @param timeout the delay before re-attempting * @param maxRetries the maximum number of attempts * @param block the suspending block to be executed * @return the first non-null result, or `null` if all attempts fail */ suspend fun timeoutAndRetry( timeout: Long, maxRetries: Int, block: suspend (Int) -> R? ): R? = timeoutAndRetry({ i -> if (i >= maxRetries) null else timeout }, block) /** * Cancel and retry a suspending block until a non-null result is obtained. * * @param timeout a function returning the delay that should follow each attempt, or `null` if no further attempts * should be made * @param block the suspending block to be executed * @return the first non-null result, or `null` if all attempts fail */ suspend fun timeoutAndRetry( timeout: (Int) -> Long?, block: suspend (Int) -> R? ): R? { var i = 1 while (true) { val attempt = i val time = timeout(attempt) ?: return null val r = withTimeoutOrNull(time) { block(attempt) } if (r != null) { return r } ++i } } cava-0.6.0/concurrent-coroutines/src/test/000077500000000000000000000000001341750772100205565ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/test/kotlin/000077500000000000000000000000001341750772100220565ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/test/kotlin/net/000077500000000000000000000000001341750772100226445ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/test/kotlin/net/consensys/000077500000000000000000000000001341750772100246705ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/test/kotlin/net/consensys/cava/000077500000000000000000000000001341750772100256025ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/test/kotlin/net/consensys/cava/concurrent/000077500000000000000000000000001341750772100277645ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/test/kotlin/net/consensys/cava/concurrent/coroutines/000077500000000000000000000000001341750772100321565ustar00rootroot00000000000000CoroutineLatchTest.kt000066400000000000000000000054551341750772100362330ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/test/kotlin/net/consensys/cava/concurrent/coroutines/* * Copyright 2018 ConsenSys AG. * * 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 import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows internal class CoroutineLatchTest { @Test fun shouldntSuspendWhenLatchIsOpen() = runBlocking { withTimeout(1) { CoroutineLatch(0).await() } withTimeout(1) { CoroutineLatch(-1).await() } } @Test fun shouldUnsuspendWhenLatchOpens() = runBlocking { val latch = CoroutineLatch(2) assertFalse(latch.isOpen) assertEquals(2, latch.count) var ok = false var done = false val job = async { latch.await() assertTrue(ok, "failed to suspend") done = true } Thread.sleep(100) assertFalse(latch.countDown()) assertFalse(latch.isOpen) assertEquals(1, latch.count) Thread.sleep(100) assertFalse(done, "woke up too early") ok = true assertTrue(latch.countDown()) assertTrue(latch.isOpen) assertEquals(0, latch.count) job.await() assertTrue(done, "failed to wakeup") } @Test fun shouldSuspendWhenLatchCloses() = runBlocking { val latch = CoroutineLatch(-1) assertTrue(latch.isOpen) assertEquals(-1, latch.count) withTimeout(1) { latch.await() } assertFalse(latch.countUp()) assertTrue(latch.isOpen) assertEquals(0, latch.count) withTimeout(1) { latch.await() } assertTrue(latch.countUp()) assertFalse(latch.isOpen) assertEquals(1, latch.count) var ok = false var done = false val job = async { latch.await() assertTrue(ok, "failed to suspend") done = true } ok = true assertTrue(latch.countDown()) assertTrue(latch.isOpen) job.await() assertTrue(done, "failed to wakeup") } @Test fun shouldTimeoutWhenBlocked() { assertThrows { runBlocking { withTimeout(1) { CoroutineLatch(1).await() } } } } } RetryableTest.kt000066400000000000000000000052161341750772100352340ustar00rootroot00000000000000cava-0.6.0/concurrent-coroutines/src/test/kotlin/net/consensys/cava/concurrent/coroutines/* * Copyright 2019 ConsenSys AG. * * 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 import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import java.lang.RuntimeException private val NOOP_EXCEPTION_HANDLER = CoroutineExceptionHandler { _, _ -> } internal class RetryableTest { @Test fun shouldNotRetryIfFirstAttemptReturns() = runBlocking { var attempts = 0 val result = retry(500) { attempts++ "done" } assertEquals("done", result) assertEquals(1, attempts) } @Test fun shouldRetryUntilSuccess() = runBlocking { var attempts = 0 val result = retry(100) { i -> attempts++ delay(470) "done $i" } assertEquals("done 1", result) assertEquals(5, attempts) } @Test fun shouldReturnAnySuccess() = runBlocking { var attempts = 0 val result = retry(25) { i -> attempts++ delay(if (i == 4) 60 else 1000) "done $i" } assertEquals("done 4", result) assertEquals(6, attempts) } @Test fun shouldStopRetryingAfterMaxAttempts() = runBlocking { var attempts = 0 val result = retry(50, 3) { i -> attempts++ delay(250) "done $i" } assertEquals("done 1", result) assertEquals(3, attempts) } @Test fun shouldReturnNullIfAllAttemptsFail() = runBlocking { var attempts = 0 val result = retry(50, 3) { attempts++ delay(250) null } assertNull(result) assertEquals(3, attempts) } @Test fun shouldThrowIfAttemptThrows() { var attempts = 0 val e = assertThrows { runBlocking(NOOP_EXCEPTION_HANDLER) { retry(25) { i -> attempts++ if (i == 4) { throw RuntimeException("catch me") } delay(1000) "done $i" } } } assertEquals("catch me", e.message) assertEquals(4, attempts) } } cava-0.6.0/concurrent/000077500000000000000000000000001341750772100146205ustar00rootroot00000000000000cava-0.6.0/concurrent/build.gradle000066400000000000000000000005521341750772100171010ustar00rootroot00000000000000description = 'Classes and utilities for working with concurrency.' dependencies { compile 'com.google.guava:guava' compileOnly 'io.vertx:vertx-core' testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testCompile 'org.assertj:assertj-core' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/concurrent/src/000077500000000000000000000000001341750772100154075ustar00rootroot00000000000000cava-0.6.0/concurrent/src/main/000077500000000000000000000000001341750772100163335ustar00rootroot00000000000000cava-0.6.0/concurrent/src/main/java/000077500000000000000000000000001341750772100172545ustar00rootroot00000000000000cava-0.6.0/concurrent/src/main/java/net/000077500000000000000000000000001341750772100200425ustar00rootroot00000000000000cava-0.6.0/concurrent/src/main/java/net/consensys/000077500000000000000000000000001341750772100220665ustar00rootroot00000000000000cava-0.6.0/concurrent/src/main/java/net/consensys/cava/000077500000000000000000000000001341750772100230005ustar00rootroot00000000000000cava-0.6.0/concurrent/src/main/java/net/consensys/cava/concurrent/000077500000000000000000000000001341750772100251625ustar00rootroot00000000000000cava-0.6.0/concurrent/src/main/java/net/consensys/cava/concurrent/AsyncCompletion.java000066400000000000000000000413151341750772100311400ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.Arrays; import java.util.Collection; import java.util.concurrent.CompletableFuture; 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.Consumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; import io.vertx.core.Vertx; import io.vertx.core.WorkerExecutor; /** * A completion that will be complete at a future time. */ public interface AsyncCompletion { AsyncCompletion COMPLETED = new DefaultCompletableAsyncCompletion(CompletableFuture.completedFuture(null)); /** * Return an already completed completion. * * @return A completed completion. */ static AsyncCompletion completed() { return COMPLETED; } /** * Return an already failed completion, caused by the given exception. * * @param ex The exception. * @return A failed result. */ static AsyncCompletion exceptional(Throwable ex) { requireNonNull(ex); CompletableAsyncCompletion completion = new DefaultCompletableAsyncCompletion(); completion.completeExceptionally(ex); return completion; } /** * Return an incomplete completion, that can be later completed or failed. * * @return An incomplete completion. */ static CompletableAsyncCompletion incomplete() { return new DefaultCompletableAsyncCompletion(); } /** * Returns an {@link AsyncCompletion} that completes when all of the given completions complete. If any completions * complete exceptionally, then the resulting completion also completes exceptionally. * * @param cs The completions to combine. * @return A completion. */ static AsyncCompletion allOf(AsyncCompletion... cs) { return allOf(Arrays.stream(cs)); } /** * Returns an {@link AsyncCompletion} that completes when all of the given completions complete. If any completions * complete exceptionally, then the resulting completion also completes exceptionally. * * @param cs The completions to combine. * @return A completion. */ static AsyncCompletion allOf(Collection cs) { return allOf(cs.stream()); } /** * Returns an {@link AsyncCompletion} that completes when all of the given completions complete. If any completions * complete exceptionally, then the resulting completion also completes exceptionally. * * @param cs The completions to combine. * @return A completion. */ static AsyncCompletion allOf(Stream cs) { @SuppressWarnings("rawtypes") java.util.concurrent.CompletableFuture[] completableFutures = cs.map(completion -> { java.util.concurrent.CompletableFuture javaFuture = new java.util.concurrent.CompletableFuture<>(); completion.whenComplete(ex -> { if (ex == null) { javaFuture.complete(null); } else { javaFuture.completeExceptionally(ex); } }); return javaFuture; }).toArray(java.util.concurrent.CompletableFuture[]::new); return new DefaultCompletableAsyncCompletion(java.util.concurrent.CompletableFuture.allOf(completableFutures)); } /** * Returns a completion that, after the given function executes on a vertx context and returns a completion, completes * when the completion from the function does. * * @param vertx The vertx context. * @param fn The function returning a completion. * @return A completion. */ static AsyncCompletion runOnContext(Vertx vertx, Supplier fn) { requireNonNull(fn); CompletableAsyncCompletion completion = AsyncCompletion.incomplete(); vertx.runOnContext(ev -> { try { fn.get().whenComplete(ex2 -> { if (ex2 == null) { try { completion.complete(); } catch (Throwable ex3) { completion.completeExceptionally(ex3); } } else { completion.completeExceptionally(ex2); } }); } catch (Throwable ex1) { completion.completeExceptionally(ex1); } }); return completion; } /** * Returns a completion that completes after the given action executes on a vertx context. * *

* 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 {@code true} if completed normally, completed exceptionally or cancelled. * * @return {@code true} if completed. */ boolean isDone(); /** * Returns {@code true} if completed exceptionally or cancelled. * * @return {@code 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 {@code true}. Subsequent calls * to {@link #isCancelled()} will always return {@code true} if this method returned {@code true}. * * @return {@code true} if this completion transitioned to a cancelled state. */ boolean cancel(); /** * Returns {@code true} if this task was cancelled before it completed normally. * * @return {@code 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> 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> 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 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 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 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 other, Consumer 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 type of the value returned by the function. * @return A new result. */ AsyncResult thenApply(AsyncResult other, Function fn); /** * Returns a completion that completes when both this completion and the other complete normally. * * @param other The other completion. * @return A completion. */ AsyncCompletion thenCombine(AsyncCompletion other); /** * Returns a new completion that, when this result completes exceptionally, completes after executing the supplied * function. Otherwise, if this result completes normally, then the returned result also completes normally with the * same value. * * @param consumer The function to execute. * @return A new result. */ AsyncCompletion exceptionally(Consumer consumer); /** * Returns a new completion that completes in the same manner as this completion, 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 whenComplete(Consumer 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 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 consumer); } cava-0.6.0/concurrent/src/main/java/net/consensys/cava/concurrent/AsyncResult.java000066400000000000000000000470361341750772100303130ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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 javax.annotation.Nullable; import io.vertx.core.Vertx; import io.vertx.core.WorkerExecutor; /** * A result that will be available at a future time. * * @param The type of the result object. */ public interface AsyncResult { /** * Return an already completed result containing the given value. * * @param value The value. * @param The type of the value. * @return A completed result. */ static AsyncResult completed(@Nullable T value) { CompletableAsyncResult result = new DefaultCompletableAsyncResult<>(); result.complete(value); return result; } /** * Return an already failed result, caused by the given exception. * * @param ex The exception. * @param The type of the value that would be available if this result hadn't completed exceptionally. * @return A failed result. */ static AsyncResult exceptional(Throwable ex) { requireNonNull(ex); CompletableAsyncResult result = new DefaultCompletableAsyncResult<>(); result.completeExceptionally(ex); return result; } /** * Return an incomplete result, that can be later completed or failed. * * @param The type of the value that this result will complete with. * @return An incomplete result. */ static CompletableAsyncResult incomplete() { return new DefaultCompletableAsyncResult<>(); } /** * Returns an {@link AsyncCompletion} that completes when all of the given results complete. If any results complete * exceptionally, then the resulting completion also completes exceptionally. * * @param rs The results to combine. * @return A completion. */ static AsyncCompletion allOf(AsyncResult... rs) { return allOf(Arrays.stream(rs)); } /** * Returns an {@link AsyncCompletion} that completes when all of the given results complete. If any results complete * exceptionally, then the resulting completion also completes exceptionally. * * @param rs The results to combine. * @return A completion. */ static AsyncCompletion allOf(Collection> rs) { return allOf(rs.stream()); } /** * Returns an {@link AsyncCompletion} that completes when all of the given results complete. If any results complete * exceptionally, then the resulting completion also completes exceptionally. * * @param rs The results to combine. * @return A completion. */ static AsyncCompletion allOf(Stream> rs) { @SuppressWarnings("rawtypes") java.util.concurrent.CompletableFuture[] completableFutures = rs.map(result -> { java.util.concurrent.CompletableFuture javaFuture = new java.util.concurrent.CompletableFuture<>(); result.whenComplete((v, ex) -> { if (ex == null) { javaFuture.complete(null); } else { javaFuture.completeExceptionally(ex); } }); return javaFuture; }).toArray(java.util.concurrent.CompletableFuture[]::new); return new DefaultCompletableAsyncCompletion(java.util.concurrent.CompletableFuture.allOf(completableFutures)); } /** * 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 The type of the values that this result will complete with. * @param rs The results to combine. * @return A new result. */ static AsyncResult> combine(Collection> 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 The type of the values that this result will complete with. * @param rs The results to combine. * @return A new result. */ static AsyncResult> combine(Stream> rs) { Stream>> ls = rs.map(r -> r.thenApply(Collections::singletonList)); return ls.reduce(AsyncResult.completed(new ArrayList<>()), (r1, r2) -> r1.thenCombine(r2, (l1, l2) -> { l1.addAll(l2); return l1; })); } /** * Returns a result that, after the given function executes on a vertx context and returns a result, completes when * the returned result completes, with the same value or exception. * *

* 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 The type of the returned result's value. * @return A new result. */ static AsyncResult runOnContext(Vertx vertx, Supplier> fn) { requireNonNull(fn); CompletableAsyncResult asyncResult = AsyncResult.incomplete(); vertx.runOnContext(ev -> { try { fn.get().whenComplete((u, ex2) -> { if (ex2 == null) { try { asyncResult.complete(u); } catch (Throwable ex3) { asyncResult.completeExceptionally(ex3); } } else { asyncResult.completeExceptionally(ex2); } }); } catch (Throwable ex1) { asyncResult.completeExceptionally(ex1); } }); return asyncResult; } /** * Returns a result that, after the given blocking function executes asynchronously on * {@link ForkJoinPool#commonPool()} and returns a result, completes when the returned result completes, with the same * value or exception. * * @param fn The function returning a result. * @param The type of the returned result's value. * @return A new result. */ static AsyncResult executeBlocking(Supplier fn) { requireNonNull(fn); CompletableAsyncResult asyncResult = AsyncResult.incomplete(); ForkJoinPool.commonPool().execute(() -> { try { asyncResult.complete(fn.get()); } catch (Throwable ex) { asyncResult.completeExceptionally(ex); } }); return asyncResult; } /** * Returns a result that, after the given blocking function executes asynchronously on an {@link Executor} and returns * a result, completes when the returned result completes, with the same value or exception. * * @param executor The executor. * @param fn The function returning a result. * @param The type of the returned result's value. * @return A new result. */ static AsyncResult executeBlocking(Executor executor, Supplier fn) { requireNonNull(fn); CompletableAsyncResult asyncResult = AsyncResult.incomplete(); executor.execute(() -> { try { asyncResult.complete(fn.get()); } catch (Throwable ex) { asyncResult.completeExceptionally(ex); } }); return asyncResult; } /** * Returns a result that, after the given blocking function executes asynchronously on a vertx context and returns a * result, completes when the returned result completes, with the same value or exception. * * @param vertx The vertx context. * @param fn The function returning a result. * @param The type of the returned result's value. * @return A new result. */ static AsyncResult executeBlocking(Vertx vertx, Supplier fn) { requireNonNull(fn); CompletableAsyncResult asyncResult = AsyncResult.incomplete(); vertx.executeBlocking(future -> future.complete(fn.get()), false, res -> { if (res.succeeded()) { asyncResult.complete(res.result()); } else { asyncResult.completeExceptionally(res.cause()); } }); return asyncResult; } /** * Returns a result that, after the given blocking function executes asynchronously on a vertx executor and returns a * result, completes when the returned result completes, with the same value or exception. * * @param executor A vertx executor. * @param fn The function returning a result. * @param The type of the returned result's value. * @return A new result. */ static AsyncResult executeBlocking(WorkerExecutor executor, Supplier fn) { requireNonNull(fn); CompletableAsyncResult asyncResult = AsyncResult.incomplete(); executor.executeBlocking(future -> future.complete(fn.get()), false, res -> { if (res.succeeded()) { asyncResult.complete(res.result()); } else { asyncResult.completeExceptionally(res.cause()); } }); return asyncResult; } /** * Returns {@code true} if completed normally, completed exceptionally or cancelled. * * @return {@code true} if completed. */ boolean isDone(); /** * Returns {@code true} if completed exceptionally or cancelled. * * @return {@code 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 {@code true}. Subsequent calls * to {@link #isCancelled()} will always return {@code true} if this method returned {@code true}. * * @return {@code true} if this result transitioned to a cancelled state. */ boolean cancel(); /** * Returns {@code true} if this task was cancelled before it completed normally. * * @return {@code 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. */ @Nullable 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. */ @Nullable 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> 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> 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 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 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 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 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 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 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 other, BiConsumer 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 The type of the value returned by the function. * @return A new result. */ AsyncResult thenCombine(AsyncResult other, BiFunction fn); /** * Returns a new result that, when this result completes exceptionally, completes with the value obtained from * executing the supplied function with this result's exception as an argument. Otherwise, if this result completes * normally, then the returned result also completes normally with the same value. * * @param fn The function to execute. * @return A new result. */ AsyncResult exceptionally(Function fn); /** * Returns a new result that completes with the same value or exception as this result, after executing the given * action with this result's value or exception. *

* 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 whenComplete(BiConsumer action); /** * 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 value and exception as arguments. *

* 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 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 consumer); } cava-0.6.0/concurrent/src/main/java/net/consensys/cava/concurrent/AtomicSlotMap.java000066400000000000000000000161411341750772100305440ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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 javax.annotation.Nullable; 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 The type of the map keys. * @param The type of values to store in the map. */ @SuppressWarnings("rawtypes") // allow ungenerified Comparable types public final class AtomicSlotMap { private final DiscreteDomain domain; private final ConcurrentHashMap> slots = new ConcurrentHashMap<>(); private final AtomicInteger size = new AtomicInteger(0); /** * Create a slot map over the range of integers > 0. * * @param The type of values to store in the map. * @return A new slot map. */ public static AtomicSlotMap positiveIntegerSlots() { return new AtomicSlotMap<>(PositiveIntegerDomain.INSTANCE); } /** * Create a slot map over the provided domain. * * @param domain The {@link DiscreteDomain} that defines the slots to be used. */ public AtomicSlotMap(DiscreteDomain domain) { requireNonNull(domain); this.domain = domain; } /** * Add a value to the slot map, using the first available slot. * * @param value The value. * @return The slot that was used to store the value. */ public K add(V value) { requireNonNull(value); K slot = domain.minValue(); Optional storedValue = Optional.of(value); while (slots.containsKey(slot) || slots.putIfAbsent(slot, storedValue) != null) { slot = domain.next(slot); } size.incrementAndGet(); return slot; } /** * Put a value into a specific slot. * * @param slot The slot to put the value in. * @param value The value. * @return The previous value in the slot, if present. */ @Nullable public V put(K slot, V value) { requireNonNull(slot); requireNonNull(value); Optional previous = slots.put(slot, Optional.of(value)); if (previous == null || !previous.isPresent()) { size.incrementAndGet(); return null; } return previous.get(); } /** * Find a slot and compute a value for it. * * @param fn A function to compute the value for a slot. * @return The slot for which the value was computed. */ public K compute(Function fn) { requireNonNull(fn); K slot = domain.minValue(); // store an empty optional to prevent contention on the slot, then replace with computed value. Optional placeholder = Optional.empty(); while (slots.containsKey(slot) || slots.putIfAbsent(slot, placeholder) != null) { slot = domain.next(slot); } try { if (slots.replace(slot, placeholder, Optional.of(fn.apply(slot)))) { size.incrementAndGet(); } return slot; } catch (Throwable ex) { slots.remove(slot, placeholder); throw ex; } } /** * Find a slot and compute a value for it. * * @param fn A function to compute the value for a slot. * @return A result that will complete with the slot for which the value was computed. */ public AsyncResult computeAsync(Function> fn) { requireNonNull(fn); K slot = domain.minValue(); // store an empty optional to prevent contention on the slot, then replace with computed value. Optional placeholder = Optional.empty(); while (slots.containsKey(slot) || slots.putIfAbsent(slot, placeholder) != null) { slot = domain.next(slot); } K finalSlot = slot; try { return fn.apply(finalSlot).thenApply(value -> { if (slots.replace(finalSlot, placeholder, Optional.of(value))) { size.incrementAndGet(); } return finalSlot; }); } catch (Throwable ex) { slots.remove(finalSlot, placeholder); throw ex; } } /** * Get the value in a slot. * * @param slot The slot. * @return The value, if present. */ @Nullable public V get(K slot) { requireNonNull(slot); Optional value = slots.get(slot); if (value == null) { return null; } return value.orElse(null); } /** * Remove a value from a slot, making the slot available again. * * @param slot The slot. * @return The value that was in the slot, if any. */ @Nullable public V remove(K slot) { requireNonNull(slot); Optional previous = slots.remove(slot); if (previous == null || !previous.isPresent()) { return null; } size.decrementAndGet(); return previous.get(); } /** * @return The number of slots filled. */ public int size() { return size.get(); } /** * @return A stream over the entries in the slot map. */ public Stream> entries() { return slots.entrySet().stream().filter(e -> e.getValue().isPresent()).map(e -> new Map.Entry() { @Override public K getKey() { return e.getKey(); } @Override public V getValue() { return e.getValue().get(); } @Override public V setValue(Object value) { throw new UnsupportedOperationException(); } }); } /** * @return A stream over the values stored in the slot map. */ public Stream values() { return slots.values().stream().filter(Optional::isPresent).map(Optional::get); } private static final class PositiveIntegerDomain extends DiscreteDomain { private static final PositiveIntegerDomain INSTANCE = new PositiveIntegerDomain(); @Override public Integer next(Integer value) { int i = value; return (i == Integer.MAX_VALUE) ? null : i + 1; } @Override public Integer previous(Integer value) { int i = value; return (i == 1) ? null : i - 1; } @Override public long distance(Integer start, Integer end) { return (long) end - start; } @Override public Integer minValue() { return 1; } @Override public Integer maxValue() { return Integer.MAX_VALUE; } } } cava-0.6.0/concurrent/src/main/java/net/consensys/cava/concurrent/CompletableAsyncCompletion.java000066400000000000000000000024621341750772100333100ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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; /** * An {@link AsyncCompletion} that can later be completed successfully or with a provided exception. */ public interface CompletableAsyncCompletion extends AsyncCompletion { /** * Complete this completion. * * @return {@code true} if this invocation caused this completion to transition to a completed state, else * {@code false}. */ boolean complete(); /** * Complete this completion with the given exception. * * @param ex The exception to complete this result with. * @return {@code true} if this invocation caused this completion to transition to a completed state, else * {@code false}. */ boolean completeExceptionally(Throwable ex); } cava-0.6.0/concurrent/src/main/java/net/consensys/cava/concurrent/CompletableAsyncResult.java000066400000000000000000000027411341750772100324550ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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 javax.annotation.Nullable; /** * An {@link AsyncResult} that can be later completed successfully with a provided value, or completed with an * exception. * * @param The type of the value returned by this result. */ public interface CompletableAsyncResult extends AsyncResult { /** * Complete this result with the given value. * * @param value The value to complete this result with. * @return {@code true} if this invocation caused this result to transition to a completed state, else {@code false}. */ boolean complete(@Nullable T value); /** * Complete this result with the given exception. * * @param ex The exception to complete this result with. * @return {@code true} if this invocation caused this result to transition to a completed state, else {@code false}. */ boolean completeExceptionally(Throwable ex); } DefaultCompletableAsyncCompletion.java000066400000000000000000000253751341750772100345460ustar00rootroot00000000000000cava-0.6.0/concurrent/src/main/java/net/consensys/cava/concurrent/* * Copyright 2018 ConsenSys AG. * * 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.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import io.vertx.core.Vertx; import io.vertx.core.WorkerExecutor; final class DefaultCompletableAsyncCompletion implements CompletableAsyncCompletion { private final CompletableFuture future; DefaultCompletableAsyncCompletion() { this(new CompletableFuture<>()); } DefaultCompletableAsyncCompletion(CompletableFuture future) { this.future = future; } @Override public boolean complete() { return future.complete(null); } @Override public boolean completeExceptionally(Throwable ex) { return future.completeExceptionally(ex); } @Override public boolean cancel() { return future.cancel(false); } @Override public boolean isDone() { return future.isDone(); } @Override public boolean isCompletedExceptionally() { return future.isCompletedExceptionally(); } @Override public boolean isCancelled() { return future.isCancelled(); } @Override public void join() throws CompletionException, InterruptedException { try { join(10, TimeUnit.SECONDS); } catch (TimeoutException ex) { throw new RuntimeException("Default timeout triggered for blocking call to AsyncCompletion::join()", ex); } } @Override public void join(long timeout, TimeUnit unit) throws CompletionException, TimeoutException, InterruptedException { requireNonNull(unit); try { future.get(timeout, unit); } catch (ExecutionException ex) { throw new CompletionException(ex.getMessage(), ex.getCause()); } } @Override public AsyncResult then(Supplier> fn) { requireNonNull(fn); CompletableAsyncResult asyncResult = AsyncResult.incomplete(); future.whenComplete((v, ex1) -> { if (ex1 == null) { try { fn.get().whenComplete((u, ex3) -> { if (ex3 == null) { asyncResult.complete(u); } else { asyncResult.completeExceptionally(ex3); } }); } catch (Throwable ex2) { asyncResult.completeExceptionally(ex2); } } else { asyncResult.completeExceptionally(ex1); } }); return asyncResult; } @Override public AsyncResult thenSchedule(Vertx vertx, Supplier> fn) { requireNonNull(vertx); requireNonNull(fn); CompletableAsyncResult asyncResult = AsyncResult.incomplete(); future.whenComplete((v, ex1) -> { if (ex1 == null) { try { vertx.runOnContext(ev -> { try { fn.get().whenComplete((u, ex4) -> { if (ex4 == null) { asyncResult.complete(u); } else { asyncResult.completeExceptionally(ex4); } }); } catch (Throwable ex3) { asyncResult.completeExceptionally(ex3); } }); } catch (Throwable ex2) { asyncResult.completeExceptionally(ex2); } } else { asyncResult.completeExceptionally(ex1); } }); return asyncResult; } @Override public AsyncCompletion thenCompose(Supplier fn) { requireNonNull(fn); CompletableAsyncCompletion completion = AsyncCompletion.incomplete(); future.whenComplete((v, ex1) -> { if (ex1 == null) { try { fn.get().whenComplete(ex3 -> { if (ex3 == null) { completion.complete(); } else { completion.completeExceptionally(ex3); } }); } catch (Throwable ex2) { completion.completeExceptionally(ex2); } } else { completion.completeExceptionally(ex1); } }); return completion; } @Override public AsyncCompletion thenRun(Runnable action) { requireNonNull(action); return new DefaultCompletableAsyncCompletion(future.thenRun(action)); } @Override public AsyncCompletion thenScheduleRun(Vertx vertx, Runnable runnable) { requireNonNull(vertx); requireNonNull(runnable); CompletableAsyncCompletion completion = AsyncCompletion.incomplete(); future.whenComplete((t, ex1) -> { if (ex1 == null) { try { vertx.runOnContext(ev -> { try { runnable.run(); } catch (Throwable ex3) { completion.completeExceptionally(ex3); return; } completion.complete(); }); } catch (Throwable ex2) { completion.completeExceptionally(ex2); } } else { completion.completeExceptionally(ex1); } }); return completion; } @Override public AsyncCompletion thenScheduleBlockingRun(Vertx vertx, Runnable runnable) { requireNonNull(vertx); requireNonNull(runnable); CompletableAsyncCompletion completion = AsyncCompletion.incomplete(); future.whenComplete((t, ex1) -> { if (ex1 == null) { try { vertx.executeBlocking(vertxFuture -> { runnable.run(); vertxFuture.complete(null); }, false, res -> { if (res.succeeded()) { completion.complete(); } else { completion.completeExceptionally(res.cause()); } }); } catch (Throwable ex2) { completion.completeExceptionally(ex2); } } else { completion.completeExceptionally(ex1); } }); return completion; } @Override public AsyncCompletion thenScheduleBlockingRun(WorkerExecutor executor, Runnable runnable) { requireNonNull(executor); requireNonNull(runnable); CompletableAsyncCompletion completion = AsyncCompletion.incomplete(); future.whenComplete((t, ex1) -> { if (ex1 == null) { try { executor.executeBlocking(vertxFuture -> { runnable.run(); vertxFuture.complete(null); }, false, res -> { if (res.succeeded()) { completion.complete(); } else { completion.completeExceptionally(res.cause()); } }); } catch (Throwable ex2) { completion.completeExceptionally(ex2); } } else { completion.completeExceptionally(ex1); } }); return completion; } @Override public AsyncResult thenSupply(Supplier supplier) { requireNonNull(supplier); return new DefaultCompletableAsyncResult<>(future.thenApply(v -> supplier.get())); } @Override public AsyncResult thenSupply(Vertx vertx, Supplier supplier) { requireNonNull(vertx); requireNonNull(supplier); CompletableAsyncResult completion = AsyncResult.incomplete(); future.whenComplete((t, ex1) -> { if (ex1 == null) { try { vertx.runOnContext(ev -> { try { completion.complete(supplier.get()); } catch (Throwable ex3) { completion.completeExceptionally(ex3); } }); } catch (Throwable ex2) { completion.completeExceptionally(ex2); } } else { completion.completeExceptionally(ex1); } }); return completion; } @Override public AsyncCompletion thenConsume(AsyncResult other, Consumer consumer) { requireNonNull(other); requireNonNull(consumer); return new DefaultCompletableAsyncCompletion(future.thenAccept(v -> other.thenAccept(consumer::accept))); } @Override public AsyncResult thenApply(AsyncResult other, Function fn) { requireNonNull(other); requireNonNull(fn); CompletableAsyncResult asyncResult = AsyncResult.incomplete(); future.whenComplete((v, ex1) -> { if (ex1 == null) { try { other.whenComplete((u, ex3) -> { if (ex3 == null) { asyncResult.complete(fn.apply(u)); } else { asyncResult.completeExceptionally(ex3); } }); } catch (Throwable ex2) { asyncResult.completeExceptionally(ex2); } } else { asyncResult.completeExceptionally(ex1); } }); return asyncResult; } @Override public AsyncCompletion thenCombine(AsyncCompletion other) { requireNonNull(other); CompletableAsyncCompletion completion = AsyncCompletion.incomplete(); future.whenComplete((v, ex1) -> { if (ex1 == null) { try { other.whenComplete(ex3 -> { if (ex3 == null) { completion.complete(); } else { completion.completeExceptionally(ex3); } }); } catch (Throwable ex2) { completion.completeExceptionally(ex2); } } else { completion.completeExceptionally(ex1); } }); return completion; } @Override public AsyncCompletion exceptionally(Consumer consumer) { requireNonNull(consumer); return new DefaultCompletableAsyncCompletion(future.exceptionally(ex -> { consumer.accept(ex); return null; })); } @Override public AsyncCompletion whenComplete(Consumer consumer) { requireNonNull(consumer); return new DefaultCompletableAsyncCompletion(future.whenComplete((v, ex) -> consumer.accept(ex))); } @Override public AsyncResult handle(Function fn) { requireNonNull(fn); return new DefaultCompletableAsyncResult<>(future.handle((v, ex) -> fn.apply(ex))); } @Override public AsyncCompletion accept(Consumer consumer) { requireNonNull(consumer); return new DefaultCompletableAsyncCompletion(future.handle((v, ex) -> { consumer.accept(ex); return null; })); } } cava-0.6.0/concurrent/src/main/java/net/consensys/cava/concurrent/DefaultCompletableAsyncResult.java000066400000000000000000000300221341750772100337530ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import javax.annotation.Nullable; import io.vertx.core.Vertx; import io.vertx.core.WorkerExecutor; final class DefaultCompletableAsyncResult implements CompletableAsyncResult { private final CompletableFuture future; DefaultCompletableAsyncResult() { this(new CompletableFuture<>()); } DefaultCompletableAsyncResult(CompletableFuture future) { this.future = future; } @Override public boolean complete(@Nullable T value) { return future.complete(value); } @Override public boolean completeExceptionally(Throwable ex) { return future.completeExceptionally(ex); } @Override public boolean cancel() { return future.cancel(false); } @Override public boolean isDone() { return future.isDone(); } @Override public boolean isCompletedExceptionally() { return future.isCompletedExceptionally(); } @Override public boolean isCancelled() { return future.isCancelled(); } @Override @Nullable public T get() throws CompletionException, InterruptedException { try { return get(10, TimeUnit.SECONDS); } catch (TimeoutException ex) { throw new RuntimeException("Default timeout triggered for blocking call to AsyncResult::get()", ex); } } @Override @Nullable public T get(long timeout, TimeUnit unit) throws CompletionException, TimeoutException, InterruptedException { requireNonNull(unit); try { return future.get(timeout, unit); } catch (ExecutionException ex) { throw new CompletionException(ex.getMessage(), ex.getCause()); } } @Override public AsyncResult then(Function> fn) { requireNonNull(fn); CompletableAsyncResult asyncResult = AsyncResult.incomplete(); future.whenComplete((t, ex1) -> { if (ex1 == null) { try { fn.apply(t).whenComplete((u, ex3) -> { if (ex3 == null) { asyncResult.complete(u); } else { asyncResult.completeExceptionally(ex3); } }); } catch (Throwable ex2) { asyncResult.completeExceptionally(ex2); } } else { asyncResult.completeExceptionally(ex1); } }); return asyncResult; } @Override public AsyncResult thenSchedule(Vertx vertx, Function> fn) { requireNonNull(vertx); requireNonNull(fn); CompletableAsyncResult asyncResult = AsyncResult.incomplete(); future.whenComplete((t, ex1) -> { if (ex1 == null) { try { vertx.runOnContext(ev -> { try { fn.apply(t).whenComplete((u, ex4) -> { if (ex4 == null) { asyncResult.complete(u); } else { asyncResult.completeExceptionally(ex4); } }); } catch (Throwable ex3) { asyncResult.completeExceptionally(ex3); } }); } catch (Throwable ex2) { asyncResult.completeExceptionally(ex2); } } else { asyncResult.completeExceptionally(ex1); } }); return asyncResult; } @Override public AsyncCompletion thenCompose(Function fn) { requireNonNull(fn); CompletableAsyncCompletion completion = AsyncCompletion.incomplete(); future.whenComplete((t, ex1) -> { if (ex1 == null) { try { fn.apply(t).whenComplete(ex3 -> { if (ex3 == null) { completion.complete(); } else { completion.completeExceptionally(ex3); } }); } catch (Throwable ex2) { completion.completeExceptionally(ex2); } } else { completion.completeExceptionally(ex1); } }); return completion; } @Override public AsyncCompletion thenRun(Runnable action) { requireNonNull(action); return new DefaultCompletableAsyncCompletion(future.thenRun(action)); } @Override public AsyncCompletion thenScheduleRun(Vertx vertx, Runnable runnable) { requireNonNull(vertx); requireNonNull(runnable); CompletableAsyncCompletion completion = AsyncCompletion.incomplete(); future.whenComplete((t, ex1) -> { if (ex1 == null) { try { vertx.runOnContext(ev -> { try { runnable.run(); } catch (Throwable ex3) { completion.completeExceptionally(ex3); return; } completion.complete(); }); } catch (Throwable ex2) { completion.completeExceptionally(ex2); } } else { completion.completeExceptionally(ex1); } }); return completion; } @Override public AsyncCompletion thenScheduleBlockingRun(Vertx vertx, Runnable runnable) { requireNonNull(vertx); requireNonNull(runnable); CompletableAsyncCompletion completion = AsyncCompletion.incomplete(); future.whenComplete((t, ex1) -> { if (ex1 == null) { try { vertx.executeBlocking(vertxFuture -> { runnable.run(); vertxFuture.complete(null); }, false, res -> { if (res.succeeded()) { completion.complete(); } else { completion.completeExceptionally(res.cause()); } }); } catch (Throwable ex2) { completion.completeExceptionally(ex2); } } else { completion.completeExceptionally(ex1); } }); return completion; } @Override public AsyncCompletion thenScheduleBlockingRun(WorkerExecutor executor, Runnable runnable) { requireNonNull(executor); requireNonNull(runnable); CompletableAsyncCompletion completion = AsyncCompletion.incomplete(); future.whenComplete((t, ex1) -> { if (ex1 == null) { try { executor.executeBlocking(vertxFuture -> { runnable.run(); vertxFuture.complete(null); }, false, res -> { if (res.succeeded()) { completion.complete(); } else { completion.completeExceptionally(res.cause()); } }); } catch (Throwable ex2) { completion.completeExceptionally(ex2); } } else { completion.completeExceptionally(ex1); } }); return completion; } @Override public AsyncResult thenApply(Function fn) { requireNonNull(fn); return new DefaultCompletableAsyncResult<>(future.thenApply(fn)); } @Override public AsyncResult thenScheduleApply(Vertx vertx, Function fn) { requireNonNull(vertx); requireNonNull(fn); CompletableAsyncResult asyncResult = AsyncResult.incomplete(); future.whenComplete((t, ex1) -> { if (ex1 == null) { try { vertx.runOnContext(ev -> { try { asyncResult.complete(fn.apply(t)); } catch (Throwable ex3) { asyncResult.completeExceptionally(ex3); } }); } catch (Throwable ex2) { asyncResult.completeExceptionally(ex2); } } else { asyncResult.completeExceptionally(ex1); } }); return asyncResult; } @Override public AsyncResult thenScheduleBlockingApply(Vertx vertx, Function fn) { requireNonNull(vertx); requireNonNull(fn); CompletableAsyncResult asyncResult = AsyncResult.incomplete(); future.whenComplete((t, ex1) -> { if (ex1 == null) { try { vertx.executeBlocking(vertxFuture -> vertxFuture.complete(fn.apply(t)), false, res -> { if (res.succeeded()) { asyncResult.complete(res.result()); } else { asyncResult.completeExceptionally(res.cause()); } }); } catch (Throwable ex2) { asyncResult.completeExceptionally(ex2); } } else { asyncResult.completeExceptionally(ex1); } }); return asyncResult; } @Override public AsyncResult thenScheduleBlockingApply(WorkerExecutor executor, Function fn) { requireNonNull(executor); requireNonNull(fn); CompletableAsyncResult asyncResult = AsyncResult.incomplete(); future.whenComplete((t, ex1) -> { if (ex1 == null) { try { executor.executeBlocking(vertxFuture -> vertxFuture.complete(fn.apply(t)), false, res -> { if (res.succeeded()) { asyncResult.complete(res.result()); } else { asyncResult.completeExceptionally(res.cause()); } }); } catch (Throwable ex2) { asyncResult.completeExceptionally(ex2); } } else { asyncResult.completeExceptionally(ex1); } }); return asyncResult; } @Override public AsyncCompletion thenAccept(Consumer consumer) { requireNonNull(consumer); return new DefaultCompletableAsyncCompletion(future.thenAccept(consumer)); } @Override public AsyncCompletion thenAcceptBoth(AsyncResult other, BiConsumer consumer) { requireNonNull(other); requireNonNull(consumer); return new DefaultCompletableAsyncCompletion(future.thenAccept(t -> other.thenAccept(u -> consumer.accept(t, u)))); } @Override public AsyncResult thenCombine( AsyncResult other, BiFunction fn) { requireNonNull(other); requireNonNull(fn); CompletableAsyncResult asyncResult = AsyncResult.incomplete(); future.whenComplete((t, ex1) -> { if (ex1 == null) { try { other.whenComplete((u, ex3) -> { if (ex3 == null) { asyncResult.complete(fn.apply(t, u)); } else { asyncResult.completeExceptionally(ex3); } }); } catch (Throwable ex2) { asyncResult.completeExceptionally(ex2); } } else { asyncResult.completeExceptionally(ex1); } }); return asyncResult; } @Override public AsyncResult exceptionally(Function fn) { requireNonNull(fn); return new DefaultCompletableAsyncResult<>(future.exceptionally(fn::apply)); } @Override public AsyncResult whenComplete(BiConsumer action) { requireNonNull(action); return new DefaultCompletableAsyncResult<>(future.whenComplete(action::accept)); } @Override public AsyncResult handle(BiFunction fn) { requireNonNull(fn); return new DefaultCompletableAsyncResult<>(future.handle(fn::apply)); } @Override public AsyncCompletion accept(BiConsumer consumer) { requireNonNull(consumer); return new DefaultCompletableAsyncCompletion(future.handle((t, ex) -> { consumer.accept(t, ex); return null; })); } } cava-0.6.0/concurrent/src/main/java/net/consensys/cava/concurrent/ExpiringMap.java000066400000000000000000000351441341750772100302570ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ConcurrentHashMap; import java.util.concurrent.PriorityBlockingQueue; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.LongSupplier; import java.util.stream.Collectors; import javax.annotation.Nullable; import com.google.common.annotations.VisibleForTesting; /** * A concurrent hash map that stores values along with an expiry. * * Values are stored in the map until their expiry is reached, after which they will no longer be available and will * appear as if removed. The actual removal is done lazily whenever the map is accessed, or when the * {@link #purgeExpired()} method is invoked. * * @param The key type. * @param The value type. */ public final class ExpiringMap implements Map { // Uses object equality, to ensure uniqueness as a value in the storage map private static final class ExpiringEntry implements Comparable> { private K key; private V value; private long expiry; @Nullable private BiConsumer expiryListener; ExpiringEntry(K key, V value, long expiry, @Nullable BiConsumer expiryListener) { this.key = key; this.value = value; this.expiry = expiry; this.expiryListener = expiryListener; } @Override public int compareTo(ExpiringEntry o) { return Long.compare(expiry, o.expiry); } } private final ConcurrentHashMap> storage = new ConcurrentHashMap<>(); private final PriorityBlockingQueue> expiryQueue = new PriorityBlockingQueue<>(); private final LongSupplier currentTimeSupplier; /** * Construct an empty map. */ public ExpiringMap() { this(System::currentTimeMillis); } @VisibleForTesting ExpiringMap(LongSupplier currentTimeSupplier) { this.currentTimeSupplier = currentTimeSupplier; } @Nullable @Override public V get(Object key) { requireNonNull(key); purgeExpired(); ExpiringEntry entry = storage.get(key); return (entry == null) ? null : entry.value; } @Override public V getOrDefault(Object key, V defaultValue) { V v; return (((v = get(key)) != null)) ? v : defaultValue; } @Override public boolean containsKey(Object key) { requireNonNull(key); purgeExpired(); return storage.containsKey(key); } @Override public boolean containsValue(Object value) { requireNonNull(value); purgeExpired(); return storage.values().stream().anyMatch(e -> e.value.equals(value)); } @Override public int size() { purgeExpired(); return storage.size(); } @Override public boolean isEmpty() { purgeExpired(); return storage.isEmpty(); } @Nullable @Override public V put(K key, V value) { requireNonNull(key); requireNonNull(value); purgeExpired(); ExpiringEntry oldEntry = storage.put(key, new ExpiringEntry<>(key, value, Long.MAX_VALUE, null)); return (oldEntry == null) ? null : oldEntry.value; } /** * Associates the specified value with the specified key in this map, and expires the entry when the specified expiry * time is reached. If the map previously contained a mapping for the key, the old value is replaced by the specified * value. * * @param key The key with which the specified value is to be associated. * @param value The value to be associated with the specified key. * @param expiry The expiry time for the value, in milliseconds since the epoch. * @return The previous value associated with {@code key}, or {@code null} if there was no mapping for {@code key}. */ @Nullable public V put(K key, V value, long expiry) { return put(key, value, expiry, null); } /** * Associates the specified value with the specified key in this map, and expires the entry when the specified expiry * time is reached. If the map previously contained a mapping for the key, the old value is replaced by the specified * value. * * @param key The key with which the specified value is to be associated. * @param value The value to be associated with the specified key. * @param expiry The expiry time for the value, in milliseconds since the epoch. * @param expiryListener A listener that will be invoked when the entry expires. * @return The previous value associated with {@code key}, or {@code null} if there was no mapping for {@code key}. */ @Nullable public V put(K key, V value, long expiry, @Nullable BiConsumer expiryListener) { requireNonNull(key); requireNonNull(value); if (expiry >= Long.MAX_VALUE) { return put(key, value); } long now = currentTimeSupplier.getAsLong(); purgeExpired(now); if (expiry <= now) { V previous = remove(key); if (expiryListener != null) { expiryListener.accept(key, value); } return previous; } ExpiringEntry newEntry = new ExpiringEntry<>(key, value, expiry, expiryListener); ExpiringEntry oldEntry = storage.put(key, newEntry); expiryQueue.offer(newEntry); if (oldEntry != null && oldEntry.expiry < Long.MAX_VALUE) { expiryQueue.remove(oldEntry); } return (oldEntry == null) ? null : oldEntry.value; } @Override public void putAll(Map m) { requireNonNull(m); purgeExpired(); for (Map.Entry e : m.entrySet()) { storage.put(e.getKey(), new ExpiringEntry<>(e.getKey(), e.getValue(), Long.MAX_VALUE, null)); } } @Nullable @Override public V putIfAbsent(K key, V value) { requireNonNull(key); requireNonNull(value); purgeExpired(); ExpiringEntry oldEntry = storage.putIfAbsent(key, new ExpiringEntry<>(key, value, Long.MAX_VALUE, null)); return (oldEntry == null) ? null : oldEntry.value; } /** * If the specified key is not already associated with a value, associates the specified value with the specified key * in this map, and expires the entry when the specified expiry time is reached. * * @param key The key with which the specified value is to be associated. * @param value The value to be associated with the specified key. * @param expiry The expiry time for the value, in milliseconds since the epoch. * @return The previous value associated with {@code key}, or {@code null} if there was no mapping for {@code key}. */ @Nullable public V putIfAbsent(K key, V value, long expiry) { return putIfAbsent(key, value, expiry, null); } /** * If the specified key is not already associated with a value, associates the specified value with the specified key * in this map, and expires the entry when the specified expiry time is reached. * * @param key The key with which the specified value is to be associated. * @param value The value to be associated with the specified key. * @param expiry The expiry time for the value, in milliseconds since the epoch. * @param expiryListener A listener that will be invoked when the entry expires. * @return The previous value associated with {@code key}, or {@code null} if there was no mapping for {@code key}. */ @Nullable public V putIfAbsent(K key, V value, long expiry, @Nullable BiConsumer expiryListener) { requireNonNull(key); requireNonNull(value); if (expiry >= Long.MAX_VALUE) { return put(key, value); } long now = currentTimeSupplier.getAsLong(); purgeExpired(now); if (expiry <= now) { V previous = remove(key); if (expiryListener != null) { expiryListener.accept(key, value); } return previous; } ExpiringEntry newEntry = new ExpiringEntry<>(key, value, expiry, expiryListener); ExpiringEntry oldEntry = storage.putIfAbsent(key, newEntry); if (oldEntry == null) { expiryQueue.offer(newEntry); return null; } return oldEntry.value; } @Override public V compute(K key, BiFunction remappingFunction) { ExpiringEntry newEntry = storage.compute(key, (k, oldEntry) -> { if (oldEntry != null && oldEntry.expiry < Long.MAX_VALUE) { expiryQueue.remove(oldEntry); } V oldValue = (oldEntry == null) ? null : oldEntry.value; V newValue = remappingFunction.apply(k, oldValue); return (newValue == null) ? null : new ExpiringEntry<>(k, newValue, Long.MAX_VALUE, null); }); return (newEntry == null) ? null : newEntry.value; } @Override public V computeIfAbsent(K key, Function mappingFunction) { ExpiringEntry newEntry = storage.computeIfAbsent(key, k -> { V newValue = mappingFunction.apply(k); return (newValue == null) ? null : new ExpiringEntry<>(k, newValue, Long.MAX_VALUE, null); }); return (newEntry == null) ? null : newEntry.value; } @Override public V computeIfPresent(K key, BiFunction remappingFunction) { ExpiringEntry newEntry = storage.computeIfPresent(key, (k, oldEntry) -> { if (oldEntry.expiry < Long.MAX_VALUE) { expiryQueue.remove(oldEntry); } V newValue = remappingFunction.apply(k, oldEntry.value); return (newValue == null) ? null : new ExpiringEntry<>(k, newValue, Long.MAX_VALUE, null); }); return (newEntry == null) ? null : newEntry.value; } @Override public V merge(K key, V value, BiFunction remappingFunction) { ExpiringEntry entry = storage.merge(key, new ExpiringEntry<>(key, value, Long.MAX_VALUE, null), (oldEntry, newEntry) -> { if (oldEntry.expiry < Long.MAX_VALUE) { expiryQueue.remove(oldEntry); } V newValue = remappingFunction.apply(oldEntry.value, newEntry.value); return (newValue == null) ? null : new ExpiringEntry<>(key, newValue, Long.MAX_VALUE, null); }); return (entry == null) ? null : entry.value; } @Override public V replace(K key, V value) { ExpiringEntry oldEntry = storage.replace(key, new ExpiringEntry<>(key, value, Long.MAX_VALUE, null)); if (oldEntry != null) { if (oldEntry.expiry < Long.MAX_VALUE) { expiryQueue.remove(oldEntry); } return oldEntry.value; } return null; } @Override public boolean replace(K key, V oldValue, V newValue) { requireNonNull(oldValue); requireNonNull(newValue); ExpiringEntry entry = storage.computeIfPresent(key, (k, oldEntry) -> { if (oldEntry.value.equals(oldValue)) { if (oldEntry.expiry < Long.MAX_VALUE) { expiryQueue.remove(oldEntry); } return new ExpiringEntry<>(k, newValue, Long.MAX_VALUE, null); } return oldEntry; }); return (entry != null) && entry.value.equals(newValue); } @Override public void replaceAll(BiFunction function) { storage.replaceAll((k, oldEntry) -> { if (oldEntry.expiry < Long.MAX_VALUE) { expiryQueue.remove(oldEntry); } return new ExpiringEntry<>(k, requireNonNull(function.apply(k, oldEntry.value)), Long.MAX_VALUE, null); }); } @Override public V remove(Object key) { requireNonNull(key); purgeExpired(); ExpiringEntry entry = storage.remove(key); if (entry == null) { return null; } if (entry.expiry < Long.MAX_VALUE) { expiryQueue.remove(entry); } return entry.value; } @Override public boolean remove(Object key, Object value) { requireNonNull(key); requireNonNull(value); purgeExpired(); ExpiringEntry entry = storage.get(key); if (entry == null || !value.equals(entry.value)) { return false; } if (!storage.remove(key, entry)) { return false; } if (entry.expiry < Long.MAX_VALUE) { expiryQueue.remove(entry); } return true; } @Override public void clear() { expiryQueue.clear(); storage.clear(); } @Override public Set keySet() { purgeExpired(); return storage.keySet(); } @Override public Collection values() { purgeExpired(); return storage.values().stream().map(e -> e.value).collect(Collectors.toList()); } @Override public Set> entrySet() { purgeExpired(); return storage.entrySet().stream().map(e -> new Map.Entry() { @Override public K getKey() { return e.getKey(); } @Override public V getValue() { return e.getValue().value; } @Override public V setValue(V value) { throw new UnsupportedOperationException(); } }).collect(Collectors.toSet()); } @Override public void forEach(BiConsumer action) { storage.forEach((k, v) -> action.accept(k, v.value)); } /** * Force immediate expiration of any key/value pairs that have reached their expiry. * * @return The earliest expiry time for the current entries in the map (in milliseconds since the epoch), or * {@code Long.MAX_VALUE} if there are no entries due to expire. */ public long purgeExpired() { return purgeExpired(currentTimeSupplier.getAsLong()); } private long purgeExpired(long oldest) { ExpiringEntry entry; while ((entry = expiryQueue.peek()) != null && entry.expiry <= oldest) { if (!expiryQueue.remove(entry) || !storage.remove(entry.key, entry)) { continue; } if (entry.expiryListener != null) { entry.expiryListener.accept(entry.key, entry.value); } } return entry == null ? Long.MAX_VALUE : entry.expiry; } @SuppressWarnings("rawtypes") @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (!(obj instanceof ExpiringMap)) { return false; } ExpiringMap other = (ExpiringMap) obj; return storage.equals(other.storage); } @Override public int hashCode() { return storage.hashCode(); } } cava-0.6.0/concurrent/src/main/java/net/consensys/cava/concurrent/ExpiringSet.java000066400000000000000000000173021341750772100302710ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.Collection; import java.util.Iterator; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.PriorityBlockingQueue; import java.util.function.Consumer; import java.util.function.LongSupplier; import javax.annotation.Nullable; import com.google.common.annotations.VisibleForTesting; /** * A concurrent hash set that stores values along with an expiry. * * Elements are stored in the set until their expiry is reached, after which they will no longer be available and will * appear as if removed. The actual removal is done lazily whenever the set is accessed, or when the * {@link #purgeExpired()} method is invoked. * * @param The element type. */ public final class ExpiringSet implements Set { // Uses object equality, to ensure uniqueness as a value in the storage map private static final class ExpiringEntry implements Comparable> { private E element; private long expiry; @Nullable private Consumer expiryListener; ExpiringEntry(E element, long expiry, @Nullable Consumer expiryListener) { this.element = element; this.expiry = expiry; this.expiryListener = expiryListener; } @Override public int compareTo(ExpiringEntry o) { return Long.compare(expiry, o.expiry); } } private final ConcurrentHashMap> storage = new ConcurrentHashMap<>(); private final PriorityBlockingQueue> expiryQueue = new PriorityBlockingQueue<>(); private final LongSupplier currentTimeSupplier; /** * Construct an empty map. */ public ExpiringSet() { this(System::currentTimeMillis); } @VisibleForTesting ExpiringSet(LongSupplier currentTimeSupplier) { this.currentTimeSupplier = currentTimeSupplier; } @Override public boolean contains(Object element) { purgeExpired(); return storage.containsKey(element); } @Override public boolean containsAll(Collection c) { purgeExpired(); for (Object element : c) { if (!storage.containsKey(element)) { return false; } } return true; } @Override public int size() { purgeExpired(); return storage.size(); } @Override public boolean isEmpty() { purgeExpired(); return storage.isEmpty(); } @Override public Iterator iterator() { purgeExpired(); return storage.keySet().iterator(); } @Override public Object[] toArray() { purgeExpired(); return storage.keySet().toArray(); } @Override public T[] toArray(T[] a) { requireNonNull(a); purgeExpired(); return storage.keySet().toArray(a); } @Override public boolean add(E e) { requireNonNull(e); purgeExpired(); ExpiringEntry oldEntry = storage.put(e, new ExpiringEntry<>(e, Long.MAX_VALUE, null)); return oldEntry != null; } /** * Adds the specified element to this set if it is not already present, and expires the entry when the specified * expiry time is reached. * * @param element The element to add to the set. * @param expiry The expiry time for the element, in milliseconds since the epoch. * @return {@code true} if this set did not already contain the specified element. */ public boolean add(E element, long expiry) { return add(element, expiry, null); } /** * Adds the specified element to this set if it is not already present, and expires the entry when the specified * expiry time is reached. * * @param element The element to add to the set. * @param expiry The expiry time for the element, in milliseconds since the epoch. * @param expiryListener A listener that will be invoked when the entry expires. * @return {@code true} if this set did not already contain the specified element. */ public boolean add(E element, long expiry, @Nullable Consumer expiryListener) { requireNonNull(element); if (expiry >= Long.MAX_VALUE) { return add(element); } long now = currentTimeSupplier.getAsLong(); purgeExpired(now); if (expiry <= now) { boolean removedPrevious = remove(element); if (expiryListener != null) { expiryListener.accept(element); } return removedPrevious; } ExpiringEntry newEntry = new ExpiringEntry<>(element, expiry, expiryListener); ExpiringEntry oldEntry = storage.put(element, newEntry); expiryQueue.offer(newEntry); if (oldEntry != null && oldEntry.expiry < Long.MAX_VALUE) { expiryQueue.remove(oldEntry); } return oldEntry != null; } @Override public boolean addAll(Collection c) { requireNonNull(c); purgeExpired(); boolean changed = false; for (E element : c) { ExpiringEntry oldEntry = storage.put(element, new ExpiringEntry<>(element, Long.MAX_VALUE, null)); if (oldEntry == null) { changed = true; } } return changed; } @Override public boolean remove(Object element) { requireNonNull(element); purgeExpired(); ExpiringEntry entry = storage.remove(element); if (entry == null) { return false; } if (entry.expiry < Long.MAX_VALUE) { expiryQueue.remove(entry); } return true; } @Override public boolean removeAll(Collection c) { requireNonNull(c); purgeExpired(); boolean changed = false; for (Object element : c) { ExpiringEntry entry = storage.remove(element); if (entry != null) { if (entry.expiry < Long.MAX_VALUE) { expiryQueue.remove(entry); } changed = true; } } return changed; } @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } @Override public void clear() { expiryQueue.clear(); storage.clear(); } /** * Force immediate expiration of any key/value pairs that have reached their expiry. * * @return The earliest expiry time for the current entries in the map (in milliseconds since the epoch), or * {@code Long.MAX_VALUE} if there are no entries due to expire. */ public long purgeExpired() { return purgeExpired(currentTimeSupplier.getAsLong()); } private long purgeExpired(long oldest) { ExpiringEntry entry; while ((entry = expiryQueue.peek()) != null && entry.expiry <= oldest) { // only remove if it's still mapped to the same entry (object equality is used) if (!expiryQueue.remove(entry) || !storage.remove(entry.element, entry)) { continue; } if (entry.expiryListener != null) { entry.expiryListener.accept(entry.element); } } return entry == null ? Long.MAX_VALUE : entry.expiry; } @SuppressWarnings("rawtypes") @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (!(obj instanceof ExpiringSet)) { return false; } ExpiringSet other = (ExpiringSet) obj; return storage.equals(other.storage); } @Override public int hashCode() { return storage.hashCode(); } } cava-0.6.0/concurrent/src/main/java/net/consensys/cava/concurrent/package-info.java000066400000000000000000000005661341750772100303600ustar00rootroot00000000000000/** * 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). */ @ParametersAreNonnullByDefault package net.consensys.cava.concurrent; import javax.annotation.ParametersAreNonnullByDefault; cava-0.6.0/concurrent/src/test/000077500000000000000000000000001341750772100163665ustar00rootroot00000000000000cava-0.6.0/concurrent/src/test/java/000077500000000000000000000000001341750772100173075ustar00rootroot00000000000000cava-0.6.0/concurrent/src/test/java/net/000077500000000000000000000000001341750772100200755ustar00rootroot00000000000000cava-0.6.0/concurrent/src/test/java/net/consensys/000077500000000000000000000000001341750772100221215ustar00rootroot00000000000000cava-0.6.0/concurrent/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100230335ustar00rootroot00000000000000cava-0.6.0/concurrent/src/test/java/net/consensys/cava/concurrent/000077500000000000000000000000001341750772100252155ustar00rootroot00000000000000cava-0.6.0/concurrent/src/test/java/net/consensys/cava/concurrent/AtomicSlotMapTest.java000066400000000000000000000103601341750772100314340ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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 org.junit.jupiter.api.Assertions.assertEquals; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; class AtomicSlotMapTest { @Test void shouldUseSlotsIncrementally() { AtomicSlotMap slotMap = AtomicSlotMap.positiveIntegerSlots(); assertEquals(1, (int) slotMap.add("value")); assertEquals(2, (int) slotMap.add("value")); assertEquals(3, (int) slotMap.add("value")); assertEquals(4, (int) slotMap.add("value")); } @Test void shouldReuseSlotsIncrementally() { AtomicSlotMap slotMap = AtomicSlotMap.positiveIntegerSlots(); assertEquals(1, (int) slotMap.add("value")); assertEquals(2, (int) slotMap.add("value")); assertEquals(3, (int) slotMap.add("value")); assertEquals(4, (int) slotMap.add("value")); slotMap.remove(2); slotMap.remove(4); assertEquals(2, (int) slotMap.add("value")); assertEquals(4, (int) slotMap.add("value")); } @Test void maintainsCountWhenConcurrentlyRemoving() { AtomicSlotMap slotMap = AtomicSlotMap.positiveIntegerSlots(); int firstSlot = slotMap.add("foo"); CompletableAsyncResult result = AsyncResult.incomplete(); AtomicInteger secondSlot = new AtomicInteger(); slotMap.computeAsync(s -> { secondSlot.set(s); return result; }); assertEquals(1, slotMap.size()); slotMap.remove(secondSlot.get()); assertEquals(1, slotMap.size()); result.complete("bar"); assertEquals(1, slotMap.size()); slotMap.remove(firstSlot); assertEquals(0, slotMap.size()); } @Test void shouldNotDuplicateSlotsWhileAddingAndRemoving() throws Exception { AtomicSlotMap slotMap = AtomicSlotMap.positiveIntegerSlots(); Set fastSlots = ConcurrentHashMap.newKeySet(); Set slowSlots = ConcurrentHashMap.newKeySet(); Callable fastAdders = () -> { int slot = slotMap.add("a fast value"); fastSlots.add(slot); return null; }; Callable slowAdders = () -> { CompletableAsyncResult result = AsyncResult.incomplete(); slotMap.computeAsync(s -> result).thenAccept(slowSlots::add); Thread.sleep(10); result.complete("a slow value"); return null; }; Callable addAndRemovers = () -> { int slot = slotMap.add("a value"); Thread.sleep(5); slotMap.remove(slot); return null; }; ExecutorService fastPool = Executors.newFixedThreadPool(20); ExecutorService slowPool = Executors.newFixedThreadPool(20); ExecutorService addAndRemovePool = Executors.newFixedThreadPool(40); List> fastFutures = fastPool.invokeAll(Collections.nCopies(1000, fastAdders)); List> slowFutures = slowPool.invokeAll(Collections.nCopies(1000, slowAdders)); List> addAndRemoveFutures = addAndRemovePool.invokeAll(Collections.nCopies(2000, addAndRemovers)); for (Future future : addAndRemoveFutures) { future.get(); } for (Future future : slowFutures) { future.get(); } for (Future future : fastFutures) { future.get(); } assertEquals(1000, fastSlots.size()); assertEquals(1000, slowSlots.size()); slowSlots.addAll(fastSlots); assertEquals(2000, slowSlots.size()); assertEquals(2000, slotMap.size()); } } DefaultCompletableAsyncCompletionTest.java000066400000000000000000000247661341750772100354440ustar00rootroot00000000000000cava-0.6.0/concurrent/src/test/java/net/consensys/cava/concurrent/* * Copyright 2018 ConsenSys AG. * * 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 org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import java.util.Arrays; import java.util.Collection; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; class DefaultCompletableAsyncCompletionTest { @Test void shouldReturnValueFromCompletedResult() { AsyncCompletion completion = AsyncCompletion.completed(); assertThat(completion.isDone()).isTrue(); } @Test void shouldReturnExceptionFromExceptionallyCompletedResult() throws Exception { Exception exception = new RuntimeException(); AsyncCompletion completion = AsyncCompletion.exceptional(exception); assertThat(completion.isDone()).isTrue(); assertCompletedWithException(completion, exception); } @Test void isNotDoneUntilCompleted() { CompletableAsyncCompletion completion = AsyncCompletion.incomplete(); assertThat(completion.isDone()).isFalse(); completion.complete(); assertThat(completion.isDone()).isTrue(); } @Test void invokesContinuationFunctionWhenCompleted() throws Exception { CompletableAsyncCompletion completion = AsyncCompletion.incomplete(); AsyncResult asyncResult = completion.then(() -> AsyncResult.completed("Completed")); assertThat(asyncResult.isDone()).isFalse(); completion.complete(); assertThat(asyncResult.isDone()).isTrue(); assertThat(asyncResult.get()).isEqualTo("Completed"); } @Test void completesExceptionallyWhenContinuationResultCompletesExceptionally() throws Exception { Exception exception = new RuntimeException(); CompletableAsyncCompletion completion = AsyncCompletion.incomplete(); AsyncResult asyncResult = completion.then(() -> AsyncResult.exceptional(exception)); assertThat(asyncResult.isDone()).isFalse(); completion.complete(); assertThat(asyncResult.isDone()).isTrue(); assertCompletedWithException(asyncResult, exception); } @Test void completesExceptionallyWhenContinuationFunctionThrows() throws Exception { RuntimeException exception = new RuntimeException(); CompletableAsyncCompletion completion = AsyncCompletion.incomplete(); AsyncResult asyncResult = completion.then(() -> { throw exception; }); assertThat(asyncResult.isDone()).isFalse(); completion.complete(); assertThat(asyncResult.isDone()).isTrue(); assertCompletedWithException(asyncResult, exception); } @Test void doesntInvokeContinuationFunctionIfCompletingExceptionally() throws Exception { RuntimeException exception = new RuntimeException(); CompletableAsyncCompletion completion = AsyncCompletion.incomplete(); AsyncResult asyncResult = completion.then(() -> { fail("should not be invoked"); throw new RuntimeException(); }); assertThat(asyncResult.isDone()).isFalse(); completion.completeExceptionally(exception); assertThat(asyncResult.isDone()).isTrue(); assertCompletedWithException(asyncResult, exception); } @Test void completesWhenComposedCompletionCompletes() { CompletableAsyncCompletion completion1 = AsyncCompletion.incomplete(); CompletableAsyncCompletion completion2 = AsyncCompletion.incomplete(); AtomicBoolean composed = new AtomicBoolean(false); AsyncCompletion result = completion1.thenCompose(() -> { composed.set(true); return completion2; }); assertThat(result.isDone()).isFalse(); assertThat(composed.get()).isFalse(); completion1.complete(); assertThat(result.isDone()).isFalse(); assertThat(composed.get()).isTrue(); completion2.complete(); assertThat(result.isDone()).isTrue(); } @Test void completesExceptionallyWhenComposedCompletionThrows() throws Exception { CompletableAsyncCompletion completion1 = AsyncCompletion.incomplete(); RuntimeException exception = new RuntimeException(); AsyncCompletion result = completion1.thenCompose(() -> { throw exception; }); assertThat(result.isDone()).isFalse(); completion1.complete(); assertCompletedWithException(result, exception); } @Test void completesExceptionallyWhenComposedCompletionCompletesExceptionally() throws Exception { CompletableAsyncCompletion completion1 = AsyncCompletion.incomplete(); CompletableAsyncCompletion completion2 = AsyncCompletion.incomplete(); AtomicBoolean composed = new AtomicBoolean(false); AsyncCompletion result = completion1.thenCompose(() -> { composed.set(true); return completion2; }); assertThat(result.isDone()).isFalse(); assertThat(composed.get()).isFalse(); completion1.complete(); assertThat(result.isDone()).isFalse(); assertThat(composed.get()).isTrue(); RuntimeException exception = new RuntimeException(); completion2.completeExceptionally(exception); assertThat(result.isDone()).isTrue(); assertCompletedWithException(result, exception); } @Test void completesExceptionallyWhenComposerCompletesExceptionally() throws Exception { CompletableAsyncCompletion completion1 = AsyncCompletion.incomplete(); CompletableAsyncCompletion completion2 = AsyncCompletion.incomplete(); AtomicBoolean composed = new AtomicBoolean(false); AsyncCompletion result = completion1.thenCompose(() -> { composed.set(true); return completion2; }); assertThat(result.isDone()).isFalse(); assertThat(composed.get()).isFalse(); RuntimeException exception = new RuntimeException(); completion1.completeExceptionally(exception); assertThat(result.isDone()).isTrue(); assertThat(composed.get()).isFalse(); assertCompletedWithException(result, exception); } @Test void completesWhenCombinedCompletionCompletes() { CompletableAsyncCompletion completion1 = AsyncCompletion.incomplete(); CompletableAsyncCompletion completion2 = AsyncCompletion.incomplete(); AsyncCompletion result = completion1.thenCombine(completion2); assertThat(result.isDone()).isFalse(); completion1.complete(); assertThat(result.isDone()).isFalse(); completion2.complete(); assertThat(result.isDone()).isTrue(); } @Test void completesExceptionallyWhenCombinedCompletionCompletesExceptionally() throws Exception { CompletableAsyncCompletion completion1 = AsyncCompletion.incomplete(); CompletableAsyncCompletion completion2 = AsyncCompletion.incomplete(); AsyncCompletion result = completion1.thenCombine(completion2); assertThat(result.isDone()).isFalse(); completion1.complete(); assertThat(result.isDone()).isFalse(); RuntimeException exception = new RuntimeException(); completion2.completeExceptionally(exception); assertThat(result.isDone()).isTrue(); assertCompletedWithException(result, exception); } @Test void completesExceptionallyWhenCombinerCompletesExceptionally() throws Exception { CompletableAsyncCompletion completion1 = AsyncCompletion.incomplete(); CompletableAsyncCompletion completion2 = AsyncCompletion.incomplete(); AsyncCompletion result = completion1.thenCombine(completion2); assertThat(result.isDone()).isFalse(); RuntimeException exception = new RuntimeException(); completion1.completeExceptionally(exception); assertThat(result.isDone()).isTrue(); assertCompletedWithException(result, exception); } @Test void completesWhenAllInCollectionComplete() { CompletableAsyncCompletion completion1 = AsyncCompletion.incomplete(); CompletableAsyncCompletion completion2 = AsyncCompletion.incomplete(); Collection list = Arrays.asList(completion1, completion2); AsyncCompletion completion = AsyncCompletion.allOf(list); assertThat(completion.isDone()).isFalse(); completion1.complete(); assertThat(completion.isDone()).isFalse(); completion2.complete(); assertThat(completion.isDone()).isTrue(); } @Test void completesWithExceptionWhenAnyInCollectionFail() throws Exception { CompletableAsyncCompletion completion1 = AsyncCompletion.incomplete(); CompletableAsyncCompletion completion2 = AsyncCompletion.incomplete(); AsyncCompletion completion = AsyncCompletion.allOf(completion1, completion2); assertThat(completion.isDone()).isFalse(); Exception exception = new RuntimeException(); completion1.completeExceptionally(exception); assertThat(completion.isDone()).isFalse(); completion2.complete(); assertThat(completion.isDone()).isTrue(); assertCompletedWithException(completion, exception); } @Test void invokesComposedWhenCanceled() { CompletableAsyncCompletion completion = AsyncCompletion.incomplete(); AtomicReference completedThrowable = new AtomicReference<>(); AsyncCompletion downstreamCompletion = completion.whenComplete(completedThrowable::set); completion.cancel(); assertThat(completion.isDone()).isTrue(); assertThat(completion.isCancelled()).isTrue(); assertThat(completion.isCompletedExceptionally()).isTrue(); assertThat(downstreamCompletion.isDone()).isTrue(); assertThat(downstreamCompletion.isCancelled()).isFalse(); assertThat(downstreamCompletion.isCompletedExceptionally()).isTrue(); assertThat(completedThrowable.get()).isInstanceOf(CancellationException.class); } private void assertCompletedWithException(AsyncCompletion completion, Exception exception) throws Exception { try { completion.join(); fail("Expected exception not thrown"); } catch (CompletionException ex) { assertThat(ex.getCause()).isSameAs(exception); } } private void assertCompletedWithException(AsyncResult asyncResult, Exception exception) throws Exception { try { asyncResult.get(); fail("Expected exception not thrown"); } catch (CompletionException ex) { assertThat(ex.getCause()).isSameAs(exception); } } } DefaultCompletableAsyncResultTest.java000066400000000000000000000167741341750772100346110ustar00rootroot00000000000000cava-0.6.0/concurrent/src/test/java/net/consensys/cava/concurrent/* * Copyright 2018 ConsenSys AG. * * 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 org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.assertNull; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletionException; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; class DefaultCompletableAsyncResultTest { @Test void shouldReturnValueFromCompletedResult() throws Exception { AsyncResult asyncResult = AsyncResult.completed("Completed"); assertThat(asyncResult.isDone()).isTrue(); assertThat(asyncResult.get()).isEqualTo("Completed"); } @Test void shouldReturnNullValueFromCompletedResult() throws Exception { AsyncResult asyncResult = AsyncResult.completed(null); assertThat(asyncResult.isDone()).isTrue(); assertNull(asyncResult.get()); } @Test void shouldReturnExceptionFromExceptionallyCompletedResult() throws Exception { Exception exception = new RuntimeException(); AsyncResult asyncResult = AsyncResult.exceptional(exception); assertThat(asyncResult.isDone()).isTrue(); assertCompletedWithException(asyncResult, exception); } @Test void isNotDoneUntilCompleted() throws Exception { CompletableAsyncResult asyncResult = AsyncResult.incomplete(); assertThat(asyncResult.isDone()).isFalse(); asyncResult.complete("Completed"); assertThat(asyncResult.isDone()).isTrue(); assertThat(asyncResult.get()).isEqualTo("Completed"); } @Test void suppliesAsyncResultWhenCompleted() throws Exception { CompletableAsyncResult asyncResult = AsyncResult.incomplete(); AsyncResult asyncResult2 = asyncResult.then(value -> { assertThat(value).isEqualTo("Completed1"); return AsyncResult.completed("Completed2"); }); assertThat(asyncResult2.isDone()).isFalse(); asyncResult.complete("Completed1"); assertThat(asyncResult2.isDone()).isTrue(); assertThat(asyncResult2.get()).isEqualTo("Completed2"); } @Test void completesExceptionallyWhenSuppliedResultCompletesExceptionally() throws Exception { Exception exception = new RuntimeException(); CompletableAsyncResult asyncResult = AsyncResult.incomplete(); AsyncResult asyncResult2 = asyncResult.then(value -> AsyncResult.exceptional(exception)); assertThat(asyncResult2.isDone()).isFalse(); asyncResult.complete("Complete"); assertThat(asyncResult2.isDone()).isTrue(); assertCompletedWithException(asyncResult2, exception); } @Test void completesExceptionallyWhenSupplierThrows() throws Exception { RuntimeException exception = new RuntimeException(); CompletableAsyncResult asyncResult = AsyncResult.incomplete(); AsyncResult asyncResult2 = asyncResult.then(value -> { throw exception; }); assertThat(asyncResult2.isDone()).isFalse(); asyncResult.complete("Complete"); assertThat(asyncResult2.isDone()).isTrue(); assertCompletedWithException(asyncResult2, exception); } @Test void doesntInvokeSupplierIfCompletingExceptionally() throws Exception { RuntimeException exception = new RuntimeException(); CompletableAsyncResult asyncResult = AsyncResult.incomplete(); AsyncResult asyncResult2 = asyncResult.then(value -> { fail("should not be invoked"); throw new RuntimeException(); }); assertThat(asyncResult2.isDone()).isFalse(); asyncResult.completeExceptionally(exception); assertThat(asyncResult2.isDone()).isTrue(); assertCompletedWithException(asyncResult2, exception); } @Test void completesWhenAllInCollectionComplete() { CompletableAsyncResult asyncResult1 = AsyncResult.incomplete(); CompletableAsyncResult asyncResult2 = AsyncResult.incomplete(); Collection> list = Arrays.asList(asyncResult1, asyncResult2); AsyncCompletion completion = AsyncResult.allOf(list); assertThat(completion.isDone()).isFalse(); asyncResult1.complete("one"); assertThat(completion.isDone()).isFalse(); asyncResult2.complete("two"); assertThat(completion.isDone()).isTrue(); } @Test void completesWithExceptionWhenAnyInCollectionFail() throws Exception { CompletableAsyncResult asyncResult1 = AsyncResult.incomplete(); CompletableAsyncResult asyncResult2 = AsyncResult.incomplete(); AsyncCompletion completion = AsyncResult.allOf(asyncResult1, asyncResult2); assertThat(completion.isDone()).isFalse(); Exception exception = new RuntimeException(); asyncResult1.completeExceptionally(exception); assertThat(completion.isDone()).isFalse(); asyncResult2.complete(2); assertThat(completion.isDone()).isTrue(); assertCompletedWithException(completion, exception); } @Test void completesWhenCombinedComplete() throws Exception { CompletableAsyncResult asyncResult1 = AsyncResult.incomplete(); CompletableAsyncResult asyncResult2 = AsyncResult.incomplete(); Collection> list = Arrays.asList(asyncResult1, asyncResult2); AsyncResult> result = AsyncResult.combine(list); assertThat(result.isDone()).isFalse(); asyncResult1.complete("one"); assertThat(result.isDone()).isFalse(); asyncResult2.complete("two"); assertThat(result.isDone()).isTrue(); List strings = result.get(); assertThat(strings).isEqualTo(Arrays.asList("one", "two")); } @Test void invokesComposedWhenCanceled() { CompletableAsyncResult asyncResult = AsyncResult.incomplete(); AtomicReference completedThrowable = new AtomicReference<>(); AsyncResult downstreamAsyncResult = asyncResult.whenComplete((result, throwable) -> completedThrowable.set(throwable)); asyncResult.cancel(); assertThat(asyncResult.isDone()).isTrue(); assertThat(asyncResult.isCancelled()).isTrue(); assertThat(asyncResult.isCompletedExceptionally()).isTrue(); assertThat(downstreamAsyncResult.isDone()).isTrue(); assertThat(downstreamAsyncResult.isCancelled()).isFalse(); assertThat(downstreamAsyncResult.isCompletedExceptionally()).isTrue(); assertThat(completedThrowable.get()).isInstanceOf(CancellationException.class); } private void assertCompletedWithException(AsyncResult asyncResult, Exception exception) throws Exception { try { asyncResult.get(); fail("Expected exception not thrown"); } catch (CompletionException ex) { assertThat(ex.getCause()).isSameAs(exception); } } private void assertCompletedWithException(AsyncCompletion completion, Exception exception) throws Exception { try { completion.join(); fail("Expected exception not thrown"); } catch (CompletionException ex) { assertThat(ex.getCause()).isSameAs(exception); } } } cava-0.6.0/concurrent/src/test/java/net/consensys/cava/concurrent/ExpiringMapTest.java000066400000000000000000000110271341750772100311440ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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 org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.Instant; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class ExpiringMapTest { private Instant currentTime; private ExpiringMap map; @BeforeEach void setup() { currentTime = Instant.now(); map = new ExpiringMap<>(() -> currentTime.toEpochMilli()); } @Test void canAddAndRemoveWithoutExpiry() { map.put(1, "foo"); assertTrue(map.containsKey(1)); assertTrue(map.containsValue("foo")); assertEquals("foo", map.get(1)); assertEquals(1, map.size()); assertFalse(map.isEmpty()); String removed = map.remove(1); assertEquals("foo", removed); assertFalse(map.containsKey(1)); assertFalse(map.containsValue("foo")); assertNull(map.get(1)); assertEquals(0, map.size()); assertTrue(map.isEmpty()); assertNull(map.remove(1)); } @Test void canAddAndRemoveWithExpiry() { map.put(1, "foo", currentTime.plusMillis(1).toEpochMilli()); assertTrue(map.containsKey(1)); assertTrue(map.containsValue("foo")); assertEquals("foo", map.get(1)); assertEquals(1, map.size()); assertFalse(map.isEmpty()); String removed = map.remove(1); assertEquals("foo", removed); assertFalse(map.containsKey(1)); assertFalse(map.containsValue("foo")); assertNull(map.get(1)); assertEquals(0, map.size()); assertTrue(map.isEmpty()); assertNull(map.remove(1)); } @Test void itemIsExpiredAfterExpiry() { Instant futureTime = currentTime.plusSeconds(10); map.put(1, "foo", futureTime.toEpochMilli()); assertTrue(map.containsKey(1)); assertEquals("foo", map.get(1)); currentTime = futureTime; assertFalse(map.containsKey(1)); } @Test void itemIsMissingAfterExpiry() { Instant futureTime = currentTime.plusSeconds(10); map.put(1, "foo", futureTime.toEpochMilli()); assertTrue(map.containsKey(1)); assertEquals("foo", map.get(1)); currentTime = futureTime; assertNull(map.get(1)); } @Test void addingExpiredItemRemovesExisting() { map.put(1, "foo"); String prev = map.put(1, "bar", 0); assertEquals("foo", prev); assertFalse(map.containsKey(1)); } @Test void doesNotExpireItemThatWasReplaced() { Instant futureTime = currentTime.plusSeconds(10); map.put(1, "foo", futureTime.toEpochMilli()); map.put(1, "bar", futureTime.plusSeconds(1).toEpochMilli()); currentTime = futureTime; assertTrue(map.containsKey(1)); assertEquals("bar", map.get(1)); } @Test void shouldReturnNextExpiryTimeWhenPurging() { Instant futureTime1 = currentTime.plusSeconds(15); Instant futureTime2 = currentTime.plusSeconds(12); Instant futureTime3 = currentTime.plusSeconds(10); map.put(1, "foo", futureTime1.toEpochMilli()); map.put(2, "bar", futureTime2.toEpochMilli()); map.put(3, "baz", futureTime3.toEpochMilli()); currentTime = futureTime3; assertEquals(futureTime2.toEpochMilli(), map.purgeExpired()); currentTime = futureTime2; assertEquals(futureTime1.toEpochMilli(), map.purgeExpired()); currentTime = futureTime1; assertEquals(Long.MAX_VALUE, map.purgeExpired()); } @Test void shouldCallExpiryListener() { AtomicBoolean removed1 = new AtomicBoolean(false); AtomicBoolean removed2 = new AtomicBoolean(false); Instant futureTime = currentTime.plusSeconds(15); map.put(1, "foo", currentTime.toEpochMilli(), (k, v) -> removed1.set(true)); assertTrue(removed1.get()); map.put(2, "bar", futureTime.toEpochMilli(), (k, v) -> removed2.set(true)); assertFalse(removed2.get()); currentTime = futureTime; map.purgeExpired(); assertTrue(removed2.get()); } } cava-0.6.0/concurrent/src/test/java/net/consensys/cava/concurrent/ExpiringSetTest.java000066400000000000000000000071711341750772100311670ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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 org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertEquals; import java.time.Instant; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class ExpiringSetTest { private Instant currentTime; private ExpiringSet set; @BeforeEach void setup() { currentTime = Instant.now(); set = new ExpiringSet<>(() -> currentTime.toEpochMilli()); } @Test void canAddAndRemoveWithoutExpiry() { set.add("foo"); assertTrue(set.contains("foo")); assertEquals(1, set.size()); assertFalse(set.isEmpty()); assertTrue(set.remove("foo")); assertFalse(set.contains("foo")); assertEquals(0, set.size()); assertTrue(set.isEmpty()); assertFalse(set.remove("foo")); } @Test void canAddAndRemoveWithExpiry() { set.add("foo", currentTime.plusMillis(1).toEpochMilli()); assertTrue(set.contains("foo")); assertEquals(1, set.size()); assertFalse(set.isEmpty()); assertTrue(set.remove("foo")); assertFalse(set.contains("foo")); assertEquals(0, set.size()); assertTrue(set.isEmpty()); assertFalse(set.remove("foo")); } @Test void itemIsMissingAfterExpiry() { Instant futureTime = currentTime.plusSeconds(10); set.add("foo", futureTime.toEpochMilli()); assertTrue(set.contains("foo")); currentTime = futureTime; assertFalse(set.contains("foo")); } @Test void addingExpiredItemRemovesExisting() { set.add("foo"); assertTrue(set.add("foo", 0)); assertFalse(set.contains("foo")); } @Test void doesNotExpireItemThatWasReplaced() { Instant futureTime = currentTime.plusSeconds(10); set.add("foo", futureTime.toEpochMilli()); set.add("foo", futureTime.plusSeconds(1).toEpochMilli()); currentTime = futureTime; assertTrue(set.contains("foo")); } @Test void shouldReturnNextExpiryTimeWhenPurging() { Instant futureTime1 = currentTime.plusSeconds(15); Instant futureTime2 = currentTime.plusSeconds(12); Instant futureTime3 = currentTime.plusSeconds(10); set.add("foo", futureTime1.toEpochMilli()); set.add("bar", futureTime2.toEpochMilli()); set.add("baz", futureTime3.toEpochMilli()); currentTime = futureTime3; assertEquals(futureTime2.toEpochMilli(), set.purgeExpired()); currentTime = futureTime2; assertEquals(futureTime1.toEpochMilli(), set.purgeExpired()); currentTime = futureTime1; assertEquals(Long.MAX_VALUE, set.purgeExpired()); } @Test void shouldCallExpiryListener() { AtomicBoolean removed1 = new AtomicBoolean(false); AtomicBoolean removed2 = new AtomicBoolean(false); Instant futureTime = currentTime.plusSeconds(15); set.add("foo", currentTime.toEpochMilli(), e -> removed1.set(true)); assertTrue(removed1.get()); set.add("bar", futureTime.toEpochMilli(), e -> removed2.set(true)); assertFalse(removed2.get()); currentTime = futureTime; set.purgeExpired(); assertTrue(removed2.get()); } } cava-0.6.0/config/000077500000000000000000000000001341750772100137035ustar00rootroot00000000000000cava-0.6.0/config/build.gradle000066400000000000000000000004721341750772100161650ustar00rootroot00000000000000description = 'Classes and utilities for working with configuration.' dependencies { compile project(':toml') compile 'com.google.guava:guava' testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/config/src/000077500000000000000000000000001341750772100144725ustar00rootroot00000000000000cava-0.6.0/config/src/main/000077500000000000000000000000001341750772100154165ustar00rootroot00000000000000cava-0.6.0/config/src/main/java/000077500000000000000000000000001341750772100163375ustar00rootroot00000000000000cava-0.6.0/config/src/main/java/net/000077500000000000000000000000001341750772100171255ustar00rootroot00000000000000cava-0.6.0/config/src/main/java/net/consensys/000077500000000000000000000000001341750772100211515ustar00rootroot00000000000000cava-0.6.0/config/src/main/java/net/consensys/cava/000077500000000000000000000000001341750772100220635ustar00rootroot00000000000000cava-0.6.0/config/src/main/java/net/consensys/cava/config/000077500000000000000000000000001341750772100233305ustar00rootroot00000000000000cava-0.6.0/config/src/main/java/net/consensys/cava/config/Configuration.java000066400000000000000000000312171341750772100270060ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.config; import static java.util.Objects.requireNonNull; import static net.consensys.cava.toml.Toml.canonicalDottedKey; import net.consensys.cava.toml.Toml; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.io.Writer; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * Represents collection of configuration properties, optionally validated against a schema. */ public interface Configuration { /** * Read a configuration from a TOML-formatted string. * * @param toml A TOML-formatted string. * @return A Configuration loaded from the TOML file. */ static Configuration fromToml(String toml) { return fromToml(toml, null); } /** * Read a configuration from a TOML-formatted string, associated with a validation schema. * * @param toml A TOML-formatted string. * @param schema The validation schema for the configuration. * @return A Configuration loaded from the TOML file. */ static Configuration fromToml(String toml, @Nullable Schema schema) { requireNonNull(toml); return new TomlBackedConfiguration(Toml.parse(toml), schema); } /** * Loads a configuration from a TOML-formatted file. * * @param file The path of the TOML-formatted configuration file. * @return A Configuration loaded from the TOML file. * @throws NoSuchFileException If the file could not be found. * @throws IOException If an IO error occurs. */ static Configuration fromToml(Path file) throws IOException { return fromToml(file, null); } /** * Loads a configuration from a file, associated with a validation schema. * * @param file The path of the TOML-formatted configuration file. * @param schema The validation schema for the configuration. * @return A Configuration loaded from the TOML file. * @throws NoSuchFileException If the file could not be found. * @throws IOException If an IO error occurs. */ static Configuration fromToml(Path file, @Nullable Schema schema) throws IOException { requireNonNull(file); return new TomlBackedConfiguration(Toml.parse(file), schema); } /** * Loads a configuration from a TOML-formatted file. * * @param is An input stream providing TOML-formatted configuration. * @return A Configuration loaded from the TOML file. * @throws IOException If an IO error occurs. */ static Configuration fromToml(InputStream is) throws IOException { return fromToml(is, null); } /** * Loads a configuration from a file, associated with a validation schema. * * @param is An input stream providing TOML-formatted configuration. * @param schema The validation schema for the configuration. * @return A Configuration loaded from the TOML file. * @throws IOException If an IO error occurs. */ static Configuration fromToml(InputStream is, @Nullable Schema schema) throws IOException { requireNonNull(is); return new TomlBackedConfiguration(Toml.parse(is), schema); } /** * Get an empty configuration, with no values. * * @return An empty configuration, with no values. */ static Configuration empty() { return EmptyConfiguration.EMPTY; } /** * Get an empty configuration, associated with a validation schema. * * @param schema The validation schema for the configuration. * @return An empty configuration. */ static Configuration empty(@Nullable Schema schema) { return new EmptyConfiguration(schema); } /** * @return {@code true} if the TOML document contained errors. */ default boolean hasErrors() { return !(errors().isEmpty()); } /** * The errors that occurred during parsing. * * @return A list of errors. */ List errors(); /** * Get a TOML-formatted representation of this configuration. * * @return A TOML-formatted representation. */ default String toToml() { StringBuilder builder = new StringBuilder(); try { toToml(builder); } catch (IOException e) { // Not reachable throw new UncheckedIOException(e); } return builder.toString(); } /** * Save a configuration to a TOML-formatted file. * *

* If necessary, parent directories for the output file will be created. * * @param path The file path to write the TOML-formatted output to. * @throws IOException If the file cannot be written. */ default void toToml(Path path) throws IOException { Files.createDirectories(path.getParent()); try (Writer writer = Files.newBufferedWriter(path)) { toToml(writer); } } /** * Writes a configuration in TOML format. * * @param appendable The output to write to. * @throws IOException If the file cannot be written. */ void toToml(Appendable appendable) throws IOException; /** * The keys of all entries present in this configuration. * * @return The keys of all entries in this configuration. */ Set keySet(); /** * Check if a key is set in this configuration. * * @param key A configuration key (e.g. {@code "server.address.hostname"}). * @return {@code true} if the entry is present in this configuration. * @throws IllegalArgumentException If the key cannot be parsed. */ boolean contains(String key); /** * Get an object from this configuration. * * @param key A configuration key (e.g. {@code "server.address.hostname"}). * @return The value, or {@code null} if no value was set in the configuration. * @throws IllegalArgumentException If the key cannot be parsed. */ @Nullable Object get(String key); /** * Get the position where a key is defined in the TOML document. * * @param key A configuration key (e.g. {@code "server.address.port"}). * @return The input position, or {@code null} if the key was not set in this configuration. * @throws IllegalArgumentException If the key cannot be parsed. */ @Nullable DocumentPosition inputPositionOf(String key); /** * Get a string from this configuration. * * @param key A configuration key (e.g. {@code "server.address.hostname"}). * @return The value. * @throws IllegalArgumentException If the key cannot be parsed. * @throws InvalidConfigurationPropertyTypeException If the value is not a string. * @throws NoConfigurationPropertyException If the key was not set in the configuration. */ String getString(String key); /** * Get an integer from this configuration. * * @param key A configuration key (e.g. {@code "server.address.port"}). * @return The value. * @throws IllegalArgumentException If the key cannot be parsed. * @throws InvalidConfigurationPropertyTypeException If the value is not an integer. * @throws NoConfigurationPropertyException If the key was not set in the configuration. */ int getInteger(String key); /** * Get a long from this configuration. * * @param key A configuration key (e.g. {@code "server.address.port"}). * @return The value. * @throws IllegalArgumentException If the key cannot be parsed. * @throws InvalidConfigurationPropertyTypeException If the value is not a long. * @throws NoConfigurationPropertyException If the key was not set in the configuration. */ long getLong(String key); /** * Get a double from this configuration. * * @param key A configuration key (e.g. {@code "server.priority"}). * @return The value. * @throws IllegalArgumentException If the key cannot be parsed. * @throws InvalidConfigurationPropertyTypeException If the value is not a double. * @throws NoConfigurationPropertyException If the key was not set in the configuration. */ double getDouble(String key); /** * Get a boolean from this configuration. * * @param key A configuration key (e.g. {@code "server.active"}). * @return The value. * @throws IllegalArgumentException If the key cannot be parsed. * @throws InvalidConfigurationPropertyTypeException If the value is not a boolean. * @throws NoConfigurationPropertyException If the key was not set in the configuration. */ boolean getBoolean(String key); /** * Get a map from this configuration. * * @param key A configuration key (e.g. {@code "server.active"}). * @return The value. * @throws IllegalArgumentException If the key cannot be parsed. * @throws InvalidConfigurationPropertyTypeException If the value is not a map. * @throws NoConfigurationPropertyException If the key was not set in the configuration. */ Map getMap(String key); /** * Get a list from this configuration. * * @param key A configuration key (e.g. {@code "server.common_names"}). * @return The value. * @throws IllegalArgumentException If the key cannot be parsed. * @throws InvalidConfigurationPropertyTypeException If the value is not a list. * @throws NoConfigurationPropertyException If the key was not set in the configuration. */ List getList(String key); /** * Get a list of strings from this configuration. * * @param key A configuration key (e.g. {@code "server.common_names"}). * @return The value. * @throws IllegalArgumentException If the key cannot be parsed. * @throws InvalidConfigurationPropertyTypeException If the value is not a list of strings. * @throws NoConfigurationPropertyException If the key was not set in the configuration. */ List getListOfString(String key); /** * Get a list of integers from this configuration. * * @param key A configuration key (e.g. {@code "server.address.ports"}). * @return The value. * @throws IllegalArgumentException If the key cannot be parsed. * @throws InvalidConfigurationPropertyTypeException If the value is not a list of integers. * @throws NoConfigurationPropertyException If the key was not set in the configuration. */ List getListOfInteger(String key); /** * Get a list of longs from this configuration. * * @param key A configuration key (e.g. {@code "server.address.ports"}). * @return The value. * @throws IllegalArgumentException If the key cannot be parsed. * @throws InvalidConfigurationPropertyTypeException If the value is not a list of longs. * @throws NoConfigurationPropertyException If the key was not set in the configuration. */ List getListOfLong(String key); /** * Get a list of doubles from this configuration. * * @param key A configuration key (e.g. {@code "server.priorities"}). * @return The value. * @throws IllegalArgumentException If the key cannot be parsed. * @throws InvalidConfigurationPropertyTypeException If the value is not a list of doubles. * @throws NoConfigurationPropertyException If the key was not set in the configuration. */ List getListOfDouble(String key); /** * Get a list of booleans from this configuration. * * @param key A configuration key (e.g. {@code "server.flags"}). * @return The value. * @throws IllegalArgumentException If the key cannot be parsed. * @throws InvalidConfigurationPropertyTypeException If the value is not a list of booleans. * @throws NoConfigurationPropertyException If the key was not set in the configuration. */ List getListOfBoolean(String key); /** * Get a list of maps from this configuration. * * @param key A configuration key (e.g. {@code "mainnet.servers"}). * @return The value. * @throws IllegalArgumentException If the key cannot be parsed. * @throws InvalidConfigurationPropertyTypeException If the value is not a list of maps. * @throws NoConfigurationPropertyException If the key was not set in the configuration. */ List> getListOfMap(String key); /** * Get the canonical form of a configuration key. * * @param key A configuration key (e.g. {@code "server.flags"}). * @return The canonical form of the key. * @throws IllegalArgumentException If the key cannot be parsed. */ static String canonicalKey(String key) { return canonicalDottedKey(key); } } cava-0.6.0/config/src/main/java/net/consensys/cava/config/ConfigurationError.java000066400000000000000000000043571341750772100300250ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.config; import javax.annotation.Nullable; /** * Provides details regarding an error in the configuration. */ public final class ConfigurationError extends RuntimeException { @Nullable private final DocumentPosition position; /** * Construct an error. * * @param message The error message. */ public ConfigurationError(String message) { this(null, message); } /** * Construct an error. * * @param message The error message. * @param cause The cause of the error. */ public ConfigurationError(String message, @Nullable Throwable cause) { this(null, message, cause); } /** * Construct an error. * * @param position The location of the error in the configuration document. * @param message The error message. */ public ConfigurationError(@Nullable DocumentPosition position, String message) { super(message); this.position = position; } /** * Construct an error. * * @param position The location of the error in the configuration document. * @param message The error message. * @param cause The cause of the error. */ public ConfigurationError(@Nullable DocumentPosition position, String message, @Nullable Throwable cause) { super(message, cause); this.position = position; } /** * @return The position in the input where the error occurred, or {@code null} if no position information is * available. */ @Nullable public DocumentPosition position() { return position; } @Override public String toString() { if (position == null) { return getMessage(); } return getMessage() + " (" + position + ")"; } } cava-0.6.0/config/src/main/java/net/consensys/cava/config/ConfigurationErrors.java000066400000000000000000000047521341750772100302070ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.config; import java.util.Collections; import java.util.List; import javax.annotation.Nullable; /** * Factory methods for collections of {@link ConfigurationError}. */ public final class ConfigurationErrors { private ConfigurationErrors() {} /** * @return An empty list of errors. */ public static List noErrors() { return Collections.emptyList(); } /** * Create a single error. * * @param message The error message. * @return A list containing a single error. */ public static List singleError(String message) { return Collections.singletonList(new ConfigurationError(message)); } /** * Create a single error. * * @param message The error message. * @param cause The cause of the error. * @return A list containing a single error. */ public static List singleError(String message, @Nullable Throwable cause) { return Collections.singletonList(new ConfigurationError(message, cause)); } /** * Create a single error. * * @param position The location of the error in the configuration document. * @param message The error message. * @return A list containing a single error. */ public static List singleError(@Nullable DocumentPosition position, String message) { return Collections.singletonList(new ConfigurationError(position, message)); } /** * Create a single error. * * @param position The location of the error in the configuration document. * @param message The error message. * @param cause The cause of the error. * @return A list containing a single error. */ public static List singleError( @Nullable DocumentPosition position, String message, @Nullable Throwable cause) { return Collections.singletonList(new ConfigurationError(position, message, cause)); } } cava-0.6.0/config/src/main/java/net/consensys/cava/config/ConfigurationValidator.java000066400000000000000000000024041341750772100306500ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.config; import java.util.List; /** * A validator for a configuration. * *

* Validators of this type are invoked during verification after all property validators. However, errors returned by * property validators do not prevent this validator being evaluated, so properties of the configuration may be missing * or invalid. */ public interface ConfigurationValidator { /** * Validate a configuration. * * @param configuration The value associated with the configuration entry. * @return A list of error messages. If no errors are found, an empty list should be returned. */ List validate(Configuration configuration); } cava-0.6.0/config/src/main/java/net/consensys/cava/config/DocumentPosition.java000066400000000000000000000041161341750772100275000ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.config; /** * A position in an input document. */ public final class DocumentPosition { private final int line; private final int column; /** * Create a position. * * @param line The line. * @param column The column. * @return A position. */ public static DocumentPosition positionAt(int line, int column) { if (line < 1) { throw new IllegalArgumentException("line must be >= 1"); } if (column < 1) { throw new IllegalArgumentException("column must be >= 1"); } return new DocumentPosition(line, column); } private DocumentPosition(int line, int column) { this.line = line; this.column = column; } /** * The line number. * *

* The first line of the document is line 1. * * @return The line number (1..). */ public int line() { return line; } /** * The column number. * *

* The first column of the document is column 1. * * @return The column number (1..). */ public int column() { return column; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof DocumentPosition)) { return false; } DocumentPosition other = (DocumentPosition) obj; return this.line == other.line && this.column == other.column; } @Override public int hashCode() { return 31 * line + column; } @Override public String toString() { return "line " + line + ", column " + column; } } cava-0.6.0/config/src/main/java/net/consensys/cava/config/EmptyConfiguration.java000066400000000000000000000074371341750772100300340ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.config; import static net.consensys.cava.config.Configuration.canonicalKey; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.Nullable; final class EmptyConfiguration implements Configuration { static final Configuration EMPTY = new EmptyConfiguration(null); private final Schema schema; private final List errors; EmptyConfiguration(@Nullable Schema schema) { if (schema != null) { this.schema = schema; this.errors = schema.validate(EMPTY).collect(Collectors.toList()); } else { this.schema = Schema.EMPTY; this.errors = Collections.emptyList(); } } @Override public List errors() { return errors; } @Override public void toToml(Appendable appendable) throws IOException { new TomlSerializer(this, schema).writeTo(appendable); } @Override public Set keySet() { return schema.defaultsKeySet(); } @Override public boolean contains(String key) { return schema.hasDefault(key); } @Nullable @Override public Object get(String key) { return schema.getDefault(canonicalKey(key)); } @Nullable @Override public DocumentPosition inputPositionOf(String key) { return null; } @Override public String getString(String key) { return getValue(key, schema::getDefaultString); } @Override public int getInteger(String key) { return getValue(key, schema::getDefaultInteger); } @Override public long getLong(String key) { return getValue(key, schema::getDefaultLong); } @Override public double getDouble(String key) { return getValue(key, schema::getDefaultDouble); } @Override public boolean getBoolean(String key) { return getValue(key, schema::getDefaultBoolean); } @Override public Map getMap(String key) { return getValue(key, schema::getDefaultMap); } @Override public List getList(String key) { return getValue(key, schema::getDefaultList); } @Override public List getListOfString(String key) { return getValue(key, schema::getDefaultListOfString); } @Override public List getListOfInteger(String key) { return getValue(key, schema::getDefaultListOfInteger); } @Override public List getListOfLong(String key) { return getValue(key, schema::getDefaultListOfLong); } @Override public List getListOfDouble(String key) { return getValue(key, schema::getDefaultListOfDouble); } @Override public List getListOfBoolean(String key) { return getValue(key, schema::getDefaultListOfBoolean); } @Override public List> getListOfMap(String key) { return getValue(key, schema::getDefaultListOfMap); } private T getValue(String key, Function defaultGet) { String canonicalKey = canonicalKey(key); T value = defaultGet.apply(canonicalKey); if (value != null) { return value; } throw new NoConfigurationPropertyException("No value for property '" + canonicalKey + "'"); } } InvalidConfigurationPropertyTypeException.java000066400000000000000000000031141341750772100345170ustar00rootroot00000000000000cava-0.6.0/config/src/main/java/net/consensys/cava/config/* * Copyright 2018 ConsenSys AG. * * 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.config; import javax.annotation.Nullable; /** * An exception thrown when an invalid type is encountered. */ public final class InvalidConfigurationPropertyTypeException extends RuntimeException { @Nullable private final DocumentPosition position; InvalidConfigurationPropertyTypeException(@Nullable DocumentPosition position, String message) { super(message); this.position = position; } InvalidConfigurationPropertyTypeException( @Nullable DocumentPosition position, String message, @Nullable Throwable cause) { super(message, cause); this.position = position; } /** * @return The position of the property in the configuration document, or {@code null} if there is no position * available. */ @Nullable public DocumentPosition position() { return position; } @Override public String toString() { if (position == null) { return getMessage(); } return getMessage() + " (" + position + ")"; } } cava-0.6.0/config/src/main/java/net/consensys/cava/config/NoConfigurationPropertyException.java000066400000000000000000000017761341750772100327360ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.config; /** * An exception thrown when a requested configuration property is not found. * *

* This exception can be avoided by using a schema that provides a default value or asserts that a value has been * provided in the configuration. */ public final class NoConfigurationPropertyException extends RuntimeException { NoConfigurationPropertyException(String message) { super(message); } } cava-0.6.0/config/src/main/java/net/consensys/cava/config/PropertyValidator.java000066400000000000000000000163471341750772100277000ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.config; import static net.consensys.cava.config.ConfigurationErrors.noErrors; import static net.consensys.cava.config.ConfigurationErrors.singleError; import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; import javax.annotation.Nullable; /** * A validator associated with a specific configuration property. */ public interface PropertyValidator { /** * Returns a single validator that combines the results of several validators. * * @param first The first validator. * @param second The second validator. * @param The validator type. * @return A single validator that combines the results of evaluating the provided validators. */ static PropertyValidator combine(PropertyValidator first, PropertyValidator second) { return combine(Arrays.>asList(first, second)); } /** * Returns a single validator that combines the results of several validators. * * @param validators The validators to be evaluated. * @param The validator type. * @return A single validator that combines the results of evaluating the provided validators. */ static PropertyValidator combine(List> validators) { return (key, position, value) -> validators .stream() .flatMap(validator -> validator.validate(key, position, value).stream()) .collect(Collectors.toList()); } /** * A validator that applies a validator to all elements of list, if the list is present. * * @param validator The validator to apply to elements of the list. * @param The type of list elements. * @return A validator that applies a validator to all elements of list, if the list is present. */ static PropertyValidator> allInList(PropertyValidator validator) { return (key, position, value) -> { if (value != null) { return value.stream().flatMap(elem -> validator.validate(key, position, elem).stream()).collect( Collectors.toList()); } return noErrors(); }; } /** * A validator that ensures a property is present. * * @return A validator that ensures a property is present. */ static PropertyValidator isPresent() { return PropertyValidators.IS_PRESENT; } /** * A validator that ensures a property, if present, is within a long integer range. * * @param from The lower bound of the range (inclusive). * @param to The upper bound of the range (exclusive). * @return A validator that ensures a property, if present, is within an integer range. */ static PropertyValidator inRange(long from, long to) { return (key, position, value) -> { if (value != null && (value.longValue() < from || value.longValue() >= to)) { return singleError(position, "Value of property '" + key + "' is outside range [" + from + "," + to + ")"); } return noErrors(); }; } /** * A validator that ensures a property, if present, has a value within a given set. * * @param values The acceptable values. * @return A validator that ensures a property, if present, is within a given set. */ static PropertyValidator anyOf(String... values) { return anyOf(Arrays.asList(values), String::compareTo); } /** * A validator that ensures a property, if present, has a value within a given set. * * @param values The acceptable values. * @return A validator that ensures a property, if present, is within a given set. */ static PropertyValidator anyOf(Collection values) { return anyOf(values, String::compareTo); } /** * A validator that ensures a property, if present, has a value within a given set. * * @param values The acceptable values. * @return A validator that ensures a property, if present, is within a given set. */ static PropertyValidator anyOfIgnoreCase(String... values) { return anyOf(Arrays.asList(values), String::compareToIgnoreCase); } /** * A validator that ensures a property, if present, has a value within a given set. * * @param values The acceptable values. * @return A validator that ensures a property, if present, is within a given set. */ static PropertyValidator anyOfIgnoreCase(Collection values) { return anyOf(values, String::compareToIgnoreCase); } /** * A validator that ensures a property, if present, has a comparable value within a given set. * * @param values The acceptable values. * @param comparator A comparator between values. * @return A validator that ensures a property, if present, has a comparable value within a given set. */ static PropertyValidator anyOf(Collection values, Comparator comparator) { StringBuilder builder = new StringBuilder(); int count = values.size(); int i = 0; for (String value : values) { builder.append('"'); builder.append(value); builder.append('"'); if (i < (count - 2)) { builder.append(", "); } else if (i == (count - 2)) { if (count >= 3) { builder.append(','); } builder.append(" or "); } ++i; } String expected = builder.toString(); return (key, position, value) -> { if (value != null && values.stream().noneMatch(p -> comparator.compare(p, value) == 0)) { return singleError(position, "Value of property '" + key + "' should be " + expected); } return noErrors(); }; } /** * A validator that ensures a property, if present, is a well-formed URL. * * @return A validator that ensures a property, if present, is a well-formed URL. */ static PropertyValidator isURL() { return (key, position, value) -> { if (value != null) { try { new URL(value); } catch (MalformedURLException e) { return singleError(position, "Value of property '" + key + "' is not a valid URL", e); } } return noErrors(); }; } /** * Validate a configuration property. * * @param key The configuration property key. * @param position The position of the property in the input document, if supported. This should be used when * constructing errors. * @param value The value associated with the configuration entry. * @return A list of errors. If no errors are found, an empty list should be returned. */ List validate(String key, @Nullable DocumentPosition position, @Nullable T value); } cava-0.6.0/config/src/main/java/net/consensys/cava/config/PropertyValidators.java000066400000000000000000000020411341750772100300450ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.config; import static net.consensys.cava.config.ConfigurationErrors.noErrors; import static net.consensys.cava.config.ConfigurationErrors.singleError; final class PropertyValidators { private PropertyValidators() {} static final PropertyValidator IS_PRESENT = (key, position, value) -> { if (value == null) { return singleError(position, "Required property '" + key + "' is missing"); } return noErrors(); }; } cava-0.6.0/config/src/main/java/net/consensys/cava/config/Schema.java000066400000000000000000000335641341750772100254060ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.config; import static java.util.Objects.requireNonNull; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; import javax.annotation.Nullable; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; /** * A schema for a configuration, providing default values and validation rules. */ public final class Schema { static final Schema EMPTY = new Schema(Collections.emptyMap(), Collections.emptyMap(), ArrayListMultimap.create(), Collections.emptyList()); private final Map propertyDescriptions; private final Map propertyDefaults; private final ListMultimap> propertyValidators; private final List configurationValidators; Schema( Map propertyDescriptions, Map propertyDefaults, ListMultimap> propertyValidators, List configurationValidators) { this.propertyDescriptions = propertyDescriptions; this.propertyDefaults = propertyDefaults; this.propertyValidators = propertyValidators; this.configurationValidators = configurationValidators; } /** * The keys of all defaults provided by this schema. * * @return The keys for all defaults provided by this schema. */ public Set defaultsKeySet() { return propertyDefaults.keySet(); } /** * Get the description for a key. * * @param key A configuration key (e.g. {@code "server.address.hostname"}). * @return A description associated with the key, or null if no description is available. */ @Nullable public String description(String key) { requireNonNull(key); return propertyDescriptions.get(key); } /** * Check if a key has a default provided by this schema. * * @param key A configuration key (e.g. {@code "server.address.hostname"}). * @return {@code true} if this schema provides a default value for the key. */ public boolean hasDefault(String key) { requireNonNull(key); return propertyDefaults.containsKey(key); } /** * Get a default value from this configuration. * * @param key A configuration key (e.g. {@code "server.address.hostname"}). * @return The value, or null if no default was available. */ public Object getDefault(String key) { requireNonNull(key); return propertyDefaults.get(key); } /** * Get a default value from this configuration as a string. * * @param key A configuration key (e.g. {@code "server.address.hostname"}). * @return The value, or null if no default was available. * @throws InvalidConfigurationPropertyTypeException If the default value is not a string. */ @Nullable public String getDefaultString(String key) { requireNonNull(key); return getTypedDefault(key, String.class, "string"); } /** * Get a default value from this configuration as a integer. * * @param key A configuration key (e.g. {@code "server.address.port"}). * @return The value, or null if no default was available. * @throws InvalidConfigurationPropertyTypeException If the default value is not an integer. */ @Nullable public Integer getDefaultInteger(String key) { requireNonNull(key); Object obj = propertyDefaults.get(key); if (obj == null) { return null; } if (obj instanceof Integer) { return (Integer) obj; } if (obj instanceof Long) { Long longValue = (Long) obj; if (longValue > Integer.MAX_VALUE) { throw new InvalidConfigurationPropertyTypeException( null, "Value of property '" + key + "' is too large for an integer"); } return longValue.intValue(); } throw new InvalidConfigurationPropertyTypeException(null, "Property at '" + key + "' was not an integer"); } /** * Get a default value from this configuration as a long. * * @param key A configuration key (e.g. {@code "server.address.port"}). * @return The value, or null if no default was available. * @throws InvalidConfigurationPropertyTypeException If the default value is not a long. */ @Nullable public Long getDefaultLong(String key) { requireNonNull(key); Object obj = propertyDefaults.get(key); if (obj == null) { return null; } if (obj instanceof Long) { return (Long) obj; } if (obj instanceof Integer) { return ((Integer) obj).longValue(); } throw new InvalidConfigurationPropertyTypeException(null, "Property at '" + key + "' was not a long"); } /** * Get a default value from this configuration as a double. * * @param key A configuration key (e.g. {@code "server.priority"}). * @return The value, or null if no default was available. * @throws InvalidConfigurationPropertyTypeException If the default value is not a double. */ @Nullable public Double getDefaultDouble(String key) { requireNonNull(key); return getTypedDefault(key, Double.class, "double"); } /** * Get a default value from this configuration as a boolean. * * @param key A configuration key (e.g. {@code "server.active"}). * @return The value, or null if no default was available. * @throws InvalidConfigurationPropertyTypeException If the default value is not a boolean. */ @Nullable public Boolean getDefaultBoolean(String key) { requireNonNull(key); return getTypedDefault(key, Boolean.class, "boolean"); } /** * Get a default value from this configuration as a map. * * @param key A configuration key (e.g. {@code "server.active"}). * @return The value, or null if no default was available. * @throws InvalidConfigurationPropertyTypeException If the default value is not a map. */ @SuppressWarnings("unchecked") @Nullable public Map getDefaultMap(String key) { requireNonNull(key); return getTypedDefault(key, Map.class, "map"); } /** * Get a default value from this configuration as a list. * * @param key A configuration key (e.g. {@code "server.common_names"}). * @return The value, or null if no default was available. * @throws IllegalArgumentException If the key cannot be parsed. * @throws InvalidConfigurationPropertyTypeException If the default value is not a list. */ @SuppressWarnings("unchecked") @Nullable public List getDefaultList(String key) { requireNonNull(key); Object obj = propertyDefaults.get(key); if (obj == null) { return null; } if (obj instanceof List) { return (List) obj; } throw new InvalidConfigurationPropertyTypeException(null, "Property at '" + key + "' is not a list"); } /** * Get a default value from this configuration as a list of strings. * * @param key A configuration key (e.g. {@code "server.common_names"}). * @return The value, or null if no default was available. * @throws InvalidConfigurationPropertyTypeException If the default value is not a list of strings. */ @Nullable public List getDefaultListOfString(String key) { requireNonNull(key); return getListDefault(key, String.class, "strings"); } /** * Get a default value from this configuration as a list of integers. * * @param key A configuration key (e.g. {@code "server.address.ports"}). * @return The value, or null if no default was available. * @throws InvalidConfigurationPropertyTypeException If the default value is not a list of integers. */ @SuppressWarnings("unchecked") @Nullable public List getDefaultListOfInteger(String key) { requireNonNull(key); Object obj = propertyDefaults.get(key); if (obj == null) { return null; } if (obj instanceof List) { List list = (List) obj; if (list.isEmpty() || list.get(0) instanceof Integer) { return (List) list; } if (list.get(0) instanceof Long) { return IntStream.range(0, list.size()).mapToObj(i -> { Long longValue = (Long) list.get(i); if (longValue == null) { return null; } if (longValue > Integer.MAX_VALUE) { throw new InvalidConfigurationPropertyTypeException( null, "Value of property '" + key + "', index " + i + ", is too large for an integer"); } return longValue.intValue(); }).collect(Collectors.toList()); } } throw new InvalidConfigurationPropertyTypeException(null, "Property at '" + key + "' was not a list of integers"); } /** * Get a default value from this configuration as a list of longs. * * @param key A configuration key (e.g. {@code "server.address.ports"}). * @return The value, or null if no default was available. * @throws InvalidConfigurationPropertyTypeException If the default value is not a list of longs. */ @SuppressWarnings("unchecked") @Nullable public List getDefaultListOfLong(String key) { requireNonNull(key); Object obj = propertyDefaults.get(key); if (obj == null) { return null; } if (obj instanceof List) { List list = (List) obj; if (list.isEmpty() || list.get(0) instanceof Long) { return (List) list; } if (list.get(0) instanceof Integer) { return ((List) list).stream().map(i -> { if (i == null) { return null; } return i.longValue(); }).collect(Collectors.toList()); } } throw new InvalidConfigurationPropertyTypeException(null, "Property at '" + key + "' was not a list of longs"); } /** * Get a default value from this configuration as a list of doubles. * * @param key A configuration key (e.g. {@code "server.priorities"}). * @return The value, or null if no default was available. * @throws InvalidConfigurationPropertyTypeException If the default value is not a list of doubles. */ @Nullable public List getDefaultListOfDouble(String key) { requireNonNull(key); return getListDefault(key, Double.class, "doubles"); } /** * Get a default value from this configuration as a list of booleans. * * @param key A configuration key (e.g. {@code "server.flags"}). * @return The value, or null if no default was available. * @throws InvalidConfigurationPropertyTypeException If the default value is not a list of booleans. */ @Nullable public List getDefaultListOfBoolean(String key) { requireNonNull(key); return getListDefault(key, Boolean.class, "booleans"); } /** * Get a default value from this configuration as a list of maps. * * @param key A configuration key (e.g. {@code "mainnet.servers"}). * @return The value, or null if no default was available. * @throws InvalidConfigurationPropertyTypeException If the default value is not a list of maps. */ @Nullable public List> getDefaultListOfMap(String key) { requireNonNull(key); return getListDefault(key, Map.class, "maps"); } @Nullable private T getTypedDefault(String key, Class clazz, String typeName) { Object obj = propertyDefaults.get(key); if (obj == null) { return null; } if (clazz.isInstance(obj)) { return clazz.cast(obj); } throw new InvalidConfigurationPropertyTypeException(null, "Property at '" + key + "' was not a " + typeName); } @SuppressWarnings("unchecked") @Nullable private List getListDefault(String key, Class listType, String typeName) { Object obj = propertyDefaults.get(key); if (obj == null) { return null; } if (obj instanceof List) { List list = (List) obj; if (list.isEmpty() || listType.isInstance(list.get(0))) { return (List) list; } } throw new InvalidConfigurationPropertyTypeException( null, "Property at '" + key + "' was not a list of " + typeName); } /** * Validate a configuration against this schema. * *

* The validations are done incrementally as the stream is consumed. Use {@code .limit(...)} on the stream to control * the maximum number of validation errors to receive. * * @param configuration The configuration to validate. * @return A stream containing any errors encountered during validation. */ public Stream validate(Configuration configuration) { requireNonNull(configuration); Stream propertyErrors = propertyValidators.entries().stream().flatMap(e -> { String key = e.getKey(); PropertyValidator validator = e.getValue(); Object value = configuration.get(key); DocumentPosition position = configuration.inputPositionOf(key); List errors = validator.validate(key, position, value); if (errors == null) { return Stream.empty(); } return errors.stream(); }); Stream configErrors = configurationValidators.stream().flatMap(v -> { List errors = v.validate(configuration); if (errors == null) { return Stream.empty(); } return errors.stream(); }); return Stream.concat(propertyErrors, configErrors); } } cava-0.6.0/config/src/main/java/net/consensys/cava/config/SchemaBuilder.java000066400000000000000000000626531341750772100267160ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.config; import static java.util.Objects.requireNonNull; import static net.consensys.cava.config.Configuration.canonicalKey; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import javax.annotation.Nullable; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; /** * This interface allows customers to determine a schema to associate with a configuration to validate the entries read * from configuration files, and provide default values if no value is present in the configuration file. */ public final class SchemaBuilder { private final Map propertyDescriptions = new HashMap<>(); private final Map propertyDefaults = new HashMap<>(); private final ListMultimap> propertyValidators = ArrayListMultimap.create(); private final List configurationValidators = new ArrayList<>(); /** * Get a new builder for a schema. * * @return A new {@link SchemaBuilder}. */ public static SchemaBuilder create() { return new SchemaBuilder(); } SchemaBuilder() {} /** * Provide documentation for a property. * *

* Invoking this method with the same key as a previous invocation will replace the description for that key. * * @param key The configuration property key. * @param description The description to associate with the property. * @return This builder. * @throws IllegalArgumentException If the key cannot be parsed. */ public SchemaBuilder documentProperty(String key, String description) { requireNonNull(key); requireNonNull(description); propertyDescriptions.put(canonicalKey(key), description); return this; } /** * Provide a default value for a property. * *

* Invoking this method with the same key as a previous invocation will replace the default value for that key. * * @param key The configuration property key. * @param value The default value for the property. * @return This builder. * @throws IllegalArgumentException If the key cannot be parsed. */ public SchemaBuilder addDefault(String key, Object value) { requireNonNull(key); requireNonNull(value); propertyDefaults.put(canonicalKey(key), value); return this; } /** * Add a property validation to this schema. * *

* Multiple validators can be provided for the same key by invoking this method multiple times. * * @param key The configuration property key. * @param validator A validator for the property. * @return This builder. * @throws IllegalArgumentException If the key cannot be parsed. */ public SchemaBuilder validateProperty(String key, PropertyValidator validator) { requireNonNull(key); requireNonNull(validator); propertyValidators.put(canonicalKey(key), validator); return this; } /** * Add a configuration validator to the schema. * *

* Multiple validators can be provided by invoking this method multiple times. * * @param validator A configuration validator. * @return This builder. */ public SchemaBuilder validateConfiguration(ConfigurationValidator validator) { requireNonNull(validator); configurationValidators.add(validator); return this; } /** * Add a string property to the schema. * *

* Even if no {@code validator} is provided, the schema will validate that the configuration property, if present, * contains a string. * *

* If a {@code defaultValue} is provided, then the provided validator, if any, will only be invoked if the value is * present (i.e. it will not be provided a {@code null} value to validate). * * @param key The configuration property key. * @param defaultValue A default value for the property or null if no default is provided. * @param description The description to associate with this property, or null if no documentation is provided. * @param validator A validator for the property, or null if no validator is provided. * @return This builder. * @throws IllegalArgumentException If the key cannot be parsed. */ public SchemaBuilder addString( String key, @Nullable String defaultValue, @Nullable String description, @Nullable PropertyValidator validator) { requireNonNull(key); return addScalar(String.class, "string", key, defaultValue, description, validator); } /** * Add an integer property to the schema. * *

* Even if no {@code validator} is provided, the schema will validate that the configuration property, if present, * contains an integer. * *

* If a {@code defaultValue} is provided, then the provided validator, if any, will only be invoked if the value is * present (i.e. it will not be provided a {@code null} value to validate). * * @param key The configuration property key. * @param defaultValue A default value for the property or null if no default is provided. * @param description The description to associate with this property, or null if no documentation is provided. * @param validator A validator for the property, or null if no validator is provided. * @return This builder. * @throws IllegalArgumentException If the key cannot be parsed. */ public SchemaBuilder addInteger( String key, @Nullable Integer defaultValue, @Nullable String description, @Nullable PropertyValidator validator) { requireNonNull(key); if (defaultValue != null) { addDefault(key, defaultValue); } if (description != null) { documentProperty(key, description); } validateProperty(key, (canonicalKey, position, value) -> { if (!(value == null || value instanceof Integer || value instanceof Long)) { return Collections .singletonList(new ConfigurationError(position, "Property at '" + canonicalKey + "' requires an integer")); } if (validator == null || (defaultValue != null && value == null)) { return Collections.emptyList(); } Integer intValue; if (value instanceof Long) { if (((Long) value) > Integer.MAX_VALUE) { return Collections.singletonList( new ConfigurationError(position, "Value of property '" + canonicalKey + "' is too large for an integer")); } intValue = ((Long) value).intValue(); } else { intValue = (Integer) value; } return validator.validate(canonicalKey, position, intValue); }); return this; } /** * Add a long property to the schema. * *

* Even if no {@code validator} is provided, the schema will validate that the configuration property, if present, * contains a long. * *

* If a {@code defaultValue} is provided, then the provided validator, if any, will only be invoked if the value is * present (i.e. it will not be provided a {@code null} value to validate). * * @param key The configuration property key. * @param defaultValue A default value for the property or null if no default is provided. * @param description The description to associate with this property, or null if no documentation is provided. * @param validator A validator for the property, or null if no validator is provided. * @return This builder. * @throws IllegalArgumentException If the key cannot be parsed. */ public SchemaBuilder addLong( String key, @Nullable Long defaultValue, @Nullable String description, @Nullable PropertyValidator validator) { requireNonNull(key); if (defaultValue != null) { addDefault(key, defaultValue); } if (description != null) { documentProperty(key, description); } validateProperty(key, (vkey, position, value) -> { if (!(value == null || value instanceof Long || value instanceof Integer)) { return Collections .singletonList(new ConfigurationError(position, "Property at '" + vkey + "' requires a long")); } if (validator == null || (defaultValue != null && value == null)) { return Collections.emptyList(); } Long longValue; if (value instanceof Integer) { longValue = ((Integer) value).longValue(); } else { longValue = (Long) value; } return validator.validate(vkey, position, longValue); }); return this; } /** * Add a double property to the schema. * *

* Even if no {@code validator} is provided, the schema will validate that the configuration property, if present, * contains a double. * *

* If a {@code defaultValue} is provided, then the provided validator, if any, will only be invoked if the value is * present (i.e. it will not be provided a {@code null} value to validate). * * @param key The configuration property key. * @param defaultValue A default value for the property or null if no default is provided. * @param description The description to associate with this property, or null if no documentation is provided. * @param validator A validator for the property, or null if no validator is provided. * @return This builder. * @throws IllegalArgumentException If the key cannot be parsed. */ public SchemaBuilder addDouble( String key, @Nullable Double defaultValue, @Nullable String description, @Nullable PropertyValidator validator) { requireNonNull(key); return addScalar(Double.class, "double", key, defaultValue, description, validator); } /** * Add a boolean property to the schema. * *

* Even if no {@code validator} is provided, the schema will validate that the configuration property, if present, * contains a boolean. * *

* If a {@code defaultValue} is provided, then the provided validator, if any, will only be invoked if the value is * present (i.e. it will not be provided a {@code null} value to validate). * * @param key The configuration property key. * @param defaultValue A default value for the property or null if no default is provided. * @param description The description to associate with this property, or null if no documentation is provided. * @param validator A validator for the property, or null if no validator is provided. * @return This builder. * @throws IllegalArgumentException If the key cannot be parsed. */ public SchemaBuilder addBoolean( String key, @Nullable Boolean defaultValue, @Nullable String description, @Nullable PropertyValidator validator) { requireNonNull(key); return addScalar(Boolean.class, "boolean", key, defaultValue, description, validator); } /** * Add a list-of-strings property to the schema. * *

* Even if no {@code validator} is provided, the schema will validate that the configuration property, if present, * contains a list of strings without any null values. * *

* If a {@code defaultValue} is provided, then the provided validator, if any, will only be invoked if the value is * present (i.e. it will not be provided a {@code null} value to validate). * * @param key The configuration property key. * @param defaultValue A default value for the property or null if no default is provided. * @param description The description to associate with this property, or null if no documentation is provided. * @param validator A validator for the property, or null if no validator is provided. * @return This builder. * @throws IllegalArgumentException If the key cannot be parsed. */ public SchemaBuilder addListOfString( String key, @Nullable List defaultValue, @Nullable String description, @Nullable PropertyValidator> validator) { requireNonNull(key); return addList(String.class, "string", key, defaultValue, description, validator); } /** * Add a list-of-integers property to the schema. * *

* Even if no {@code validator} is provided, the schema will validate that the configuration property, if present, * contains a list of integers without any null values. * *

* If a {@code defaultValue} is provided, then the provided validator, if any, will only be invoked if the value is * present (i.e. it will not be provided a {@code null} value to validate). * * @param key The configuration property key. * @param defaultValue A default value for the property or null if no default is provided. * @param description The description to associate with this property, or null if no documentation is provided. * @param validator A validator for the property, or null if no validator is provided. * @return This builder. * @throws IllegalArgumentException If the key cannot be parsed. */ @SuppressWarnings("unchecked") public SchemaBuilder addListOfInteger( String key, @Nullable List defaultValue, @Nullable String description, @Nullable PropertyValidator> validator) { requireNonNull(key); if (defaultValue != null) { if (defaultValue.stream().anyMatch(Objects::isNull)) { throw new IllegalArgumentException("default value list contains null value(s)"); } addDefault(key, defaultValue); } if (description != null) { documentProperty(key, description); } validateProperty(key, (vkey, position, value) -> { if (value != null && !(value instanceof List)) { return Collections .singletonList(new ConfigurationError(position, "Property at '" + vkey + "' requires a list of integers")); } boolean containsLong = false; if (value != null) { List objs = (List) value; for (int i = 0; i < objs.size(); ++i) { Object obj = objs.get(i); if (obj == null) { return Collections.singletonList( new ConfigurationError(position, "Value of property '" + vkey + "', index " + i + ", is null")); } if (!(obj instanceof Integer) && !(obj instanceof Long)) { return Collections.singletonList( new ConfigurationError( position, "Value of property '" + vkey + "', index " + i + ", is not an integer")); } if (obj instanceof Long) { containsLong = true; if (((Long) obj) > Integer.MAX_VALUE) { return Collections.singletonList( new ConfigurationError( position, "Value of property '" + vkey + "', index " + i + ", is too large for an integer")); } } } } if (validator == null || (defaultValue != null && value == null)) { return Collections.emptyList(); } if (!containsLong) { return validator.validate(vkey, position, (List) value); } else { return validator.validate(vkey, position, ((List) value).stream().map(o -> { if (o instanceof Integer) { return (Integer) o; } assert (o instanceof Long); return ((Long) o).intValue(); }).collect(Collectors.toList())); } }); return this; } /** * Add a list-of-longs property to the schema. * *

* Even if no {@code validator} is provided, the schema will validate that the configuration property, if present, * contains a list of longs without any null values. * *

* If a {@code defaultValue} is provided, then the provided validator, if any, will only be invoked if the value is * present (i.e. it will not be provided a {@code null} value to validate). * * @param key The configuration property key. * @param defaultValue A default value for the property or null if no default is provided. * @param description The description to associate with this property, or null if no documentation is provided. * @param validator A validator for the property, or null if no validator is provided. * @return This builder. * @throws IllegalArgumentException If the key cannot be parsed. */ @SuppressWarnings("unchecked") public SchemaBuilder addListOfLong( String key, @Nullable List defaultValue, @Nullable String description, @Nullable PropertyValidator> validator) { requireNonNull(key); if (defaultValue != null) { if (defaultValue.stream().anyMatch(Objects::isNull)) { throw new IllegalArgumentException("default value list contains null value(s)"); } addDefault(key, defaultValue); } if (description != null) { documentProperty(key, description); } validateProperty(key, (canonicalKey, position, value) -> { if (value != null && !(value instanceof List)) { return Collections.singletonList( new ConfigurationError(position, "Property at '" + canonicalKey + "' requires a list of longs")); } boolean containsInteger = false; if (value != null) { List objs = (List) value; for (int i = 0; i < objs.size(); ++i) { Object obj = objs.get(i); if (obj == null) { return Collections.singletonList( new ConfigurationError(position, "Value of property '" + canonicalKey + "', index " + i + ", is null")); } if (!(obj instanceof Long) && !(obj instanceof Integer)) { return Collections.singletonList( new ConfigurationError( position, "Value of property '" + canonicalKey + "', index " + i + ", is not a long")); } containsInteger |= (obj instanceof Integer); } } if (validator == null || (defaultValue != null && value == null)) { return Collections.emptyList(); } if (!containsInteger) { return validator.validate(canonicalKey, position, (List) value); } else { return validator.validate(canonicalKey, position, ((List) value).stream().map(o -> { if (o instanceof Long) { return (Long) o; } assert (o instanceof Integer); return ((Integer) o).longValue(); }).collect(Collectors.toList())); } }); return this; } /** * Add a list-of-doubles property to the schema. * *

* Even if no {@code validator} is provided, the schema will validate that the configuration property, if present, * contains a list of doubles without any null values. * *

* If a {@code defaultValue} is provided, then the provided validator, if any, will only be invoked if the value is * present (i.e. it will not be provided a {@code null} value to validate). * * @param key The configuration property key. * @param defaultValue A default value for the property or null if no default is provided. * @param description The description to associate with this property, or null if no documentation is provided. * @param validator A validator for the property, or null if no validator is provided. * @return This builder. * @throws IllegalArgumentException If the key cannot be parsed. */ public SchemaBuilder addListOfDouble( String key, @Nullable List defaultValue, @Nullable String description, @Nullable PropertyValidator> validator) { requireNonNull(key); return addList(Double.class, "double", key, defaultValue, description, validator); } /** * Add a list-of-booleans property to the schema. * *

* Even if no {@code validator} is provided, the schema will validate that the configuration property, if present, * contains a list of booleans without any null values. * *

* If a {@code defaultValue} is provided, then the provided validator, if any, will only be invoked if the value is * present (i.e. it will not be provided a {@code null} value to validate). * * @param key The configuration property key. * @param defaultValue A default value for the property or null if no default is provided. * @param description The description to associate with this property, or null if no documentation is provided. * @param validator A validator for the property, or null if no validator is provided. * @return This builder. * @throws IllegalArgumentException If the key cannot be parsed. */ public SchemaBuilder addListOfBoolean( String key, @Nullable List defaultValue, @Nullable String description, @Nullable PropertyValidator> validator) { requireNonNull(key); return addList(Boolean.class, "boolean", key, defaultValue, description, validator); } /** * Add a list-of-maps property to the schema. * *

* Even if no {@code validator} is provided, the schema will validate that the configuration property, if present, * contains a list of maps without any null values. * *

* If a {@code defaultValue} is provided, then the provided validator, if any, will only be invoked if the value is * present (i.e. it will not be provided a {@code null} value to validate). * * @param key The configuration property key. * @param defaultValue A default value for the property or null if no default is provided. * @param description The description to associate with this property, or null if no documentation is provided. * @param validator A validator for the property, or null if no validator is provided. * @return This builder. * @throws IllegalArgumentException If the key cannot be parsed. */ public SchemaBuilder addListOfMap( String key, @Nullable List> defaultValue, @Nullable String description, @Nullable PropertyValidator>> validator) { requireNonNull(key); return addList(Map.class, "map", key, defaultValue, description, validator); } /** * Return the {@link Schema} constructed by this builder. * * @return A {@link Schema}. */ public Schema toSchema() { return new Schema(propertyDescriptions, propertyDefaults, propertyValidators, configurationValidators); } private SchemaBuilder addScalar( Class clazz, String typeName, String key, @Nullable T defaultValue, @Nullable String description, @Nullable PropertyValidator validator) { if (defaultValue != null) { addDefault(key, defaultValue); } if (description != null) { documentProperty(key, description); } validateProperty(key, (canonicalKey, position, value) -> { if (value != null && !clazz.isInstance(value)) { return Collections.singletonList( new ConfigurationError(position, "Property at '" + canonicalKey + "' requires a " + typeName)); } if (validator == null || (defaultValue != null && value == null)) { return Collections.emptyList(); } return validator.validate(canonicalKey, position, clazz.cast(value)); }); return this; } @SuppressWarnings("unchecked") private SchemaBuilder addList( Class innerClass, String typeName, String key, @Nullable List defaultValue, @Nullable String description, @Nullable PropertyValidator> validator) { if (defaultValue != null) { if (defaultValue.stream().anyMatch(Objects::isNull)) { throw new IllegalArgumentException("default value list contains null value(s)"); } addDefault(key, defaultValue); } if (description != null) { documentProperty(key, description); } validateProperty(key, (canonicalKey, position, value) -> { if (value != null && !(value instanceof List)) { return Collections.singletonList( new ConfigurationError( position, "Property at '" + canonicalKey + "' requires a list of " + typeName + "s")); } if (value != null) { List objs = (List) value; for (int i = 0; i < objs.size(); ++i) { Object obj = objs.get(i); if (obj == null) { return Collections.singletonList( new ConfigurationError(position, "Value of property '" + canonicalKey + "', index " + i + ", is null")); } if (!innerClass.isInstance(obj)) { return Collections.singletonList( new ConfigurationError( position, "Value of property '" + canonicalKey + "', index " + i + ", is not a " + typeName)); } } } if (validator == null || (defaultValue != null && value == null)) { return Collections.emptyList(); } return validator.validate(canonicalKey, position, (List) value); }); return this; } } cava-0.6.0/config/src/main/java/net/consensys/cava/config/TomlBackedConfiguration.java000066400000000000000000000223111341750772100307270ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.config; import static net.consensys.cava.toml.Toml.joinKeyPath; import static net.consensys.cava.toml.Toml.parseDottedKey; import net.consensys.cava.toml.TomlArray; import net.consensys.cava.toml.TomlInvalidTypeException; import net.consensys.cava.toml.TomlParseResult; import net.consensys.cava.toml.TomlPosition; import net.consensys.cava.toml.TomlTable; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.IntStream; import javax.annotation.Nullable; final class TomlBackedConfiguration implements Configuration { private final TomlTable toml; private final Schema schema; private final List errors; TomlBackedConfiguration(TomlParseResult toml, @Nullable Schema schema) { List errors = new ArrayList<>(); toml.errors().forEach( err -> errors.add(new ConfigurationError(documentPosition(err.position()), err.getMessage(), err))); if (schema != null) { schema.validate(new TomlBackedConfiguration(toml, null)).forEach(errors::add); } else { schema = Schema.EMPTY; } this.toml = toml; this.schema = schema; this.errors = errors; } @Override public List errors() { return errors; } @Override public void toToml(Appendable appendable) throws IOException { new TomlSerializer(this, schema).writeTo(appendable); } @Override public Set keySet() { Set keys = new HashSet<>(); keys.addAll(toml.dottedKeySet()); keys.addAll(schema.defaultsKeySet()); return keys; } @Override public boolean contains(String key) { return toml.contains(key) || schema.hasDefault(key); } @Nullable @Override public Object get(String key) { Object obj = toml.get(key); if (obj != null) { if (obj instanceof TomlArray) { return deepToList((TomlArray) obj); } if (obj instanceof TomlTable) { return deepToMap((TomlTable) obj); } return obj; } return schema.getDefault(key); } @Nullable @Override public DocumentPosition inputPositionOf(String key) { TomlPosition position = toml.inputPositionOf(key); if (position == null) { return null; } return documentPosition(position); } private DocumentPosition inputPositionOf(List keyPath) { TomlPosition position = toml.inputPositionOf(keyPath); if (position == null) { return null; } return documentPosition(position); } @Override public String getString(String key) { return getValue(key, toml::getString, schema::getDefaultString); } @Override public int getInteger(String key) { return getValue(key, keyPath -> { Long longValue = toml.getLong(keyPath); if (longValue != null && longValue > Integer.MAX_VALUE) { throw new InvalidConfigurationPropertyTypeException( inputPositionOf(keyPath), "Value of property '" + joinKeyPath(keyPath) + "' is too large for an integer"); } return (longValue != null) ? longValue.intValue() : null; }, schema::getDefaultInteger); } @Override public long getLong(String key) { return getValue(key, toml::getLong, schema::getDefaultLong); } @Override public double getDouble(String key) { return getValue(key, toml::getDouble, schema::getDefaultDouble); } @Override public boolean getBoolean(String key) { return getValue(key, toml::getBoolean, schema::getDefaultBoolean); } @Override public Map getMap(String key) { return getValue(key, keyPath -> { TomlTable table = toml.getTable(keyPath); if (table == null) { return null; } return deepToMap(table); }, schema::getDefaultMap); } @Override public List getList(String key) { return getValue(key, keyPath -> { TomlArray array = toml.getArray(keyPath); if (array == null) { return null; } return deepToList(array); }, schema::getDefaultList); } @Override public List getListOfString(String key) { return getList(key, "strings", TomlArray::containsStrings, schema::getDefaultListOfString); } @Override public List getListOfInteger(String key) { return getValue(key, keyPath -> { TomlArray array = toml.getArray(keyPath); if (array == null) { return null; } if (!array.containsLongs()) { throw new InvalidConfigurationPropertyTypeException( inputPositionOf(keyPath), "List property '" + joinKeyPath(keyPath) + "' does not contain integers"); } @SuppressWarnings("unchecked") List longList = (List) (List) array.toList(); return IntStream.range(0, longList.size()).mapToObj(i -> { Long value = longList.get(i); if (value > Integer.MAX_VALUE) { throw new InvalidConfigurationPropertyTypeException( inputPositionOf(keyPath), "Value of property '" + joinKeyPath(keyPath) + "', index " + i + ", is too large for an integer"); } return value.intValue(); }).collect(Collectors.toList()); }, schema::getDefaultListOfInteger); } @Override public List getListOfLong(String key) { return getList(key, "longs", TomlArray::containsLongs, schema::getDefaultListOfLong); } @Override public List getListOfDouble(String key) { return getList(key, "doubles", TomlArray::containsDoubles, schema::getDefaultListOfDouble); } @Override public List getListOfBoolean(String key) { return getList(key, "booleans", TomlArray::containsBooleans, schema::getDefaultListOfBoolean); } @Override public List> getListOfMap(String key) { return getValue(key, keyPath -> { TomlArray array = toml.getArray(keyPath); if (array == null) { return null; } if (!array.containsTables()) { throw new InvalidConfigurationPropertyTypeException( inputPositionOf(keyPath), "List property '" + joinKeyPath(keyPath) + "' does not contain maps"); } @SuppressWarnings("unchecked") List> typedList = (List>) (List) deepToList(array); return typedList; }, schema::getDefaultListOfMap); } private DocumentPosition documentPosition(TomlPosition position) { return DocumentPosition.positionAt(position.line(), position.column()); } private T getValue(String key, Function, T> tomlGet, Function defaultGet) { List keyPath = parseDottedKey(key); T value; try { value = tomlGet.apply(keyPath); } catch (TomlInvalidTypeException e) { throw new InvalidConfigurationPropertyTypeException(inputPositionOf(keyPath), e.getMessage(), e); } if (value != null) { return value; } String canonicalKey = joinKeyPath(keyPath); value = defaultGet.apply(canonicalKey); if (value != null) { return value; } throw new NoConfigurationPropertyException("No value for property '" + canonicalKey + "'"); } private List getList( String key, String typeName, Predicate tomlCheck, Function> defaultGet) { return getValue(key, keyPath -> { TomlArray array = toml.getArray(keyPath); if (array == null) { return null; } if (!tomlCheck.test(array)) { throw new InvalidConfigurationPropertyTypeException( inputPositionOf(keyPath), "List property '" + joinKeyPath(keyPath) + "' does not contain " + typeName); } @SuppressWarnings("unchecked") List typedList = (List) array.toList(); return typedList; }, defaultGet); } private static List deepToList(TomlArray array) { return array.toList().stream().map(o -> { if (o instanceof TomlArray) { return deepToList((TomlArray) o); } if (o instanceof TomlTable) { return deepToMap((TomlTable) o); } return o; }).collect(Collectors.toList()); } private static Map deepToMap(TomlTable table) { return table.toMap().entrySet().stream().collect(Collectors.toMap(Entry::getKey, e -> { Object o = e.getValue(); if (o instanceof TomlArray) { return deepToList((TomlArray) o); } if (o instanceof TomlTable) { return deepToMap((TomlTable) o); } return o; })); } } cava-0.6.0/config/src/main/java/net/consensys/cava/config/TomlSerializer.java000066400000000000000000000142671341750772100271520ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.config; import static net.consensys.cava.config.Configuration.canonicalKey; import net.consensys.cava.toml.Toml; import java.io.IOException; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import com.google.common.base.Splitter; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; final class TomlSerializer { private static final Splitter LINE_SPLITTER = Splitter.onPattern("\\r?\\n"); private final Configuration configuration; private final Schema schema; // tableKey -> configKey private final ListMultimap tableMap; // configKey -> leafName private final Map keyMap; TomlSerializer(Configuration configuration, Schema schema) { ListMultimap tableMap = ArrayListMultimap.create(); Map keyMap = new HashMap<>(); configuration.keySet().forEach(configKey -> { List keyPath; try { keyPath = Toml.parseDottedKey(configKey); } catch (IllegalArgumentException e) { throw new IllegalStateException("Configuration key '" + configKey + "' is not valid in TOML"); } String tableKey = Toml.joinKeyPath(keyPath.subList(0, keyPath.size() - 1)); tableMap.put(tableKey, configKey); keyMap.put(configKey, keyPath.get(keyPath.size() - 1)); }); this.configuration = configuration; this.schema = schema; this.tableMap = tableMap; this.keyMap = keyMap; } void writeTo(Appendable appendable) throws IOException { List tableKeys = tableMap.keySet().stream().sorted().collect(Collectors.toList()); for (Iterator iterator = tableKeys.iterator(); iterator.hasNext();) { String tableKey = iterator.next(); if (!tableKey.isEmpty()) { writeDocumentation(tableKey, appendable); } if (!tableKey.isEmpty()) { appendable.append('['); appendable.append(tableKey); appendable.append(']'); appendable.append(System.lineSeparator()); } List configKeys = tableMap.get(tableKey); configKeys.sort(Comparator.naturalOrder()); for (String configKey : configKeys) { String leafName = keyMap.get(configKey); Object obj = configuration.get(configKey); if (obj == null) { throw new IllegalStateException("Configuration key '" + configKey + "' was unexpectedly null"); } Object defaultValue = schema.getDefault(configKey); if (obj instanceof Integer) { obj = ((Integer) obj).longValue(); } if (defaultValue instanceof Integer) { defaultValue = ((Integer) defaultValue).longValue(); } writeDocumentation(configKey, appendable); String leafKey = Toml.joinKeyPath(Collections.singletonList(leafName)); if (defaultValue != null) { appendable.append('#'); appendable.append(leafKey); appendable.append(" = "); writeValue(defaultValue, appendable); appendable.append(System.lineSeparator()); } if (!obj.equals(defaultValue)) { appendable.append(leafKey); appendable.append(" = "); writeValue(obj, appendable); appendable.append(System.lineSeparator()); } } if (iterator.hasNext()) { appendable.append(System.lineSeparator()); } } } private void writeValue(Object obj, Appendable appendable) throws IOException { if (obj instanceof String) { appendable.append('\"'); appendable.append(Toml.tomlEscape((String) obj)); appendable.append('\"'); } else if (obj instanceof List) { @SuppressWarnings("unchecked") List list = (List) obj; writeList(list, appendable); } else if (obj instanceof Map) { @SuppressWarnings("unchecked") Map map = (Map) obj; writeMap(map, appendable); } else { appendable.append(obj.toString()); } } private void writeList(List list, Appendable appendable) throws IOException { appendable.append('['); for (Iterator iterator = list.iterator(); iterator.hasNext();) { Object obj = iterator.next(); if (obj == null) { throw new IllegalStateException("Unexpected null in list property"); } writeValue(obj, appendable); if (iterator.hasNext()) { appendable.append(", "); } } appendable.append(']'); } private void writeMap(Map map, Appendable appendable) throws IOException { appendable.append('{'); for (Iterator iterator = map.keySet().stream().sorted().iterator(); iterator.hasNext();) { String key = iterator.next(); Object obj = map.get(key); if (obj == null) { throw new IllegalStateException("Unexpected null in map property"); } appendable.append(Toml.joinKeyPath(Collections.singletonList(key))); appendable.append(" = "); writeValue(obj, appendable); if (iterator.hasNext()) { appendable.append(", "); } } appendable.append('}'); } private void writeDocumentation(String key, Appendable appendable) throws IOException { String description = schema.description(canonicalKey(key)); if (description == null) { return; } for (String line : LINE_SPLITTER.split(description)) { appendable.append("## "); appendable.append(line); appendable.append(System.lineSeparator()); } } } cava-0.6.0/config/src/main/java/net/consensys/cava/config/package-info.java000066400000000000000000000005561341750772100265250ustar00rootroot00000000000000/** * 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). */ @ParametersAreNonnullByDefault package net.consensys.cava.config; import javax.annotation.ParametersAreNonnullByDefault; cava-0.6.0/config/src/test/000077500000000000000000000000001341750772100154515ustar00rootroot00000000000000cava-0.6.0/config/src/test/java/000077500000000000000000000000001341750772100163725ustar00rootroot00000000000000cava-0.6.0/config/src/test/java/net/000077500000000000000000000000001341750772100171605ustar00rootroot00000000000000cava-0.6.0/config/src/test/java/net/consensys/000077500000000000000000000000001341750772100212045ustar00rootroot00000000000000cava-0.6.0/config/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100221165ustar00rootroot00000000000000cava-0.6.0/config/src/test/java/net/consensys/cava/config/000077500000000000000000000000001341750772100233635ustar00rootroot00000000000000cava-0.6.0/config/src/test/java/net/consensys/cava/config/PropertyValidatorTest.java000066400000000000000000000076441341750772100305730ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.config; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; class PropertyValidatorTest { @Test void combinedValidatorEvaluatesBoth() { PropertyValidator combined = PropertyValidator.combine(PropertyValidator.isPresent(), PropertyValidator.inRange(1, 5)); assertTrue(combined.validate("foo", null, 2).isEmpty()); List whenNull = combined.validate("foo", null, null); assertEquals(1, whenNull.size()); assertTrue(whenNull.get(0).getMessage().contains("is missing")); List whenOutOfRange = combined.validate("foo", null, 10); assertEquals(1, whenOutOfRange.size()); assertTrue(whenOutOfRange.get(0).getMessage().contains("is outside range")); } @Test void validatesAllElementsInList() { PropertyValidator> allInList = PropertyValidator.allInList(PropertyValidator.inRange(1, 5)); assertTrue(allInList.validate("foo", null, Arrays.asList(1, 2, 3, 4)).isEmpty()); List oneError = allInList.validate("foo", null, Arrays.asList(1, 10, 3, 4)); assertEquals(1, oneError.size()); assertTrue(oneError.get(0).getMessage().contains("is outside range")); List twoErrors = allInList.validate("foo", null, Arrays.asList(1, 10, 30, 4)); assertEquals(2, twoErrors.size()); assertTrue(twoErrors.get(0).getMessage().contains("is outside range")); assertTrue(twoErrors.get(1).getMessage().contains("is outside range")); } @Test void validatesURLs() { PropertyValidator urlValidator = PropertyValidator.isURL(); assertTrue(urlValidator.validate("foo", null, "http://127.0.0.1:5678/bar").isEmpty()); List errors = urlValidator.validate("foo", null, "abcdefg"); assertEquals(1, errors.size()); assertTrue(errors.get(0).getMessage().contains("not a valid URL")); } @Test void validatesInSet() { PropertyValidator inSetValidator = PropertyValidator.anyOf("one", "two", "three "); assertTrue(inSetValidator.validate("foo", null, "one").isEmpty()); assertTrue(inSetValidator.validate("foo", null, "two").isEmpty()); assertTrue(inSetValidator.validate("foo", null, "three ").isEmpty()); assertEquals(1, inSetValidator.validate("foo", null, "three").size()); List errors = inSetValidator.validate("foo", null, "foobar"); assertEquals(1, errors.size()); assertEquals("Value of property 'foo' should be \"one\", \"two\", or \"three \"", errors.get(0).getMessage()); } @Test void validatesInSetIgnoreCase() { PropertyValidator inSetValidator = PropertyValidator.anyOfIgnoreCase("one", "two", "three "); assertTrue(inSetValidator.validate("foo", null, "OnE").isEmpty()); assertTrue(inSetValidator.validate("foo", null, "TWo").isEmpty()); assertTrue(inSetValidator.validate("foo", null, "THree ").isEmpty()); assertEquals(1, inSetValidator.validate("foo", null, "three").size()); List errors = inSetValidator.validate("foo", null, "foobar"); assertEquals(1, errors.size()); assertEquals("Value of property 'foo' should be \"one\", \"two\", or \"three \"", errors.get(0).getMessage()); } } cava-0.6.0/config/src/test/java/net/consensys/cava/config/SchemaBuilderTest.java000066400000000000000000000061521341750772100276010ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.config; import static net.consensys.cava.config.ConfigurationErrors.noErrors; import static net.consensys.cava.config.ConfigurationErrors.singleError; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Arrays; import java.util.Collections; import org.junit.jupiter.api.Test; class SchemaBuilderTest { @Test void shouldThrowForDefaultListContainingNulls() { SchemaBuilder schemaBuilder = new SchemaBuilder(); assertThrows( IllegalArgumentException.class, () -> schemaBuilder.addListOfString("strings", Arrays.asList("a", null, "b"), null, null)); assertThrows( IllegalArgumentException.class, () -> schemaBuilder.addListOfInteger("ints", Arrays.asList(null, 1, 2), null, null)); assertThrows( IllegalArgumentException.class, () -> schemaBuilder.addListOfLong("longs", Arrays.asList(1L, 2L, null), null, null)); assertThrows( IllegalArgumentException.class, () -> schemaBuilder.addListOfDouble("doubles", Arrays.asList(1.0, 2.0, 3.0, null), null, null)); assertThrows( IllegalArgumentException.class, () -> schemaBuilder.addListOfBoolean("bools", Arrays.asList(true, null, false), null, null)); assertThrows( IllegalArgumentException.class, () -> schemaBuilder .addListOfMap("maps", Arrays.asList(Collections.emptyMap(), null, Collections.emptyMap()), null, null)); } @Test void validateListOfStrings() { SchemaBuilder schemaBuilder = new SchemaBuilder(); schemaBuilder.addListOfString("key", Collections.emptyList(), "Some description here", (key, pos, value) -> { if (value.size() == 2 && value.get(0).startsWith("no")) { return singleError("This won't work out"); } return noErrors(); }); Configuration config = Configuration.fromToml("key=[\"no\",\"yes\"]", schemaBuilder.toSchema()); assertEquals(1, config.errors().size()); } @Test void validateListOfMaps() { SchemaBuilder schemaBuilder = new SchemaBuilder(); schemaBuilder.addListOfMap("key", Collections.emptyList(), "Some description here", (key, pos, value) -> { if (value.size() == 2 && value.get(0).size() == 1 && (Long) value.get(0).get("a") == 1L) { return singleError("This won't work out"); } return noErrors(); }); Configuration config = Configuration.fromToml("key=[{a = 1},{a = 2}]", schemaBuilder.toSchema()); assertEquals(1, config.errors().size()); } } cava-0.6.0/config/src/test/java/net/consensys/cava/config/TomlBackedConfigurationTest.java000066400000000000000000000517571341750772100316420ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.config; import static net.consensys.cava.config.ConfigurationErrors.noErrors; import static net.consensys.cava.config.ConfigurationErrors.singleError; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.nio.file.NoSuchFileException; import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import org.opentest4j.AssertionFailedError; class TomlBackedConfigurationTest { @Test void loadSimpleConfigFile() throws Exception { // @formatter:off Configuration config = Configuration.fromToml( "foo = \"12\"\n" + "bar = 13\n" + "foobar = 156.34\n" + "amaps = [{a = 1, b = 2}, {a = 'hello'}]\n" + "\n" + "[boo]\n" + "baz=[1,2,3]\n" + "\n" + "[amap]\n" + "a=1\n" + "b=2\n" + "[deepmap]\n" + "a=4\n" + "[deepmap.deeper]\n" + "b=5\n" ); // @formatter:on assertFalse(config.hasErrors()); assertEquals("12", config.getString("foo")); assertEquals(13, config.getInteger("bar")); assertEquals(13L, config.getLong("bar")); assertEquals(156.34d, config.getDouble("foobar")); assertEquals(Arrays.asList(1, 2, 3), config.getListOfInteger("boo.baz")); assertEquals(Arrays.asList(1L, 2L, 3L), config.getListOfLong("boo.baz")); assertEquals(Arrays.asList(1L, 2L, 3L), config.getList("boo.baz")); Map expectedMap = new HashMap<>(); expectedMap.put("a", 1L); expectedMap.put("b", 2L); assertEquals(expectedMap, config.getMap("amap")); assertEquals(Collections.singletonMap("baz", Arrays.asList(1L, 2L, 3L)), config.getMap("boo")); Map expectedDeepMap = new HashMap<>(); expectedDeepMap.put("a", 4L); expectedDeepMap.put("deeper", Collections.singletonMap("b", 5L)); assertEquals(expectedDeepMap, config.getMap("deepmap")); List> expectedList = Arrays.asList(expectedMap, Collections.singletonMap("a", "hello")); assertEquals(expectedList, config.getListOfMap("amaps")); } @Test void testKeyPresent() throws Exception { Configuration config = Configuration.fromToml("foo=\"12\"\nbar=\"13\"\n[baz]\nfoobar = 156.34"); assertFalse(config.hasErrors()); assertEquals(new HashSet<>(Arrays.asList("foo", "bar", "baz.foobar")), config.keySet()); assertTrue(config.contains("foo")); assertTrue(config.contains("bar")); assertTrue(config.contains("baz.foobar")); assertFalse(config.contains("example")); } @Test void testFindsValuesBasedOnCanonicalKey() throws Exception { Configuration config = Configuration.fromToml("foo=12\nbar=\"13\"\n[baz]\nfoobar = 156.34"); assertFalse(config.hasErrors()); assertEquals(new HashSet<>(Arrays.asList("foo", "bar", "baz.foobar")), config.keySet()); assertEquals(12, config.getInteger("foo")); assertEquals(12, config.getInteger(" foo ")); assertEquals(12, config.getInteger(" 'foo' ")); assertEquals(12, config.getInteger(" \"foo\" ")); assertTrue(config.contains("baz.foobar")); assertTrue(config.contains("baz . foobar")); assertTrue(config.contains("baz . 'foobar'")); assertTrue(config.contains("\"baz\" . 'foobar'")); } @Test void throwsForMissingValue() throws Exception { Configuration config = Configuration.fromToml("foo=12\nbar=\"13\"\n[baz]\nfoobar = 156.34"); assertFalse(config.hasErrors()); Exception e = assertThrows(NoConfigurationPropertyException.class, () -> config.getString("foo.blah")); assertEquals("No value for property 'foo.blah'", e.getMessage()); e = assertThrows(NoConfigurationPropertyException.class, () -> config.getInteger(" foobaz ")); assertEquals("No value for property 'foobaz'", e.getMessage()); } @Test void throwsForInvalidType() throws Exception { Configuration config = Configuration.fromToml( "[foo]\n" + "bar=99\n" + "baz='buz'\n" + "biz=false\n" + "buz = [1,2,3]\n" + "sbuz = ['1', '2', '3']\n"); assertFalse(config.hasErrors()); assertEquals(99, config.getInteger("foo.bar")); InvalidConfigurationPropertyTypeException e = assertThrows(InvalidConfigurationPropertyTypeException.class, () -> config.getString("foo.bar")); assertEquals("Value of 'foo.bar' is a integer", e.getMessage()); assertEquals(DocumentPosition.positionAt(2, 1), e.position()); assertEquals("buz", config.getString("foo.baz")); e = assertThrows(InvalidConfigurationPropertyTypeException.class, () -> config.getDouble("foo.baz")); assertEquals("Value of 'foo.baz' is a string", e.getMessage()); assertEquals(DocumentPosition.positionAt(3, 1), e.position()); assertFalse(config.getBoolean("foo.biz")); e = assertThrows(InvalidConfigurationPropertyTypeException.class, () -> config.getLong("foo.biz")); assertEquals("Value of 'foo.biz' is a boolean", e.getMessage()); assertEquals(DocumentPosition.positionAt(4, 1), e.position()); e = assertThrows(InvalidConfigurationPropertyTypeException.class, () -> config.getLong("foo")); assertEquals("Value of 'foo' is a table", e.getMessage()); assertEquals(DocumentPosition.positionAt(1, 1), e.position()); e = assertThrows(InvalidConfigurationPropertyTypeException.class, () -> config.getListOfString("foo.buz")); assertEquals("List property 'foo.buz' does not contain strings", e.getMessage()); assertEquals(DocumentPosition.positionAt(5, 1), e.position()); e = assertThrows(InvalidConfigurationPropertyTypeException.class, () -> config.getListOfInteger("foo.sbuz")); assertEquals("List property 'foo.sbuz' does not contain integers", e.getMessage()); assertEquals(DocumentPosition.positionAt(6, 1), e.position()); e = assertThrows(InvalidConfigurationPropertyTypeException.class, () -> config.getListOfMap("foo.sbuz")); assertEquals("List property 'foo.sbuz' does not contain maps", e.getMessage()); assertEquals(DocumentPosition.positionAt(6, 1), e.position()); } @Test void throwsForImpossibleIntegerConversion() throws Exception { // @formatter:off Configuration config = Configuration.fromToml( "[foo]\n" + "\" bar\" = " + (1L + Integer.MAX_VALUE) + "\n" + "buz = [1, 2, " + (1L + Integer.MAX_VALUE) + ", 4]\n" ); // @formatter:on assertFalse(config.hasErrors()); Exception e = assertThrows(InvalidConfigurationPropertyTypeException.class, () -> config.getInteger("foo.' bar'")); assertEquals("Value of property 'foo.\" bar\"' is too large for an integer", e.getMessage()); e = assertThrows(InvalidConfigurationPropertyTypeException.class, () -> config.getListOfInteger("foo.buz")); assertEquals("Value of property 'foo.buz', index 2, is too large for an integer", e.getMessage()); } @Test void loadMissingFile() { assertThrows(NoSuchFileException.class, () -> { Configuration.fromToml(Paths.get("FileThatDoesntExist")); }); } @Test void invalidTOMLFile() throws Exception { Configuration config = Configuration.fromToml("foo=\"12\"\nfoobar = \"156.34"); assertTrue(config.hasErrors()); assertEquals("Unexpected end of input, expected \" or a character", config.errors().get(0).getMessage()); assertEquals(DocumentPosition.positionAt(2, 17), config.errors().get(0).position()); assertEquals("12", config.getString("foo")); } @Test void getDefaultValue() throws Exception { SchemaBuilder builder = SchemaBuilder.create(); builder.addString("foo", "goodbye", null, null); Schema schema = builder.toSchema(); Configuration config = Configuration.fromToml("foobar = 'hello'", schema); assertEquals("hello", config.getString("foobar")); assertTrue(config.contains("foo")); assertEquals("goodbye", config.getString("foo")); } @Test void keysContainSchemaKeys() throws Exception { SchemaBuilder builder = SchemaBuilder.create(); builder.addString("foo", "bar", null, null); builder.addString("foo.bar", "buz", null, null); Schema schema = builder.toSchema(); Configuration config = Configuration.fromToml("foobar = 'hello'", schema); assertEquals(new HashSet<>(Arrays.asList("foo", "foo.bar", "foobar")), config.keySet()); } @Test void validateConfiguration() throws Exception { SchemaBuilder builder = SchemaBuilder.create(); builder.validateConfiguration(config -> { if (config.getInteger("expenses") > config.getInteger("revenue")) { return singleError("Expenses cannot be larger than revenue"); } return noErrors(); }); Schema schema = builder.toSchema(); Configuration config = Configuration.fromToml("expenses = 2000\nrevenue = 1500\n", schema); assertTrue(config.hasErrors()); assertEquals("Expenses cannot be larger than revenue", config.errors().get(0).getMessage()); } @Test void validateConfigurationProperty() throws Exception { SchemaBuilder builder = SchemaBuilder.create(); builder.addString("foo", "foobar", null, (key, position, value) -> { if ("bar".equals(value)) { return singleError(position, "No bar allowed"); } return noErrors(); }); Schema schema = builder.toSchema(); Configuration config = Configuration.fromToml(" foo = \"bar\"", schema); assertTrue(config.hasErrors()); ConfigurationError error = config.errors().get(0); assertEquals("No bar allowed", error.getMessage()); assertEquals(DocumentPosition.positionAt(1, 2), error.position()); } @Test void validateIntegerProperty() throws Exception { SchemaBuilder builder = SchemaBuilder.create(); builder.addInteger("foo", null, null, (key, position, value) -> { throw new AssertionFailedError("should not be reached"); }); Schema schema = builder.toSchema(); Configuration config = Configuration.fromToml(" foo = " + (1L + Integer.MAX_VALUE) + "\n", schema); assertTrue(config.hasErrors()); ConfigurationError error = config.errors().get(0); assertEquals("Value of property 'foo' is too large for an integer", error.getMessage()); assertEquals(DocumentPosition.positionAt(1, 2), error.position()); } @Test void shouldValidateStringList() throws Exception { SchemaBuilder builder = SchemaBuilder.create(); builder.addListOfString("foo", null, null, (key, position, value) -> { throw new AssertionFailedError("should not be reached"); }); Schema schema = builder.toSchema(); Configuration config = Configuration.fromToml(" foo = [1, 2]\n", schema); assertTrue(config.hasErrors()); ConfigurationError error = config.errors().get(0); assertEquals("Value of property 'foo', index 0, is not a string", error.getMessage()); assertEquals(DocumentPosition.positionAt(1, 2), error.position()); } @Test void shouldValidateIntegerList() throws Exception { SchemaBuilder builder = SchemaBuilder.create(); builder.addListOfInteger("foo", null, null, (key, position, value) -> { throw new AssertionFailedError("should not be reached"); }); Schema schema = builder.toSchema(); Configuration config = Configuration.fromToml(" foo = ['a', 'b']\n", schema); assertTrue(config.hasErrors()); ConfigurationError error = config.errors().get(0); assertEquals("Value of property 'foo', index 0, is not an integer", error.getMessage()); assertEquals(DocumentPosition.positionAt(1, 2), error.position()); } @Test void shouldValidateLongListAsIntegers() throws Exception { SchemaBuilder builder = SchemaBuilder.create(); builder.addListOfInteger("foo", null, null, (key, position, value) -> { assertEquals(Arrays.asList(1, 2, 3), value); return singleError("should reach here"); }); Schema schema = builder.toSchema(); Configuration config = Configuration.fromToml(" foo = [1, 2, 3]\n", schema); assertTrue(config.hasErrors()); ConfigurationError error = config.errors().get(0); assertEquals("should reach here", error.getMessage()); assertNull(error.position()); } @Test void shouldValidateWithinIntegerList() throws Exception { SchemaBuilder builder = SchemaBuilder.create(); builder.addListOfInteger("foo", null, null, (key, position, value) -> { throw new AssertionFailedError("should not be reached"); }); Schema schema = builder.toSchema(); Configuration config = Configuration.fromToml(" foo = [1, 2, " + (1L + Integer.MAX_VALUE) + ", 3]\n", schema); assertTrue(config.hasErrors()); ConfigurationError error = config.errors().get(0); assertEquals("Value of property 'foo', index 2, is too large for an integer", error.getMessage()); assertEquals(DocumentPosition.positionAt(1, 2), error.position()); } @Test void shouldValidateLongList() throws Exception { SchemaBuilder builder = SchemaBuilder.create(); builder.addListOfLong("foo", null, null, (key, position, value) -> { throw new AssertionFailedError("should not be reached"); }); Schema schema = builder.toSchema(); Configuration config = Configuration.fromToml(" foo = ['a', 'b']\n", schema); assertTrue(config.hasErrors()); ConfigurationError error = config.errors().get(0); assertEquals("Value of property 'foo', index 0, is not a long", error.getMessage()); assertEquals(DocumentPosition.positionAt(1, 2), error.position()); } @Test void shouldValidateWithIsPresent() throws Exception { SchemaBuilder builder = SchemaBuilder.create(); builder.addString("foo.bar", null, null, PropertyValidator.isPresent()); Schema schema = builder.toSchema(); Configuration config = Configuration.fromToml(" foo = ['a', 'b']\n", schema); assertTrue(config.hasErrors()); ConfigurationError error = config.errors().get(0); assertEquals("Required property 'foo.bar' is missing", error.getMessage()); assertNull(error.position()); } @Test void shouldValidateWithInRange() throws Exception { SchemaBuilder builder = SchemaBuilder.create(); builder.addInteger("foo", null, null, PropertyValidator.inRange(0, 10)); Schema schema = builder.toSchema(); Configuration config = Configuration.fromToml(" foo = 15\n", schema); assertTrue(config.hasErrors()); ConfigurationError error = config.errors().get(0); assertEquals("Value of property 'foo' is outside range [0,10)", error.getMessage()); assertEquals(DocumentPosition.positionAt(1, 2), error.position()); Configuration config2 = Configuration.fromToml(" foo = 9\n", schema); assertFalse(config2.hasErrors()); } @Test void validatorNotCalledWhenDefaultUsed() throws Exception { SchemaBuilder builder = SchemaBuilder.create(); builder.addString("fooS", "hello", null, (key, position, value) -> { throw new AssertionFailedError("should not be reached"); }); builder.addInteger("fooI", 2, null, (key, position, value) -> { throw new AssertionFailedError("should not be reached"); }); builder.addLong("fooL", 2L, null, (key, position, value) -> { throw new AssertionFailedError("should not be reached"); }); builder.addListOfString("fooLS", Collections.emptyList(), null, (key, position, value) -> { throw new AssertionFailedError("should not be reached"); }); builder.addListOfInteger("fooLI", Collections.emptyList(), null, (key, position, value) -> { throw new AssertionFailedError("should not be reached"); }); builder.addListOfLong("fooLL", Collections.emptyList(), null, (key, position, value) -> { throw new AssertionFailedError("should not be reached"); }); Schema schema = builder.toSchema(); Configuration config = Configuration.fromToml("\n", schema); assertFalse(config.hasErrors()); } @Test void writeConfigurationToToml() throws Exception { // @formatter:off Configuration config = Configuration.fromToml( "foo = \"12\"\n" + "bar = 13\n" + "foobar = 156.34\n" + "amaps = [{a = 1, b = 2}, {a = 'hello'}]\n" + "\n" + "[boo]\n" + "baz=[1,2,3]\n" + "\n" + "[amap]\n" + "a=1\n" + "b=2\n" + "[deepmap]\n" + "' a' = 4\n" + "[deepmap.' deep']\n" + "'' = 'emptykey'\n" + "[deepmap.deeper]\n" + "farewell='goodbye'\n" ); // @formatter:on // @formatter:off String expected = "amaps = [{a = 1, b = 2}, {a = \"hello\"}]\n" + "bar = 13\n" + "foo = \"12\"\n" + "foobar = 156.34\n" + "\n" + "[amap]\n" + "a = 1\n" + "b = 2\n" + "\n" + "[boo]\n" + "baz = [1, 2, 3]\n" + "\n" + "[deepmap]\n" + "\" a\" = 4\n" + "\n" + "[deepmap.\" deep\"]\n" + "\"\" = \"emptykey\"\n" + "\n" + "[deepmap.deeper]\n" + "farewell = \"goodbye\"\n"; // @formatter:on assertEquals(expected, config.toToml()); } @Test void writeConfigurationWithDocumentationToToml() throws Exception { SchemaBuilder builder = SchemaBuilder.create(); builder.documentProperty("deepmap", "This table will have sub-tables"); builder.documentProperty("boo.baz", "A list of longs"); builder.documentProperty("bar", "Lucky number 13"); Schema schema = builder.toSchema(); // @formatter:off Configuration config = Configuration.fromToml( "foo = \"12\"\n" + "bar = 13\n" + "foobar = 156.34\n" + "amaps = [{a = 1, b = 2}, {a = 'hello'}]\n" + "\n" + "[boo]\n" + "baz=[1,2,3]\n" + "\n" + "[amap]\n" + "a=1\n" + "b=2\n" + "[deepmap]\n" + "' a' = 4\n" + "[deepmap.' deep']\n" + "'' = 'emptykey'\n" + "[deepmap.deeper]\n" + "farewell='goodbye'\n", schema); // @formatter:on // @formatter:off String expected = "amaps = [{a = 1, b = 2}, {a = \"hello\"}]\n" + "## Lucky number 13\n" + "bar = 13\n" + "foo = \"12\"\n" + "foobar = 156.34\n" + "\n" + "[amap]\n" + "a = 1\n" + "b = 2\n" + "\n" + "[boo]\n" + "## A list of longs\n" + "baz = [1, 2, 3]\n" + "\n" + "## This table will have sub-tables\n" + "[deepmap]\n" + "\" a\" = 4\n" + "\n" + "[deepmap.\" deep\"]\n" + "\"\" = \"emptykey\"\n" + "\n" + "[deepmap.deeper]\n" + "farewell = \"goodbye\"\n"; // @formatter:on assertEquals(expected, config.toToml()); } @Test void writeConfigurationToTomlIncludesDefaults() throws Exception { SchemaBuilder builder = SchemaBuilder.create(); builder.addString("farewell", "goodbye", "a farewell", null); builder.addInteger("xxx", 10, "documentation\n over multiple lines!", null); builder.addInteger("zzz", 10, null, null); Schema schema = builder.toSchema(); // @formatter:off Configuration config = Configuration.fromToml( "foo = \"12\"\n" + "foobar = 156.34\n" + "xxx = 10\n" + "zzz = 5\n" + "\n" + "[boo]\n" + "baz=[1,2,3]\n" + "\n", schema); // @formatter:on // @formatter:off String expected = "## a farewell\n" + "#farewell = \"goodbye\"\n" + "foo = \"12\"\n" + "foobar = 156.34\n" + "## documentation\n" + "## over multiple lines!\n" + "#xxx = 10\n" + "#zzz = 10\n" + "zzz = 5\n" + "\n" + "[boo]\n" + "baz = [1, 2, 3]\n"; // @formatter:on assertEquals(expected.replace("\n", System.lineSeparator()), config.toToml()); } @Test void buildSchemaAndDumpToToml() throws Exception { SchemaBuilder builder = SchemaBuilder.create(); builder.addString("somekey", "somevalue", "Got milk", null); builder.addBoolean("foo", false, "Toggle switch", null); builder.addDouble("bar", 1.0, "Value of currency", null); builder.addLong("'Here now'", 123L, "One two three", null); builder.addString("'No defaults'", null, null, null); Configuration config = Configuration.fromToml("", builder.toSchema()); // @formatter:off String expected = "## One two three\n" + "#\"Here now\" = 123\n" + "## Value of currency\n" + "#bar = 1.0\n" + "## Toggle switch\n" + "#foo = false\n" + "## Got milk\n" + "#somekey = \"somevalue\"\n"; // @formatter:on assertEquals(expected.replace("\n", System.lineSeparator()), config.toToml()); } } cava-0.6.0/crypto/000077500000000000000000000000001341750772100137565ustar00rootroot00000000000000cava-0.6.0/crypto/build.gradle000066400000000000000000000010671341750772100162410ustar00rootroot00000000000000description = 'Classes and utilities for working with cryptography.' javadoc { exclude '**/LibSodium*' } dependencies { compile project(':bytes') compile project(':io') compile project(':units') compile 'com.google.guava:guava' compile 'com.github.jnr:jnr-ffi' compileOnly 'org.bouncycastle:bcprov-jdk15on' testCompile project(':junit') testCompile 'org.bouncycastle:bcprov-jdk15on' testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/crypto/src/000077500000000000000000000000001341750772100145455ustar00rootroot00000000000000cava-0.6.0/crypto/src/main/000077500000000000000000000000001341750772100154715ustar00rootroot00000000000000cava-0.6.0/crypto/src/main/java/000077500000000000000000000000001341750772100164125ustar00rootroot00000000000000cava-0.6.0/crypto/src/main/java/net/000077500000000000000000000000001341750772100172005ustar00rootroot00000000000000cava-0.6.0/crypto/src/main/java/net/consensys/000077500000000000000000000000001341750772100212245ustar00rootroot00000000000000cava-0.6.0/crypto/src/main/java/net/consensys/cava/000077500000000000000000000000001341750772100221365ustar00rootroot00000000000000cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/000077500000000000000000000000001341750772100234565ustar00rootroot00000000000000cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/Hash.java000066400000000000000000000163261341750772100252140ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto; import static java.util.Objects.requireNonNull; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * Various utilities for providing hashes (digests) of arbitrary data. * * Requires the BouncyCastleProvider to be loaded and available. See * https://www.bouncycastle.org/wiki/display/JA1/Provider+Installation for detail. */ public final class Hash { private Hash() {} // SHA-2 private static String SHA2_256 = "SHA-256"; private static String SHA2_512_256 = "SHA-512/256"; // Keccak private static String KECCAK_256 = "KECCAK-256"; private static String KECCAK_512 = "KECCAK-512"; // SHA-3 private static String SHA3_256 = "SHA3-256"; private static String SHA3_512 = "SHA3-512"; /** * Helper method to generate a digest using the provided algorithm. * * @param input The input bytes to produce the digest for. * @param alg The name of the digest algorithm to use. * @return A digest. * @throws NoSuchAlgorithmException If no Provider supports a MessageDigestSpi implementation for the specified * algorithm. */ public static byte[] digestUsingAlgorithm(byte[] input, String alg) throws NoSuchAlgorithmException { requireNonNull(input); requireNonNull(alg); MessageDigest digest = MessageDigest.getInstance(alg); digest.update(input); return digest.digest(); } /** * Helper method to generate a digest using the provided algorithm. * * @param input The input bytes to produce the digest for. * @param alg The name of the digest algorithm to use. * @return A digest. * @throws NoSuchAlgorithmException If no Provider supports a MessageDigestSpi implementation for the specified * algorithm. */ public static Bytes digestUsingAlgorithm(Bytes input, String alg) throws NoSuchAlgorithmException { requireNonNull(input); requireNonNull(alg); MessageDigest digest = MessageDigest.getInstance(alg); input.update(digest); return Bytes.wrap(digest.digest()); } /** * Digest using SHA2-256. * * @param input The input bytes to produce the digest for. * @return A digest. */ public static byte[] sha2_256(byte[] input) { try { return digestUsingAlgorithm(input, SHA2_256); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Algorithm should be available but was not", e); } } /** * Digest using SHA2-256. * * @param input The input bytes to produce the digest for. * @return A digest. */ public static Bytes32 sha2_256(Bytes input) { try { return Bytes32.wrap(digestUsingAlgorithm(input, SHA2_256).toArrayUnsafe()); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Algorithm should be available but was not", e); } } /** * Digest using SHA2-512/256. * * @param input The value to encode. * @return A digest. */ public static byte[] sha2_512_256(byte[] input) { try { return digestUsingAlgorithm(input, SHA2_512_256); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Algorithm should be available but was not", e); } } /** * Digest using SHA-512/256. * * @param input The value to encode. * @return A digest. */ public static Bytes32 sha2_512_256(Bytes input) { try { return Bytes32.wrap(digestUsingAlgorithm(input, SHA2_512_256).toArrayUnsafe()); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Algorithm should be available but was not", e); } } /** * Digest using keccak-256. * * @param input The input bytes to produce the digest for. * @return A digest. */ public static byte[] keccak256(byte[] input) { try { return digestUsingAlgorithm(input, KECCAK_256); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Algorithm should be available but was not", e); } } /** * Digest using keccak-256. * * @param input The input bytes to produce the digest for. * @return A digest. */ public static Bytes32 keccak256(Bytes input) { try { return Bytes32.wrap(digestUsingAlgorithm(input, KECCAK_256).toArrayUnsafe()); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Algorithm should be available but was not", e); } } /** * Digest using keccak-512. * * @param input The input bytes to produce the digest for. * @return A digest. */ public static byte[] keccak512(byte[] input) { try { return digestUsingAlgorithm(input, KECCAK_512); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Algorithm should be available but was not", e); } } /** * Digest using keccak-512. * * @param input The input bytes to produce the digest for. * @return A digest. */ public static Bytes keccak512(Bytes input) { try { return Bytes.wrap(digestUsingAlgorithm(input, KECCAK_512).toArrayUnsafe()); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Algorithm should be available but was not", e); } } /** * Digest using SHA3-256. * * @param input The value to encode. * @return A digest. */ public static byte[] sha3_256(byte[] input) { try { return digestUsingAlgorithm(input, SHA3_256); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Algorithm should be available but was not", e); } } /** * Digest using SHA3-256. * * @param input The value to encode. * @return A digest. */ public static Bytes32 sha3_256(Bytes input) { try { return Bytes32.wrap(digestUsingAlgorithm(input, SHA3_256).toArrayUnsafe()); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Algorithm should be available but was not", e); } } /** * Digest using SHA3-512. * * @param input The value to encode. * @return A digest. */ public static byte[] sha3_512(byte[] input) { try { return digestUsingAlgorithm(input, SHA3_512); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Algorithm should be available but was not", e); } } /** * Digest using SHA3-512. * * @param input The value to encode. * @return A digest. */ public static Bytes sha3_512(Bytes input) { try { return Bytes.wrap(digestUsingAlgorithm(input, SHA3_512).toArrayUnsafe()); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Algorithm should be available but was not", e); } } } InvalidSEC256K1SecretKeyStoreException.java000066400000000000000000000014441341750772100333320ustar00rootroot00000000000000cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/* * Copyright 2018 ConsenSys AG. * * 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.crypto; /** * Exception thrown when reading a store that contains an invalid SEC256K1 private keys. */ public final class InvalidSEC256K1SecretKeyStoreException extends RuntimeException { } cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/SECP256K1.java000066400000000000000000001006001341750772100255010ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto; import static com.google.common.base.Preconditions.*; import static java.nio.file.StandardOpenOption.READ; import static net.consensys.cava.crypto.Hash.keccak256; import static net.consensys.cava.crypto.SECP256K1.Parameters.CURVE; import static net.consensys.cava.io.file.Files.atomicReplace; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import net.consensys.cava.bytes.MutableBytes; import net.consensys.cava.units.bigints.UInt256; import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.FileChannel; import java.nio.file.Path; import java.security.*; import java.security.spec.ECGenParameterSpec; import java.util.Arrays; import javax.annotation.Nullable; import javax.security.auth.Destroyable; import com.google.common.base.Objects; import org.bouncycastle.asn1.sec.SECNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.asn1.x9.X9IntegerConverter; import org.bouncycastle.crypto.agreement.ECDHBasicAgreement; import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.signers.ECDSASigner; import org.bouncycastle.crypto.signers.HMacDSAKCalculator; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; import org.bouncycastle.math.ec.ECAlgorithms; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.math.ec.FixedPointCombMultiplier; import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve; /* * Adapted from the BitcoinJ ECKey (Apache 2 License) implementation: * https://github.com/bitcoinj/bitcoinj/blob/master/core/src/main/java/org/bitcoinj/core/ECKey.java * * Adapted from the web3j (Apache 2 License) implementations: * https://github.com/web3j/web3j/crypto/src/main/java/org/web3j/crypto/*.java */ /** * An Elliptic Curve Digital Signature using parameters as used by Bitcoin, and defined in Standards for Efficient * Cryptography (SEC) (Certicom Research, http://www.secg.org/sec2-v2.pdf). * *

* This class depends upon the BouncyCastle library being available and added as a {@link java.security.Provider}. See * https://www.bouncycastle.org/wiki/display/JA1/Provider+Installation. * *

* BouncyCastle can be included using the gradle dependency 'org.bouncycastle:bcprov-jdk15on'. */ public final class SECP256K1 { private SECP256K1() {} private static final String ALGORITHM = "ECDSA"; private static final String CURVE_NAME = "secp256k1"; private static final String PROVIDER = "BC"; // Lazily initialize parameters by using java initialization on demand public static final class Parameters { public static final ECDomainParameters CURVE; static final BigInteger CURVE_ORDER; static final BigInteger HALF_CURVE_ORDER; static final KeyPairGenerator KEY_PAIR_GENERATOR; static final X9IntegerConverter X_9_INTEGER_CONVERTER; static { try { Class.forName("org.bouncycastle.asn1.sec.SECNamedCurves"); } catch (ClassNotFoundException e) { throw new IllegalStateException( "BouncyCastle is not available on the classpath, see https://www.bouncycastle.org/latest_releases.html"); } X9ECParameters params = SECNamedCurves.getByName(CURVE_NAME); CURVE = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); CURVE_ORDER = CURVE.getN(); HALF_CURVE_ORDER = CURVE_ORDER.shiftRight(1); if (CURVE_ORDER.compareTo(SecP256K1Curve.q) >= 0) { throw new IllegalStateException("secp256k1.n should be smaller than secp256k1.q, but is not"); } try { KEY_PAIR_GENERATOR = KeyPairGenerator.getInstance(ALGORITHM, PROVIDER); } catch (NoSuchProviderException e) { throw new IllegalStateException( "BouncyCastleProvider is not available, see https://www.bouncycastle.org/wiki/display/JA1/Provider+Installation", e); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Algorithm should be available but was not", e); } ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec(CURVE_NAME); try { KEY_PAIR_GENERATOR.initialize(ecGenParameterSpec, new SecureRandom()); } catch (InvalidAlgorithmParameterException e) { throw new IllegalStateException("Algorithm parameter should be available but was not", e); } X_9_INTEGER_CONVERTER = new X9IntegerConverter(); } } // Decompress a compressed public key (x co-ord and low-bit of y-coord). @Nullable private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { byte[] compEnc = Parameters.X_9_INTEGER_CONVERTER .integerToBytes(xBN, 1 + Parameters.X_9_INTEGER_CONVERTER.getByteLength(Parameters.CURVE.getCurve())); compEnc[0] = (byte) (yBit ? 0x03 : 0x02); try { return Parameters.CURVE.getCurve().decodePoint(compEnc); } catch (IllegalArgumentException e) { // the compressed key was invalid return null; } } /** * Given the components of a signature and a selector value, recover and return the public key that generated the * signature according to the algorithm in SEC1v2 section 4.1.6. * *

* The recovery id is an index from 0 to 3 which indicates which of the 4 possible keys is the correct one. Because * the key recovery operation yields multiple potential keys, the correct key must either be stored alongside the * signature, or you must be willing to try each recovery id in turn until you find one that outputs the key you are * expecting. * *

* If this method returns null it means recovery was not possible and recovery id should be iterated. * *

* Given the above two points, a correct usage of this method is inside a for loop from 0 to 3, and if the output is * null OR a key that is not the one you expect, you try again with the next recovery id. * * @param v Which possible key to recover. * @param r The R component of the signature. * @param s The S component of the signature. * @param messageHash Hash of the data that was signed. * @return A ECKey containing only the public part, or {@code null} if recovery wasn't possible. */ @Nullable private static BigInteger recoverFromSignature(int v, BigInteger r, BigInteger s, Bytes32 messageHash) { assert (v == 0 || v == 1); assert (r.signum() >= 0); assert (s.signum() >= 0); assert (messageHash != null); // Compressed keys require you to know an extra bit of data about the y-coord as there are two possibilities. // So it's encoded in the recovery id (v). ECPoint R = decompressKey(r, (v & 1) == 1); // 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers responsibility). if (R == null || !R.multiply(Parameters.CURVE_ORDER).isInfinity()) { return null; } // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification. BigInteger e = messageHash.toUnsignedBigInteger(); // 1.6. For k from 1 to 2 do the following. (loop is outside this function via iterating v) // 1.6.1. Compute a candidate public key as: // Q = mi(r) * (sR - eG) // // Where mi(x) is the modular multiplicative inverse. We transform this into the following: // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) // Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n). // In the above equation ** is point multiplication and + is point addition (the EC group // operator). // // We can find the additive inverse by subtracting e from zero then taking the mod. For example the additive // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod 11 = 8. BigInteger eInv = BigInteger.ZERO.subtract(e).mod(Parameters.CURVE_ORDER); BigInteger rInv = r.modInverse(Parameters.CURVE_ORDER); BigInteger srInv = rInv.multiply(s).mod(Parameters.CURVE_ORDER); BigInteger eInvrInv = rInv.multiply(eInv).mod(Parameters.CURVE_ORDER); ECPoint q = ECAlgorithms.sumOfTwoMultiplies(Parameters.CURVE.getG(), eInvrInv, R, srInv); if (q.isInfinity()) { return null; } byte[] qBytes = q.getEncoded(false); // We remove the prefix return new BigInteger(1, Arrays.copyOfRange(qBytes, 1, qBytes.length)); } /** * Generates an ECDSA signature. * * @param data The data to sign. * @param keyPair The keypair to sign using. * @return The signature. */ public static Signature sign(byte[] data, KeyPair keyPair) { return signHashed(keccak256(data), keyPair); } /** * Generates an ECDSA signature. * * @param data The data to sign. * @param keyPair The keypair to sign using. * @return The signature. */ public static Signature sign(Bytes data, KeyPair keyPair) { return signHashed(keccak256(data), keyPair); } /** * Generates an ECDSA signature. * * @param hash The keccak256 hash of the data to sign. * @param keyPair The keypair to sign using. * @return The signature. */ public static Signature signHashed(byte[] hash, KeyPair keyPair) { return signHashed(Bytes32.wrap(hash), keyPair); } /** * Generates an ECDSA signature. * * @param hash The keccak256 hash of the data to sign. * @param keyPair The keypair to sign using. * @return The signature. */ public static Signature signHashed(Bytes32 hash, KeyPair keyPair) { ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest())); ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(keyPair.secretKey().bytes().toUnsignedBigInteger(), Parameters.CURVE); signer.init(true, privKey); BigInteger[] components = signer.generateSignature(hash.toArrayUnsafe()); BigInteger r = components[0]; BigInteger s = components[1]; // Automatically adjust the S component to be less than or equal to half the curve // order, if necessary. This is required because for every signature (r,s) the signature // (r, -s (mod N)) is a valid signature of the same message. However, we dislike the // ability to modify the bits of a Bitcoin transaction after it's been signed, as that // violates various assumed invariants. Thus in future only one of those forms will be // considered legal and the other will be banned. if (s.compareTo(Parameters.HALF_CURVE_ORDER) > 0) { // The order of the curve is the number of valid points that exist on that curve. // If S is in the upper half of the number of valid points, then bring it back to // the lower half. Otherwise, imagine that: // N = 10 // s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are valid solutions. // 10 - 8 == 2, giving us always the latter solution, which is canonical. s = Parameters.CURVE_ORDER.subtract(s); } // Now we have to work backwards to figure out the recovery id needed to recover the signature. // On this curve, there are only two possible values for the recovery id. int recId = -1; BigInteger publicKeyBI = keyPair.publicKey().bytes().toUnsignedBigInteger(); for (int i = 0; i < 2; i++) { BigInteger k = recoverFromSignature(i, r, s, hash); if (k != null && k.equals(publicKeyBI)) { recId = i; break; } } if (recId == -1) { // this should never happen throw new RuntimeException("Unexpected error - could not construct a recoverable key."); } byte v = (byte) recId; return new Signature(v, r, s); } /** * Verifies the given ECDSA signature against the message bytes using the public key bytes. * * @param data The data to verify. * @param signature The signature. * @param publicKey The public key. * @return True if the verification is successful. */ public static boolean verify(byte[] data, Signature signature, PublicKey publicKey) { return verifyHashed(keccak256(data), signature, publicKey); } /** * Verifies the given ECDSA signature against the message bytes using the public key bytes. * * @param data The data to verify. * @param signature The signature. * @param publicKey The public key. * @return True if the verification is successful. */ public static boolean verify(Bytes data, Signature signature, PublicKey publicKey) { return verifyHashed(keccak256(data), signature, publicKey); } /** * Verifies the given ECDSA signature against the message bytes using the public key bytes. * * @param hash The keccak256 hash of the data to verify. * @param signature The signature. * @param publicKey The public key. * @return True if the verification is successful. */ public static boolean verifyHashed(Bytes32 hash, Signature signature, PublicKey publicKey) { return verifyHashed(hash.toArrayUnsafe(), signature, publicKey); } /** * Verifies the given ECDSA signature against the message bytes using the public key bytes. * * @param hash The keccak256 hash of the data to verify. * @param signature The signature. * @param publicKey The public key. * @return True if the verification is successful. */ public static boolean verifyHashed(byte[] hash, Signature signature, PublicKey publicKey) { ECDSASigner signer = new ECDSASigner(); Bytes toDecode = Bytes.wrap(Bytes.of((byte) 4), publicKey.bytes()); ECPublicKeyParameters params = new ECPublicKeyParameters(Parameters.CURVE.getCurve().decodePoint(toDecode.toArray()), Parameters.CURVE); signer.init(false, params); try { return signer.verifySignature(hash, signature.r, signature.s); } catch (NullPointerException e) { // Bouncy Castle contains a bug that can cause NPEs given specially crafted signatures. Those signatures // are inherently invalid/attack sigs so we just fail them here rather than crash the thread. return false; } } /** * Calculates an ECDH key agreement between the private and the public key of another party, formatted as a 32 bytes * array. * * @param privKey the private key * @param theirPubKey the public key * @return shared secret as 32 bytes */ public static Bytes32 calculateKeyAgreement(SecretKey privKey, PublicKey theirPubKey) { checkArgument(privKey != null, "missing private key"); checkArgument(theirPubKey != null, "missing remote public key"); ECPrivateKeyParameters privKeyP = new ECPrivateKeyParameters(privKey.bytes().toUnsignedBigInteger(), Parameters.CURVE); ECPublicKeyParameters pubKeyP = new ECPublicKeyParameters(theirPubKey.asEcPoint(), Parameters.CURVE); ECDHBasicAgreement agreement = new ECDHBasicAgreement(); agreement.init(privKeyP); return UInt256.valueOf(agreement.calculateAgreement(pubKeyP)).toBytes(); } /** * A SECP256K1 private key. */ public static class SecretKey implements Destroyable { private Bytes32 keyBytes; @Override protected void finalize() { destroy(); } @Override public void destroy() { if (keyBytes != null) { byte[] b = keyBytes.toArrayUnsafe(); keyBytes = null; Arrays.fill(b, (byte) 0); } } /** * Create the private key from a {@link BigInteger}. * * @param key The integer describing the key. * @return The private key. * @throws IllegalArgumentException If the integer would overflow 32 bytes. */ public static SecretKey fromInteger(BigInteger key) { checkNotNull(key); byte[] bytes = key.toByteArray(); int offset = 0; while (bytes[offset] == 0) { ++offset; } if ((bytes.length - offset) > Bytes32.SIZE) { throw new IllegalArgumentException("key integer is too large"); } return fromBytes(Bytes32.leftPad(Bytes.wrap(bytes, offset, bytes.length - offset))); } /** * Create the private key from bytes. * * @param bytes The key bytes. * @return The private key. */ public static SecretKey fromBytes(Bytes32 bytes) { return new SecretKey(bytes.copy()); } /** * Load a private key from a file. * * @param file The file to read the key from. * @return The private key. * @throws IOException On a filesystem error. * @throws InvalidSEC256K1SecretKeyStoreException If the file does not contain a valid key. */ public static SecretKey load(Path file) throws IOException, InvalidSEC256K1SecretKeyStoreException { // use buffers for all secret key data transfer, so they can be overwritten on completion ByteBuffer byteBuffer = ByteBuffer.allocate(65); CharBuffer charBuffer = CharBuffer.allocate(64); try { FileChannel channel = FileChannel.open(file, READ); while (byteBuffer.hasRemaining() && channel.read(byteBuffer) > 0) { // no body } channel.close(); if (byteBuffer.remaining() > 1) { throw new InvalidSEC256K1SecretKeyStoreException(); } byteBuffer.flip(); for (int i = 0; i < 64; ++i) { charBuffer.put((char) byteBuffer.get()); } if (byteBuffer.limit() == 65 && byteBuffer.get(64) != '\n' && byteBuffer.get(64) != '\r') { throw new InvalidSEC256K1SecretKeyStoreException(); } charBuffer.flip(); return SecretKey.fromBytes(Bytes32.fromHexString(charBuffer)); } catch (IllegalArgumentException ex) { throw new InvalidSEC256K1SecretKeyStoreException(); } finally { Arrays.fill(byteBuffer.array(), (byte) 0); Arrays.fill(charBuffer.array(), (char) 0); } } private SecretKey(Bytes32 bytes) { checkNotNull(bytes); this.keyBytes = bytes; } /** * Write the secret key to a file. * * @param file The file to write to. * @throws IOException On a filesystem error. */ public void store(Path file) throws IOException { checkState(keyBytes != null, "SecretKey has been destroyed"); // use buffers for all secret key data transfer, so they can be overwritten on completion byte[] bytes = new byte[64]; CharBuffer hexChars = keyBytes.appendHexTo(CharBuffer.allocate(64)); try { hexChars.flip(); for (int i = 0; i < 64; ++i) { bytes[i] = (byte) hexChars.get(); } atomicReplace(file, bytes); } finally { Arrays.fill(bytes, (byte) 0); Arrays.fill(hexChars.array(), (char) 0); } } @Override public boolean equals(Object obj) { if (!(obj instanceof SecretKey)) { return false; } checkState(keyBytes != null, "SecretKey has been destroyed"); SecretKey other = (SecretKey) obj; return this.keyBytes.equals(other.keyBytes); } @Override public int hashCode() { checkState(keyBytes != null, "SecretKey has been destroyed"); return keyBytes.hashCode(); } /** * @return The bytes of the key. */ public Bytes32 bytes() { checkState(keyBytes != null, "SecretKey has been destroyed"); return keyBytes; } /** * @return The bytes of the key. */ public byte[] bytesArray() { checkState(keyBytes != null, "SecretKey has been destroyed"); return keyBytes.toArrayUnsafe(); } } /** * A SECP256K1 public key. */ public static class PublicKey { private static final int BYTE_LENGTH = 64; private final Bytes keyBytes; /** * Create the public key from a secret key. * * @param secretKey The secret key. * @return The associated public key. */ public static PublicKey fromSecretKey(SecretKey secretKey) { BigInteger privKey = secretKey.bytes().toUnsignedBigInteger(); /* * TODO: FixedPointCombMultiplier currently doesn't support scalars longer than the group * order, but that could change in future versions. */ if (privKey.bitLength() > Parameters.CURVE_ORDER.bitLength()) { privKey = privKey.mod(Parameters.CURVE_ORDER); } ECPoint point = new FixedPointCombMultiplier().multiply(Parameters.CURVE.getG(), privKey); return PublicKey.fromBytes(Bytes.wrap(Arrays.copyOfRange(point.getEncoded(false), 1, 65))); } private static Bytes toBytes64(byte[] backing) { if (backing.length == BYTE_LENGTH) { return Bytes.wrap(backing); } else if (backing.length > BYTE_LENGTH) { return Bytes.wrap(backing, backing.length - BYTE_LENGTH, BYTE_LENGTH); } else { MutableBytes res = MutableBytes.create(BYTE_LENGTH); Bytes.wrap(backing).copyTo(res, BYTE_LENGTH - backing.length); return res; } } /** * Create the public key from a secret key. * * @param privateKey The secret key. * @return The associated public key. */ public static PublicKey fromInteger(BigInteger privateKey) { checkNotNull(privateKey); return fromBytes(toBytes64(privateKey.toByteArray())); } /** * Create the public key from bytes. * * @param bytes The key bytes. * @return The public key. */ public static PublicKey fromBytes(Bytes bytes) { return new PublicKey(bytes); } /** * Create the public key from a hex string. * * @param str The hexadecimal string to parse, which may or may not start with "0x". * @return The public key. */ public static PublicKey fromHexString(CharSequence str) { return new PublicKey(Bytes.fromHexString(str)); } /** * Recover a public key using a digital signature and the data it signs. * * @param data The signed data. * @param signature The digital signature. * @return The associated public key, or {@code null} if recovery wasn't possible. */ @Nullable public static PublicKey recoverFromSignature(byte[] data, Signature signature) { return recoverFromHashAndSignature(keccak256(data), signature); } /** * Recover a public key using a digital signature and the data it signs. * * @param data The signed data. * @param signature The digital signature. * @return The associated public key, or {@code null} if recovery wasn't possible. */ @Nullable public static PublicKey recoverFromSignature(Bytes data, Signature signature) { return recoverFromHashAndSignature(keccak256(data), signature); } /** * Recover a public key using a digital signature and a keccak256 hash of the data it signs. * * @param hash The keccak256 hash of the signed data. * @param signature The digital signature. * @return The associated public key, or {@code null} if recovery wasn't possible. */ @Nullable public static PublicKey recoverFromHashAndSignature(byte[] hash, Signature signature) { return recoverFromHashAndSignature(Bytes32.wrap(hash), signature); } /** * Recover a public key using a digital signature and a keccak256 hash of the data it signs. * * @param hash The keccak256 hash of the signed data. * @param signature The digital signature. * @return The associated public key, or {@code null} if recovery wasn't possible. */ @Nullable public static PublicKey recoverFromHashAndSignature(Bytes32 hash, Signature signature) { BigInteger publicKeyBI = SECP256K1.recoverFromSignature(signature.v(), signature.r(), signature.s(), hash); return (publicKeyBI != null) ? fromInteger(publicKeyBI) : null; } private PublicKey(Bytes bytes) { checkNotNull(bytes); checkArgument(bytes.size() == BYTE_LENGTH, "Key must be %s bytes long, got %s", BYTE_LENGTH, bytes.size()); this.keyBytes = bytes; } @Override public boolean equals(Object other) { if (!(other instanceof PublicKey)) { return false; } PublicKey that = (PublicKey) other; return this.keyBytes.equals(that.keyBytes); } @Override public int hashCode() { return keyBytes.hashCode(); } /** * @return The bytes of the key. */ public Bytes bytes() { return keyBytes; } /** * @return The bytes of the key. */ public byte[] bytesArray() { return keyBytes.toArrayUnsafe(); } /** * Computes the public key as a point on the elliptic curve. * * @return the public key as a BouncyCastle elliptic curve point */ public ECPoint asEcPoint() { // 0x04 is the prefix for uncompressed keys. Bytes val = Bytes.concatenate(Bytes.of(0x04), keyBytes); return CURVE.getCurve().decodePoint(val.toArrayUnsafe()); } @Override public String toString() { return keyBytes.toString(); } /** * @return This key represented as hexadecimal, starting with "0x". */ public String toHexString() { return keyBytes.toHexString(); } } /** * A SECP256K1 key pair. */ public static class KeyPair { private final SecretKey secretKey; private final PublicKey publicKey; /** * Create a keypair from a private and public key. * * @param secretKey The private key. * @param publicKey The public key. * @return The key pair. */ public static KeyPair create(SecretKey secretKey, PublicKey publicKey) { return new KeyPair(secretKey, publicKey); } /** * Create a keypair using only a private key. * * @param secretKey The private key. * @return The key pair. */ public static KeyPair fromSecretKey(SecretKey secretKey) { return new KeyPair(secretKey, PublicKey.fromSecretKey(secretKey)); } /** * Generate a new keypair. * * Entropy for the generation is drawn from {@link SecureRandom}. * * @return A new keypair. */ public static KeyPair random() { java.security.KeyPair rawKeyPair = Parameters.KEY_PAIR_GENERATOR.generateKeyPair(); BCECPrivateKey privateKey = (BCECPrivateKey) rawKeyPair.getPrivate(); BCECPublicKey publicKey = (BCECPublicKey) rawKeyPair.getPublic(); BigInteger privateKeyValue = privateKey.getD(); // Ethereum does not use encoded public keys like bitcoin - see // https://en.bitcoin.it/wiki/Elliptic_Curve_Digital_Signature_Algorithm for details // Additionally, as the first bit is a constant prefix (0x04) we ignore this value byte[] publicKeyBytes = publicKey.getQ().getEncoded(false); BigInteger publicKeyValue = new BigInteger(1, Arrays.copyOfRange(publicKeyBytes, 1, publicKeyBytes.length)); return new KeyPair(SecretKey.fromInteger(privateKeyValue), PublicKey.fromInteger(publicKeyValue)); } /** * Load a key pair from a path. * * @param file The file containing a private key. * @return The key pair. * @throws IOException On a filesystem error. * @throws InvalidSEC256K1SecretKeyStoreException If the file does not contain a valid key. */ public static KeyPair load(Path file) throws IOException, InvalidSEC256K1SecretKeyStoreException { return fromSecretKey(SecretKey.load(file)); } private KeyPair(SecretKey secretKey, PublicKey publicKey) { checkNotNull(secretKey); checkNotNull(publicKey); this.secretKey = secretKey; this.publicKey = publicKey; } @Override public int hashCode() { return Objects.hashCode(secretKey, publicKey); } @Override public boolean equals(Object other) { if (!(other instanceof KeyPair)) { return false; } KeyPair that = (KeyPair) other; return this.secretKey.equals(that.secretKey) && this.publicKey.equals(that.publicKey); } /** * @return The secret key. */ public SecretKey secretKey() { return secretKey; } /** * @return The public key. */ public PublicKey publicKey() { return publicKey; } /** * Write the key pair to a file. * * @param file The file to write to. * @throws IOException On a filesystem error. */ public void store(Path file) throws IOException { secretKey.store(file); } } /** * A SECP256K1 digital signature. */ public static class Signature { /* * Parameter v is the recovery id to reconstruct the public key used to create the signature. It must be in * the range 0 to 3 and indicates which of the 4 possible keys is the correct one. Because the key recovery * operation yields multiple potential keys, the correct key must either be stored alongside the signature, * or you must be willing to try each recovery id in turn until you find one that outputs the key you are * expecting. */ private final byte v; private final BigInteger r; private final BigInteger s; /** * Create a signature from bytes. * * @param bytes The signature bytes. * @return The signature. */ public static Signature fromBytes(Bytes bytes) { checkNotNull(bytes); checkArgument(bytes.size() == 65, "Signature must be 65 bytes, but got %s instead", bytes.size()); BigInteger r = bytes.slice(0, 32).toUnsignedBigInteger(); BigInteger s = bytes.slice(32, 32).toUnsignedBigInteger(); return new Signature(bytes.get(64), r, s); } /** * Create a signature from parameters. * * @param v The v-value (recovery id). * @param r The r-value. * @param s The s-value. * @return The signature. * @throws IllegalArgumentException If any argument has an invalid range. */ public static Signature create(byte v, BigInteger r, BigInteger s) { return new Signature(v, r, s); } Signature(byte v, BigInteger r, BigInteger s) { checkArgument(v == 0 || v == 1, "Invalid v-value, should be 0 or 1, got %s", v); checkNotNull(r); checkNotNull(s); checkArgument( r.compareTo(BigInteger.ONE) >= 0 && r.compareTo(Parameters.CURVE_ORDER) < 0, "Invalid r-value, should be >= 1 and < %s, got %s", Parameters.CURVE_ORDER, r); checkArgument( s.compareTo(BigInteger.ONE) >= 0 && s.compareTo(Parameters.CURVE_ORDER) < 0, "Invalid s-value, should be >= 1 and < %s, got %s", Parameters.CURVE_ORDER, s); this.v = v; this.r = r; this.s = s; } /** * @return The v-value (recovery id) of the signature. */ public byte v() { return v; } /** * @return The r-value of the signature. */ public BigInteger r() { return r; } /** * @return The s-value of the signature. */ public BigInteger s() { return s; } /** * Check if the signature is canonical. * * Every signature (r,s) has an equivalent signature (r, -s (mod N)) that is also valid for the same message. The * canonical signature is considered the signature with the s-value less than or equal to half the curve order. * * @return {@code true} if this is the canonical form of the signature, and {@code false} otherwise. */ public boolean isCanonical() { return s.compareTo(Parameters.HALF_CURVE_ORDER) <= 0; } @Override public boolean equals(Object other) { if (!(other instanceof Signature)) { return false; } Signature that = (Signature) other; return this.r.equals(that.r) && this.s.equals(that.s) && this.v == that.v; } /** * @return The bytes of the signature. */ public Bytes bytes() { MutableBytes signature = MutableBytes.create(65); UInt256.valueOf(r).toBytes().copyTo(signature, 0); UInt256.valueOf(s).toBytes().copyTo(signature, 32); signature.set(64, v); return signature; } @Override public int hashCode() { return Objects.hashCode(r, s, v); } @Override public String toString() { return "Signature{" + "r=" + r + ", s=" + s + ", v=" + v + '}'; } } } cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/package-info.java000066400000000000000000000004241341750772100266450ustar00rootroot00000000000000/** * 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; cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/000077500000000000000000000000001341750772100247565ustar00rootroot00000000000000cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/AES256GCM.java000066400000000000000000001022401341750772100270140ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static java.util.Objects.requireNonNull; import net.consensys.cava.bytes.Bytes; import java.util.Arrays; import javax.annotation.Nullable; import javax.security.auth.Destroyable; import jnr.ffi.Pointer; import jnr.ffi.byref.LongLongByReference; // Documentation copied under the ISC License, from // https://github.com/jedisct1/libsodium-doc/blob/424b7480562c2e063bc8c52c452ef891621c8480/secret-key_cryptography/aes-256-gcm.md /** * Authenticated Encryption with Additional Data using AES-GCM. * *

* WARNING: Despite being the most popular AEAD construction due to its use in TLS, safely using AES-GCM in a different * context is tricky. * *

* No more than ~350 GB of input data should be encrypted with a given key. This is for ~16 KB messages -- Actual * figures vary according to message sizes. * *

* In addition, nonces are short and repeated nonces would totally destroy the security of this scheme. Nonces should * thus come from atomic counters, which can be difficult to set up in a distributed environment. * *

* Unless you absolutely need AES-GCM, use {@link XChaCha20Poly1305} instead. It doesn't have any of these limitations. * Or, if you don't need to authenticate additional data, just stick to * {@link Sodium#crypto_box(byte[], byte[], long, byte[], byte[], byte[])}. * *

* This class depends 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'. */ public final class AES256GCM implements AutoCloseable { private static final byte[] EMPTY_BYTES = new byte[0]; /** * Check if Sodium and the AES256-GCM algorithm is available. * * @return {@code true} if Sodium and the AES256-GCM algorithm is available. */ public static boolean isAvailable() { try { return Sodium.crypto_aead_aes256gcm_is_available() != 0; } catch (LinkageError e) { return false; } } private static void assertAvailable() { if (!isAvailable()) { throw new UnsupportedOperationException("Sodium AES256-GCM is not available"); } } /** * An AES256-GSM key. */ public static final class Key implements Destroyable { @Nullable private Pointer ptr; private final int length; private Key(Pointer ptr, int length) { this.ptr = ptr; this.length = length; } @Override protected void finalize() { destroy(); } @Override public void destroy() { if (ptr != null) { Pointer p = ptr; ptr = null; Sodium.sodium_free(p); } } @Override public boolean isDestroyed() { return ptr == null; } /** * Create a {@link Key} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the key. * @return A key, based on the supplied bytes. */ public static Key fromBytes(Bytes bytes) { return fromBytes(bytes.toArrayUnsafe()); } /** * Create a {@link Key} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the key. * @return A key, based on the supplied bytes. * @throws UnsupportedOperationException If AES256-GSM support is not available. */ public static Key fromBytes(byte[] bytes) { assertAvailable(); if (bytes.length != Sodium.crypto_aead_aes256gcm_keybytes()) { throw new IllegalArgumentException( "key must be " + Sodium.crypto_aead_aes256gcm_keybytes() + " bytes, got " + bytes.length); } return Sodium.dup(bytes, Key::new); } /** * Obtain the length of the key in bytes (32). * * @return The length of the key in bytes (32). * @throws UnsupportedOperationException If AES256-GSM support is not available. */ public static int length() { assertAvailable(); long keybytes = Sodium.crypto_aead_aes256gcm_keybytes(); if (keybytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_aead_aes256gcm_keybytes: " + keybytes + " is too large"); } return (int) keybytes; } /** * Generate a new key using a random generator. * * @return A randomly generated key. * @throws UnsupportedOperationException If AES256-GSM support is not available. */ public static Key random() { assertAvailable(); int length = length(); Pointer ptr = Sodium.malloc(length); try { // When support for 10.0.11 is dropped, use this instead //Sodium.crypto_aead_aes256gcm_keygen(ptr); Sodium.randombytes_buf(ptr, length); return new Key(ptr, length); } catch (Throwable e) { Sodium.sodium_free(ptr); throw e; } } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof Key)) { return false; } checkState(this.ptr != null, "Key has been destroyed"); Key other = (Key) obj; return other.ptr != null && Sodium.sodium_memcmp(this.ptr, other.ptr, length) == 0; } @Override public int hashCode() { checkState(this.ptr != null, "Key has been destroyed"); return Sodium.hashCode(ptr, length); } /** * @return The bytes of this key. */ public Bytes bytes() { return Bytes.wrap(bytesArray()); } /** * @return The bytes of this key. */ public byte[] bytesArray() { checkState(ptr != null, "Key has been destroyed"); return Sodium.reify(ptr, length); } } /** * An AES256-GSM nonce. */ public static final class Nonce { private final Pointer ptr; private final int length; private Nonce(Pointer ptr, int length) { this.ptr = ptr; this.length = length; } @Override protected void finalize() { Sodium.sodium_free(ptr); } /** * Create a {@link Nonce} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the nonce. * @return A nonce, based on these bytes. */ public static Nonce fromBytes(Bytes bytes) { return fromBytes(bytes.toArrayUnsafe()); } /** * Create a {@link Nonce} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the nonce. * @return A nonce, based on these bytes. * @throws UnsupportedOperationException If AES256-GSM support is not available. */ public static Nonce fromBytes(byte[] bytes) { assertAvailable(); if (bytes.length != Sodium.crypto_aead_aes256gcm_npubbytes()) { throw new IllegalArgumentException( "nonce must be " + Sodium.crypto_aead_aes256gcm_npubbytes() + " bytes, got " + bytes.length); } return Sodium.dup(bytes, Nonce::new); } /** * Obtain the length of the nonce in bytes (12). * * @return The length of the nonce in bytes (12). * @throws UnsupportedOperationException If AES256-GSM support is not available. */ public static int length() { assertAvailable(); long npubbytes = Sodium.crypto_aead_aes256gcm_npubbytes(); if (npubbytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_aead_aes256gcm_npubbytes: " + npubbytes + " is too large"); } return (int) npubbytes; } /** * Generate a new {@link Nonce} using a random generator. * * @return A randomly generated nonce. */ public static Nonce random() { return Sodium.randomBytes(length(), Nonce::new); } /** * Increment this nonce. * *

* Note that this is not synchronized. If multiple threads are creating encrypted messages and incrementing this * nonce, then external synchronization is required to ensure no two encrypt operations use the same nonce. * * @return A new {@link Nonce}. */ public Nonce increment() { return Sodium.dupAndIncrement(ptr, length, Nonce::new); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof Nonce)) { return false; } Nonce other = (Nonce) obj; return Sodium.sodium_memcmp(this.ptr, other.ptr, length) == 0; } @Override public int hashCode() { return Sodium.hashCode(ptr, length); } /** * @return The bytes of this nonce. */ public Bytes bytes() { return Bytes.wrap(bytesArray()); } /** * @return The bytes of this nonce. */ public byte[] bytesArray() { return Sodium.reify(ptr, length); } } private Pointer ctx; private AES256GCM(Key key) { checkArgument(key.ptr != null, "Key has been destroyed"); ctx = Sodium.malloc(Sodium.crypto_aead_aes256gcm_statebytes()); try { int rc = Sodium.crypto_aead_aes256gcm_beforenm(ctx, key.ptr); if (rc != 0) { throw new SodiumException("crypto_aead_aes256gcm_beforenm: failed with result " + rc); } } catch (Throwable e) { Sodium.sodium_free(ctx); ctx = null; throw e; } } /** * Pre-compute the expansion for the key. * *

* Note that the returned instance of {@link AES256GCM} should be closed using {@link #close()} (or * try-with-resources) to ensure timely release of the expanded key, which is held in native memory. * * @param key The key to precompute an expansion for. * @return A {@link AES256GCM} instance. * @throws UnsupportedOperationException If AES256-GSM support is not available. */ public static AES256GCM forKey(Key key) { requireNonNull(key); assertAvailable(); return new AES256GCM(key); } /** * Encrypt a message for a given key. * * @param message The message to encrypt. * @param key The key to encrypt for. * @param nonce A unique nonce. * @return The encrypted data. */ public static Bytes encrypt(Bytes message, Key key, Nonce nonce) { return Bytes.wrap(encrypt(message.toArrayUnsafe(), key, nonce)); } /** * Encrypt a message for a given key. * * @param message The message to encrypt. * @param key The key to encrypt for. * @param nonce A unique nonce. * @return The encrypted data. */ public static byte[] encrypt(byte[] message, Key key, Nonce nonce) { return encrypt(message, EMPTY_BYTES, key, nonce); } /** * Encrypt a message for a given key. * * @param message The message to encrypt. * @param data Extra non-confidential data that will be included with the encrypted payload. * @param key The key to encrypt for. * @param nonce A unique nonce. * @return The encrypted data. */ public static Bytes encrypt(Bytes message, Bytes data, Key key, Nonce nonce) { return Bytes.wrap(encrypt(message.toArrayUnsafe(), data.toArrayUnsafe(), key, nonce)); } /** * Encrypt a message for a given key. * * @param message The message to encrypt. * @param data Extra non-confidential data that will be included with the encrypted payload. * @param key The key to encrypt for. * @param nonce A unique nonce. * @return The encrypted data. * @throws UnsupportedOperationException If AES256-GSM support is not available. */ public static byte[] encrypt(byte[] message, byte[] data, Key key, Nonce nonce) { assertAvailable(); checkArgument(key.ptr != null, "Key has been destroyed"); byte[] cipherText = new byte[maxCombinedCypherTextLength(message)]; LongLongByReference cipherTextLen = new LongLongByReference(); int rc = Sodium.crypto_aead_aes256gcm_encrypt( cipherText, cipherTextLen, message, message.length, data, data.length, null, nonce.ptr, key.ptr); if (rc != 0) { throw new SodiumException("crypto_aead_aes256gcm_encrypt: failed with result " + rc); } return maybeSliceResult(cipherText, cipherTextLen, "crypto_aead_aes256gcm_encrypt"); } /** * Encrypt a message. * * @param message The message to encrypt. * @param nonce A unique nonce. * @return The encrypted data. */ public Bytes encrypt(Bytes message, Nonce nonce) { return Bytes.wrap(encrypt(message.toArrayUnsafe(), nonce)); } /** * Encrypt a message. * * @param message The message to encrypt. * @param nonce A unique nonce. * @return The encrypted data. */ public byte[] encrypt(byte[] message, Nonce nonce) { return encrypt(message, EMPTY_BYTES, nonce); } /** * Encrypt a message. * * @param message The message to encrypt. * @param data Extra non-confidential data that will be included with the encrypted payload. * @param nonce A unique nonce. * @return The encrypted data. */ public Bytes encrypt(Bytes message, Bytes data, Nonce nonce) { return Bytes.wrap(encrypt(message.toArrayUnsafe(), data.toArrayUnsafe(), nonce)); } /** * Encrypt a message. * * @param message The message to encrypt. * @param data Extra non-confidential data that will be included with the encrypted payload. * @param nonce A unique nonce. * @return The encrypted data. */ public byte[] encrypt(byte[] message, byte[] data, Nonce nonce) { assertOpen(); byte[] cipherText = new byte[maxCombinedCypherTextLength(message)]; LongLongByReference cipherTextLen = new LongLongByReference(); int rc = Sodium.crypto_aead_aes256gcm_encrypt_afternm( cipherText, cipherTextLen, message, message.length, data, data.length, null, nonce.ptr, ctx); if (rc != 0) { throw new SodiumException("crypto_aead_aes256gcm_encrypt_afternm: failed with result " + rc); } return maybeSliceResult(cipherText, cipherTextLen, "crypto_aead_aes256gcm_encrypt_afternm"); } private static int maxCombinedCypherTextLength(byte[] message) { long abytes = Sodium.crypto_aead_aes256gcm_abytes(); if (abytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_aead_aes256gcm_abytes: " + abytes + " is too large"); } return (int) abytes + message.length; } /** * Encrypt a message for a given key, generating a detached message authentication code. * * @param message The message to encrypt. * @param key The key to encrypt for. * @param nonce A unique nonce. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptDetached(Bytes message, Key key, Nonce nonce) { return encryptDetached(message.toArrayUnsafe(), key, nonce); } /** * Encrypt a message for a given key, generating a detached message authentication code. * * @param message The message to encrypt. * @param key The key to encrypt for. * @param nonce A unique nonce. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptDetached(byte[] message, Key key, Nonce nonce) { return encryptDetached(message, EMPTY_BYTES, key, nonce); } /** * Encrypt a message for a given key, generating a detached message authentication code. * * @param message The message to encrypt. * @param data Extra non-confidential data that will be included with the encrypted payload. * @param key The key to encrypt for. * @param nonce A unique nonce. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptDetached(Bytes message, Bytes data, Key key, Nonce nonce) { return encryptDetached(message.toArrayUnsafe(), data.toArrayUnsafe(), key, nonce); } /** * Encrypt a message for a given key, generating a detached message authentication code. * * @param message The message to encrypt. * @param data Extra non-confidential data that will be included with the encrypted payload. * @param key The key to encrypt for. * @param nonce A unique nonce. * @return The encrypted data and message authentication code. * @throws UnsupportedOperationException If AES256-GSM support is not available. */ public static DetachedEncryptionResult encryptDetached(byte[] message, byte[] data, Key key, Nonce nonce) { assertAvailable(); checkArgument(key.ptr != null, "Key has been destroyed"); byte[] cipherText = new byte[message.length]; long abytes = Sodium.crypto_aead_aes256gcm_abytes(); if (abytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_aead_aes256gcm_abytes: " + abytes + " is too large"); } byte[] mac = new byte[(int) abytes]; LongLongByReference macLen = new LongLongByReference(); int rc = Sodium.crypto_aead_aes256gcm_encrypt_detached( cipherText, mac, macLen, message, message.length, data, data.length, null, nonce.ptr, key.ptr); if (rc != 0) { throw new SodiumException("crypto_aead_aes256gcm_encrypt_detached: failed with result " + rc); } return new DefaultDetachedEncryptionResult( cipherText, maybeSliceResult(mac, macLen, "crypto_aead_aes256gcm_encrypt_detached")); } /** * Encrypt a message, generating a detached message authentication code. * * @param message The message to encrypt. * @param nonce A unique nonce. * @return The encrypted data and message authentication code. */ public DetachedEncryptionResult encryptDetached(Bytes message, Nonce nonce) { return encryptDetached(message.toArrayUnsafe(), nonce); } /** * Encrypt a message, generating a detached message authentication code. * * @param message The message to encrypt. * @param nonce A unique nonce. * @return The encrypted data and message authentication code. */ public DetachedEncryptionResult encryptDetached(byte[] message, Nonce nonce) { return encryptDetached(message, EMPTY_BYTES, nonce); } /** * Encrypt a message, generating a detached message authentication code. * * @param message The message to encrypt. * @param data Extra non-confidential data that will be included with the encrypted payload. * @param nonce A unique nonce. * @return The encrypted data and message authentication code. */ public DetachedEncryptionResult encryptDetached(Bytes message, Bytes data, Nonce nonce) { return encryptDetached(message.toArrayUnsafe(), data.toArrayUnsafe(), nonce); } /** * Encrypt a message, generating a detached message authentication code. * * @param message The message to encrypt. * @param data Extra non-confidential data that will be included with the encrypted payload. * @param nonce A unique nonce. * @return The encrypted data and message authentication code. */ public DetachedEncryptionResult encryptDetached(byte[] message, byte[] data, Nonce nonce) { assertOpen(); byte[] cipherText = new byte[message.length]; long abytes = Sodium.crypto_aead_aes256gcm_abytes(); if (abytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_aead_aes256gcm_abytes: " + abytes + " is too large"); } byte[] mac = new byte[(int) abytes]; LongLongByReference macLen = new LongLongByReference(); int rc = Sodium.crypto_aead_aes256gcm_encrypt_detached_afternm( cipherText, mac, macLen, message, message.length, data, data.length, null, nonce.ptr, ctx); if (rc != 0) { throw new SodiumException("crypto_aead_aes256gcm_encrypt_detached_afternm: failed with result " + rc); } return new DefaultDetachedEncryptionResult( cipherText, maybeSliceResult(mac, macLen, "crypto_aead_aes256gcm_encrypt_detached_afternm")); } /** * Decrypt a message using a given key. * * @param cipherText The cipher text to decrypt. * @param key The key to use for decryption. * @param nonce The nonce to use when decrypting. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decrypt(Bytes cipherText, Key key, Nonce nonce) { byte[] bytes = decrypt(cipherText.toArrayUnsafe(), key, nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a given key. * * @param cipherText The cipher text to decrypt. * @param key The key to use for decryption. * @param nonce The nonce to use when decrypting. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decrypt(byte[] cipherText, Key key, Nonce nonce) { return decrypt(cipherText, EMPTY_BYTES, key, nonce); } /** * Decrypt a message using a given key. * * @param cipherText The cipher text to decrypt. * @param data Extra non-confidential data that is included within the encrypted payload. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decrypt(Bytes cipherText, Bytes data, Key key, Nonce nonce) { byte[] bytes = decrypt(cipherText.toArrayUnsafe(), data.toArrayUnsafe(), key, nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a given key. * * @param cipherText The cipher text to decrypt. * @param data Extra non-confidential data that is included within the encrypted payload. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. * @throws UnsupportedOperationException If AES256-GSM support is not available. */ @Nullable public static byte[] decrypt(byte[] cipherText, byte[] data, Key key, Nonce nonce) { assertAvailable(); checkArgument(key.ptr != null, "Key has been destroyed"); byte[] clearText = new byte[maxClearTextLength(cipherText)]; LongLongByReference clearTextLen = new LongLongByReference(); int rc = Sodium.crypto_aead_aes256gcm_decrypt( clearText, clearTextLen, null, cipherText, cipherText.length, data, data.length, nonce.ptr, key.ptr); if (rc == -1) { return null; } if (rc != 0) { throw new SodiumException("crypto_aead_aes256gcm_encrypt: failed with result " + rc); } return maybeSliceResult(clearText, clearTextLen, "crypto_aead_aes256gcm_decrypt"); } /** * Decrypt a message. * * @param cipherText The cipher text to decrypt. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public Bytes decrypt(Bytes cipherText, Nonce nonce) { byte[] bytes = decrypt(cipherText.toArrayUnsafe(), nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message. * * @param cipherText The cipher text to decrypt. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public byte[] decrypt(byte[] cipherText, Nonce nonce) { return decrypt(cipherText, EMPTY_BYTES, nonce); } /** * Decrypt a message. * * @param cipherText The cipher text to decrypt. * @param data Extra non-confidential data that is included within the encrypted payload. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public Bytes decrypt(Bytes cipherText, Bytes data, Nonce nonce) { byte[] bytes = decrypt(cipherText.toArrayUnsafe(), data.toArrayUnsafe(), nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message. * * @param cipherText The cipher text to decrypt. * @param data Extra non-confidential data that is included within the encrypted payload. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public byte[] decrypt(byte[] cipherText, byte[] data, Nonce nonce) { assertOpen(); byte[] clearText = new byte[maxClearTextLength(cipherText)]; LongLongByReference clearTextLen = new LongLongByReference(); int rc = Sodium.crypto_aead_aes256gcm_decrypt_afternm( clearText, clearTextLen, null, cipherText, cipherText.length, data, data.length, nonce.ptr, ctx); if (rc == -1) { return null; } if (rc != 0) { throw new SodiumException("crypto_aead_aes256gcm_decrypt_afternm: failed with result " + rc); } return maybeSliceResult(clearText, clearTextLen, "crypto_aead_aes256gcm_decrypt_afternm"); } private static int maxClearTextLength(byte[] cipherText) { long abytes = Sodium.crypto_aead_aes256gcm_abytes(); if (abytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_aead_aes256gcm_abytes: " + abytes + " is too large"); } if (abytes > cipherText.length) { throw new IllegalArgumentException("cipherText is too short"); } return cipherText.length - ((int) abytes); } /** * Decrypt a message using a given key and a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decryptDetached(Bytes cipherText, Bytes mac, Key key, Nonce nonce) { byte[] bytes = decryptDetached(cipherText.toArrayUnsafe(), mac.toArrayUnsafe(), key, nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a given key and a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decryptDetached(byte[] cipherText, byte[] mac, Key key, Nonce nonce) { return decryptDetached(cipherText, mac, EMPTY_BYTES, key, nonce); } /** * Decrypt a message using a given key and a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param data Extra non-confidential data that is included within the encrypted payload. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decryptDetached(Bytes cipherText, Bytes mac, Bytes data, Key key, Nonce nonce) { byte[] bytes = decryptDetached(cipherText.toArrayUnsafe(), mac.toArrayUnsafe(), data.toArrayUnsafe(), key, nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a given key and a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param data Extra non-confidential data that is included within the encrypted payload. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. * @throws UnsupportedOperationException If AES256-GSM support is not available. */ @Nullable public static byte[] decryptDetached(byte[] cipherText, byte[] mac, byte[] data, Key key, Nonce nonce) { assertAvailable(); checkArgument(key.ptr != null, "Key has been destroyed"); long abytes = Sodium.crypto_aead_aes256gcm_abytes(); if (abytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_aead_aes256gcm_abytes: " + abytes + " is too large"); } if (mac.length != abytes) { throw new IllegalArgumentException("mac must be " + abytes + " bytes, got " + mac.length); } byte[] clearText = new byte[cipherText.length]; int rc = Sodium.crypto_aead_aes256gcm_decrypt_detached( clearText, null, cipherText, cipherText.length, mac, data, data.length, nonce.ptr, key.ptr); if (rc == -1) { return null; } if (rc != 0) { throw new SodiumException("crypto_aead_aes256gcm_encrypt: failed with result " + rc); } return clearText; } /** * Decrypt a message using a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public Bytes decryptDetached(Bytes cipherText, Bytes mac, Nonce nonce) { byte[] bytes = decryptDetached(cipherText.toArrayUnsafe(), mac.toArrayUnsafe(), nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public byte[] decryptDetached(byte[] cipherText, byte[] mac, Nonce nonce) { return decryptDetached(cipherText, mac, EMPTY_BYTES, nonce); } /** * Decrypt a message using a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param data Extra non-confidential data that is included within the encrypted payload. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public Bytes decryptDetached(Bytes cipherText, Bytes mac, Bytes data, Nonce nonce) { byte[] bytes = decryptDetached(cipherText.toArrayUnsafe(), mac.toArrayUnsafe(), data.toArrayUnsafe(), nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param data Extra non-confidential data that is included within the encrypted payload. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. * @throws UnsupportedOperationException If AES256-GSM support is not available. */ @Nullable public byte[] decryptDetached(byte[] cipherText, byte[] mac, byte[] data, Nonce nonce) { assertAvailable(); long abytes = Sodium.crypto_aead_aes256gcm_abytes(); if (abytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_aead_aes256gcm_abytes: " + abytes + " is too large"); } if (mac.length != abytes) { throw new IllegalArgumentException("mac must be " + abytes + " bytes, got " + mac.length); } byte[] clearText = new byte[cipherText.length]; int rc = Sodium.crypto_aead_aes256gcm_decrypt_detached_afternm( clearText, null, cipherText, cipherText.length, mac, data, data.length, nonce.ptr, ctx); if (rc == -1) { return null; } if (rc != 0) { throw new SodiumException("crypto_aead_aes256gcm_decrypt_detached_afternm: failed with result " + rc); } return clearText; } private void assertOpen() { if (ctx == null) { throw new IllegalStateException(getClass().getName() + ": already closed"); } } private static byte[] maybeSliceResult(byte[] bytes, LongLongByReference actualLength, String methodName) { if (actualLength.longValue() == bytes.length) { return bytes; } if (actualLength.longValue() > Integer.MAX_VALUE) { throw new SodiumException(methodName + ": result of length " + actualLength.longValue() + " is too large"); } return Arrays.copyOfRange(bytes, 0, actualLength.intValue()); } @Override public void close() { if (ctx != null) { Sodium.sodium_free(ctx); ctx = null; } } @Override protected void finalize() { close(); } } cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/Auth.java000066400000000000000000000167111341750772100265300ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import net.consensys.cava.bytes.Bytes; import javax.annotation.Nullable; import javax.security.auth.Destroyable; import jnr.ffi.Pointer; // Documentation copied under the ISC License, from // https://github.com/jedisct1/libsodium-doc/blob/424b7480562c2e063bc8c52c452ef891621c8480/secret-key_cryptography/secret-key_authentication.md /** * Secret-key authentication. * *

* These operations computes an authentication tag for a message and a secret key, and provides a way to verify that a * given tag is valid for a given message and a key. * *

* The function computing the tag is deterministic: the same (message, key) tuple will always produce the same output. * *

* However, even if the message is public, knowing the key is required in order to be able to compute a valid tag. * Therefore, the key should remain confidential. The tag, however, can be public. * *

* A typical use case is: * *

    *
  • {@code A} prepares a message, add an authentication tag, sends it to {@code B}
  • *
  • {@code A} doesn't store the message
  • *
  • Later on, {@code B} sends the message and the authentication tag to {@code A}
  • *
  • {@code A} uses the authentication tag to verify that it created this message.
  • *
* *

* This operation does not encrypt the message. It only computes and verifies an authentication tag. */ public final class Auth { private Auth() {} /** * An Auth key. */ public static final class Key implements Destroyable { @Nullable private Pointer ptr; private final int length; private Key(Pointer ptr, int length) { this.ptr = ptr; this.length = length; } @Override protected void finalize() { destroy(); } @Override public void destroy() { if (ptr != null) { Pointer p = ptr; ptr = null; Sodium.sodium_free(p); } } @Override public boolean isDestroyed() { return ptr == null; } /** * Create a {@link Key} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the key. * @return A key, based on the supplied bytes. */ public static Key fromBytes(Bytes bytes) { return fromBytes(bytes.toArrayUnsafe()); } /** * Create a {@link Key} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the key. * @return A key, based on the supplied bytes. */ public static Key fromBytes(byte[] bytes) { if (bytes.length != Sodium.crypto_auth_keybytes()) { throw new IllegalArgumentException( "key must be " + Sodium.crypto_auth_keybytes() + " bytes, got " + bytes.length); } return Sodium.dup(bytes, Key::new); } /** * Obtain the length of the key in bytes (32). * * @return The length of the key in bytes (32). */ public static int length() { long keybytes = Sodium.crypto_auth_keybytes(); if (keybytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_auth_keybytes: " + keybytes + " is too large"); } return (int) keybytes; } /** * Generate a new key using a random generator. * * @return A randomly generated key. */ public static Key random() { int length = length(); Pointer ptr = Sodium.malloc(length); try { // When support for 10.0.11 is dropped, use this instead //Sodium.crypto_auth_keygen(ptr); Sodium.randombytes_buf(ptr, length); return new Key(ptr, length); } catch (Throwable e) { Sodium.sodium_free(ptr); throw e; } } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof Key)) { return false; } checkState(ptr != null, "Key has been destroyed"); Key other = (Key) obj; return other.ptr != null && Sodium.sodium_memcmp(this.ptr, other.ptr, length) == 0; } @Override public int hashCode() { checkState(ptr != null, "Key has been destroyed"); return Sodium.hashCode(ptr, length); } /** * @return The bytes of this key. */ public Bytes bytes() { return Bytes.wrap(bytesArray()); } /** * @return The bytes of this key. */ public byte[] bytesArray() { checkState(ptr != null, "Key has been destroyed"); return Sodium.reify(ptr, length); } } /** * Create an authentication tag for a given input. * * @param input The input to generate an authentication tag for. * @param key A confidential key. * @return The authentication tag. */ public static Bytes auth(Bytes input, Key key) { return Bytes.wrap(auth(input.toArrayUnsafe(), key)); } /** * Create an authentication tag for a given input. * * @param input The input to generate an authentication tag for. * @param key A confidential key. * @return The authentication tag. */ public static byte[] auth(byte[] input, Key key) { checkArgument(key.ptr != null, "Key has been destroyed"); long abytes = Sodium.crypto_auth_bytes(); if (abytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_auth_bytes: " + abytes + " is too large"); } byte[] tag = new byte[(int) abytes]; int rc = Sodium.crypto_auth(tag, input, input.length, key.ptr); if (rc != 0) { throw new SodiumException("crypto_auth_bytes: failed with result " + rc); } return tag; } /** * Verify an input using an authentication tag. * * @param tag The authentication tag for the input. * @param input The input. * @param key A confidential key that was used for tag creation. * @return {@code true} if the tag correction authenticates the input (using the specified key). */ public static boolean verify(Bytes tag, Bytes input, Key key) { return verify(tag.toArrayUnsafe(), input.toArrayUnsafe(), key); } /** * Verify an input using an authentication tag. * * @param tag The authentication tag for the input. * @param input The input. * @param key A confidential key that was used for tag creation. * @return {@code true} if the tag correction authenticates the input (using the specified key). */ public static boolean verify(byte[] tag, byte[] input, Key key) { checkArgument(key.ptr != null, "Key has been destroyed"); long abytes = Sodium.crypto_auth_bytes(); if (tag.length != abytes) { throw new IllegalArgumentException("tag must be " + abytes + " bytes, got " + tag.length); } int rc = Sodium.crypto_auth_verify(tag, input, input.length, key.ptr); return (rc == 0); } } cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/Box.java000066400000000000000000001047461341750772100263650ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import net.consensys.cava.bytes.Bytes; import java.util.Objects; import javax.annotation.Nullable; import javax.security.auth.Destroyable; import jnr.ffi.Pointer; // Documentation copied under the ISC License, from // https://github.com/jedisct1/libsodium-doc/blob/424b7480562c2e063bc8c52c452ef891621c8480/public-key_cryptography/authenticated_encryption.md /** * Public-key authenticated encryption. * *

* Using public-key authenticated encryption, Bob can encrypt a confidential message specifically for Alice, using * Alice's public key. * *

* Using Bob's public key, Alice can compute a shared secret key. Using Alice's public key and his secret key, Bob can * compute the exact same shared secret key. That shared secret key can be used to verify that the encrypted message was * not tampered with, before eventually decrypting it. * *

* Alice only needs Bob's public key, the nonce and the ciphertext. Bob should never ever share his secret key, even * with Alice. * *

* And in order to send messages to Alice, Bob only needs Alice's public key. Alice should never ever share her secret * key either, even with Bob. * *

* Alice can reply to Bob using the same system, without having to generate a distinct key pair. * *

* The nonce doesn't have to be confidential, but it should be used with just one encryption for a particular pair of * public and secret keys. * *

* One easy way to generate a nonce is to use {@link Nonce#random()}, considering the size of the nonces the risk of any * random collisions is negligible. For some applications, if you wish to use nonces to detect missing messages or to * ignore replayed messages, it is also acceptable to use an incrementing counter as a nonce. * *

* When doing so you must ensure that the same value can never be re-used (for example you may have multiple threads or * even hosts generating messages using the same key pairs). * *

* As stated above, senders can decrypt their own messages, and compute a valid authentication tag for any messages * encrypted with a given shared secret key. This is generally not an issue for online protocols. * *

* This class depends 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'. */ public final class Box implements AutoCloseable { /** * A Box public key. */ public static final class PublicKey { private final Pointer ptr; private final int length; private PublicKey(Pointer ptr, int length) { this.ptr = ptr; this.length = length; } @Override protected void finalize() { Sodium.sodium_free(ptr); } /** * Create a {@link PublicKey} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the public key. * @return A public key. */ public static PublicKey fromBytes(Bytes bytes) { return fromBytes(bytes.toArrayUnsafe()); } /** * Create a {@link PublicKey} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the public key. * @return A public key. */ public static PublicKey fromBytes(byte[] bytes) { if (bytes.length != Sodium.crypto_box_publickeybytes()) { throw new IllegalArgumentException( "key must be " + Sodium.crypto_box_publickeybytes() + " bytes, got " + bytes.length); } return Sodium.dup(bytes, PublicKey::new); } /** * Obtain the length of the key in bytes (32). * * @return The length of the key in bytes (32). */ public static int length() { long keybytes = Sodium.crypto_box_publickeybytes(); if (keybytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_box_publickeybytes: " + keybytes + " is too large"); } return (int) keybytes; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof PublicKey)) { return false; } PublicKey other = (PublicKey) obj; return Sodium.sodium_memcmp(this.ptr, other.ptr, length) == 0; } @Override public int hashCode() { return Sodium.hashCode(ptr, length); } /** * @return The bytes of this key. */ public Bytes bytes() { return Bytes.wrap(bytesArray()); } /** * @return The bytes of this key. */ public byte[] bytesArray() { return Sodium.reify(ptr, length); } } /** * A Box secret key. */ public static final class SecretKey implements Destroyable { @Nullable private Pointer ptr; private final int length; private SecretKey(Pointer ptr, int length) { this.ptr = ptr; this.length = length; } @Override protected void finalize() { destroy(); } @Override public void destroy() { if (ptr != null) { Pointer p = ptr; ptr = null; Sodium.sodium_free(p); } } @Override public boolean isDestroyed() { return ptr == null; } /** * Create a {@link SecretKey} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the secret key. * @return A secret key. */ public static SecretKey fromBytes(Bytes bytes) { return fromBytes(bytes.toArrayUnsafe()); } /** * Create a {@link SecretKey} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the secret key. * @return A secret key. */ public static SecretKey fromBytes(byte[] bytes) { if (bytes.length != Sodium.crypto_box_secretkeybytes()) { throw new IllegalArgumentException( "key must be " + Sodium.crypto_box_secretkeybytes() + " bytes, got " + bytes.length); } return Sodium.dup(bytes, SecretKey::new); } /** * Obtain the length of the key in bytes (32). * * @return The length of the key in bytes (32). */ public static int length() { long keybytes = Sodium.crypto_box_secretkeybytes(); if (keybytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_box_secretkeybytes: " + keybytes + " is too large"); } return (int) keybytes; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof SecretKey)) { return false; } checkState(ptr != null, "SecretKey has been destroyed"); SecretKey other = (SecretKey) obj; return other.ptr != null && Sodium.sodium_memcmp(this.ptr, other.ptr, length) == 0; } @Override public int hashCode() { checkState(ptr != null, "SecretKey has been destroyed"); return Sodium.hashCode(ptr, length); } /** * @return The bytes of this key. */ public Bytes bytes() { return Bytes.wrap(bytesArray()); } /** * @return The bytes of this key. */ public byte[] bytesArray() { checkState(ptr != null, "SecretKey has been destroyed"); return Sodium.reify(ptr, length); } } /** * A Box key pair seed. */ public static final class Seed { private final Pointer ptr; private final int length; private Seed(Pointer ptr, int length) { this.ptr = ptr; this.length = length; } @Override protected void finalize() { Sodium.sodium_free(ptr); } /** * Create a {@link Seed} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the seed. * @return A seed. */ public static Seed fromBytes(Bytes bytes) { return fromBytes(bytes.toArrayUnsafe()); } /** * Create a {@link Seed} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the seed. * @return A seed. */ public static Seed fromBytes(byte[] bytes) { if (bytes.length != Sodium.crypto_box_seedbytes()) { throw new IllegalArgumentException( "key must be " + Sodium.crypto_box_seedbytes() + " bytes, got " + bytes.length); } return Sodium.dup(bytes, Seed::new); } /** * Obtain the length of the seed in bytes (32). * * @return The length of the seed in bytes (32). */ public static int length() { long seedbytes = Sodium.crypto_box_seedbytes(); if (seedbytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_box_seedbytes: " + seedbytes + " is too large"); } return (int) seedbytes; } /** * Generate a new {@link Seed} using a random generator. * * @return A randomly generated seed. */ public static Seed random() { return Sodium.randomBytes(length(), Seed::new); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof Seed)) { return false; } Seed other = (Seed) obj; return Sodium.sodium_memcmp(this.ptr, other.ptr, length) == 0; } @Override public int hashCode() { return Sodium.hashCode(ptr, length); } /** * @return The bytes of this seed. */ public Bytes bytes() { return Bytes.wrap(bytesArray()); } /** * @return The bytes of this seed. */ public byte[] bytesArray() { return Sodium.reify(ptr, length); } } /** * A Box key pair. */ public static final class KeyPair { private final PublicKey publicKey; private final SecretKey secretKey; /** * Create a {@link KeyPair} from pair of keys. * * @param publicKey The bytes for the public key. * @param secretKey The bytes for the secret key. */ public KeyPair(PublicKey publicKey, SecretKey secretKey) { this.publicKey = publicKey; this.secretKey = secretKey; } /** * Create a {@link KeyPair} from an array of secret key bytes. * * @param secretKey The secret key. * @return A {@link KeyPair}. */ public static KeyPair forSecretKey(SecretKey secretKey) { checkArgument(secretKey.ptr != null, "SecretKey has been destroyed"); return Sodium.scalarMultBase(secretKey.ptr, SecretKey.length(), (ptr, len) -> { int publicKeyLength = PublicKey.length(); if (len != publicKeyLength) { throw new IllegalStateException( "Public key length " + publicKeyLength + " is not same as generated key length " + len); } return new KeyPair(new PublicKey(ptr, publicKeyLength), secretKey); }); } /** * Generate a new key using a random generator. * * @return A randomly generated key pair. */ public static KeyPair random() { int publicKeyLength = PublicKey.length(); Pointer publicKey = Sodium.malloc(publicKeyLength); Pointer secretKey = null; try { int secretKeyLength = SecretKey.length(); secretKey = Sodium.malloc(secretKeyLength); int rc = Sodium.crypto_box_keypair(publicKey, secretKey); if (rc != 0) { throw new SodiumException("crypto_box_keypair: failed with result " + rc); } PublicKey pk = new PublicKey(publicKey, publicKeyLength); publicKey = null; SecretKey sk = new SecretKey(secretKey, secretKeyLength); secretKey = null; return new KeyPair(pk, sk); } catch (Throwable e) { if (publicKey != null) { Sodium.sodium_free(publicKey); } if (secretKey != null) { Sodium.sodium_free(secretKey); } throw e; } } /** * Generate a new key using a seed. * * @param seed A seed. * @return The generated key pair. */ public static KeyPair fromSeed(Seed seed) { int publicKeyLength = PublicKey.length(); Pointer publicKey = Sodium.malloc(publicKeyLength); Pointer secretKey = null; try { int secretKeyLength = SecretKey.length(); secretKey = Sodium.malloc(secretKeyLength); int rc = Sodium.crypto_box_seed_keypair(publicKey, secretKey, seed.ptr); if (rc != 0) { throw new SodiumException("crypto_box_keypair: failed with result " + rc); } PublicKey pk = new PublicKey(publicKey, publicKeyLength); publicKey = null; SecretKey sk = new SecretKey(secretKey, secretKeyLength); secretKey = null; return new KeyPair(pk, sk); } catch (Throwable e) { if (publicKey != null) { Sodium.sodium_free(publicKey); } if (secretKey != null) { Sodium.sodium_free(secretKey); } throw e; } } /** * @return The public key of the key pair. */ public PublicKey publicKey() { return publicKey; } /** * @return The secret key of the key pair. */ public SecretKey secretKey() { return secretKey; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof KeyPair)) { return false; } KeyPair other = (KeyPair) obj; return this.publicKey.equals(other.publicKey) && this.secretKey.equals(other.secretKey); } @Override public int hashCode() { return Objects.hash(publicKey, secretKey); } } /** * A Box nonce. */ public static final class Nonce { private final Pointer ptr; private final int length; private Nonce(Pointer ptr, int length) { this.ptr = ptr; this.length = length; } @Override protected void finalize() { Sodium.sodium_free(ptr); } /** * Create a {@link Nonce} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the nonce. * @return A nonce, based on these bytes. */ public static Nonce fromBytes(Bytes bytes) { return fromBytes(bytes.toArrayUnsafe()); } /** * Create a {@link Nonce} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the nonce. * @return A nonce, based on these bytes. */ public static Nonce fromBytes(byte[] bytes) { if (bytes.length != Sodium.crypto_box_noncebytes()) { throw new IllegalArgumentException( "nonce must be " + Sodium.crypto_box_noncebytes() + " bytes, got " + bytes.length); } return Sodium.dup(bytes, Nonce::new); } /** * Obtain the length of the nonce in bytes (24). * * @return The length of the nonce in bytes (24). */ public static int length() { long npubbytes = Sodium.crypto_box_noncebytes(); if (npubbytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_box_noncebytes: " + npubbytes + " is too large"); } return (int) npubbytes; } /** * Generate a new {@link Nonce} using a random generator. * * @return A randomly generated nonce. */ public static Nonce random() { return Sodium.randomBytes(length(), Nonce::new); } /** * Increment this nonce. * *

* Note that this is not synchronized. If multiple threads are creating encrypted messages and incrementing this * nonce, then external synchronization is required to ensure no two encrypt operations use the same nonce. * * @return A new {@link Nonce}. */ public Nonce increment() { return Sodium.dupAndIncrement(ptr, length, Nonce::new); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof Nonce)) { return false; } Nonce other = (Nonce) obj; return Sodium.sodium_memcmp(this.ptr, other.ptr, length) == 0; } @Override public int hashCode() { return Sodium.hashCode(ptr, length); } /** * @return The bytes of this nonce. */ public Bytes bytes() { return Bytes.wrap(bytesArray()); } /** * @return The bytes of this nonce. */ public byte[] bytesArray() { return Sodium.reify(ptr, length); } } private Pointer ctx; private Box(PublicKey publicKey, SecretKey secretKey) { checkArgument(secretKey.ptr != null, "SecretKey has been destroyed"); ctx = Sodium.malloc(Sodium.crypto_box_beforenmbytes()); try { int rc = Sodium.crypto_box_beforenm(ctx, publicKey.ptr, secretKey.ptr); if (rc != 0) { throw new SodiumException("crypto_box_beforenm: failed with result " + rc); } } catch (Throwable e) { Sodium.sodium_free(ctx); ctx = null; throw e; } } /** * Precompute the shared key for a given sender and receiver. * *

* Note that the returned instance of {@link Box} should be closed using {@link #close()} (or try-with-resources) to * ensure timely release of the shared key, which is held in native memory. * * @param receiver The public key of the receiver. * @param sender The secret key of the sender. * @return A {@link Box} instance. */ public static Box forKeys(PublicKey receiver, SecretKey sender) { return new Box(receiver, sender); } /** * Encrypt a message for a given key. * * @param message The message to encrypt. * @param receiver The public key of the receiver. * @param sender The secret key of the sender. * @param nonce A unique nonce. * @return The encrypted data. */ public static Bytes encrypt(Bytes message, PublicKey receiver, SecretKey sender, Nonce nonce) { return Bytes.wrap(encrypt(message.toArrayUnsafe(), receiver, sender, nonce)); } /** * Encrypt a message for a given key. * * @param message The message to encrypt. * @param receiver The public key of the receiver. * @param sender The secret key of the sender. * @param nonce A unique nonce. * @return The encrypted data. */ public static byte[] encrypt(byte[] message, PublicKey receiver, SecretKey sender, Nonce nonce) { checkArgument(sender.ptr != null, "SecretKey has been destroyed"); byte[] cipherText = new byte[combinedCypherTextLength(message)]; int rc = Sodium.crypto_box_easy(cipherText, message, message.length, nonce.ptr, receiver.ptr, sender.ptr); if (rc != 0) { throw new SodiumException("crypto_box_easy: failed with result " + rc); } return cipherText; } /** * Encrypt a message for a given key. * * @param message The message to encrypt. * @param nonce A unique nonce. * @return The encrypted data. */ public Bytes encrypt(Bytes message, Nonce nonce) { return Bytes.wrap(encrypt(message.toArrayUnsafe(), nonce)); } /** * Encrypt a message for a given key. * * @param message The message to encrypt. * @param nonce A unique nonce. * @return The encrypted data. */ public byte[] encrypt(byte[] message, Nonce nonce) { assertOpen(); byte[] cipherText = new byte[combinedCypherTextLength(message)]; int rc = Sodium.crypto_box_easy_afternm(cipherText, message, message.length, nonce.ptr, ctx); if (rc != 0) { throw new SodiumException("crypto_box_easy_afternm: failed with result " + rc); } return cipherText; } /** * Encrypt a sealed message for a given key. * *

* Sealed boxes are designed to anonymously send messages to a recipient given its public key. * *

* Only the recipient can decrypt these messages, using its private key. While the recipient can verify the integrity * of the message, it cannot verify the identity of the sender. * *

* A message is encrypted using an ephemeral key pair, whose secret part is destroyed right after the encryption * process. * *

* Without knowing the secret key used for a given message, the sender cannot decrypt its own message later. And * without additional data, a message cannot be correlated with the identity of its sender. * * @param message The message to encrypt. * @param receiver The public key of the receiver. * @return The encrypted data. */ public static Bytes encryptSealed(Bytes message, PublicKey receiver) { return Bytes.wrap(encryptSealed(message.toArrayUnsafe(), receiver)); } /** * Encrypt a sealed message for a given key. * *

* Sealed boxes are designed to anonymously send messages to a recipient given its public key. * *

* Only the recipient can decrypt these messages, using its private key. While the recipient can verify the integrity * of the message, it cannot verify the identity of the sender. * *

* A message is encrypted using an ephemeral key pair, whose secret part is destroyed right after the encryption * process. * *

* Without knowing the secret key used for a given message, the sender cannot decrypt its own message later. And * without additional data, a message cannot be correlated with the identity of its sender. * * @param message The message to encrypt. * @param receiver The public key of the receiver. * @return The encrypted data. */ public static byte[] encryptSealed(byte[] message, PublicKey receiver) { long sealbytes = Sodium.crypto_box_sealbytes(); if (sealbytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_box_sealbytes: " + sealbytes + " is too large"); } byte[] cipherText = new byte[(int) sealbytes + message.length]; int rc = Sodium.crypto_box_seal(cipherText, message, message.length, receiver.ptr); if (rc != 0) { throw new SodiumException("crypto_box_seal: failed with result " + rc); } return cipherText; } private static int combinedCypherTextLength(byte[] message) { long macbytes = Sodium.crypto_box_macbytes(); if (macbytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_box_macbytes: " + macbytes + " is too large"); } return (int) macbytes + message.length; } /** * Encrypt a message for a given key, generating a detached message authentication code. * * @param message The message to encrypt. * @param receiver The public key of the receiver. * @param sender The secret key of the sender. * @param nonce A unique nonce. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptDetached( Bytes message, PublicKey receiver, SecretKey sender, Nonce nonce) { return encryptDetached(message.toArrayUnsafe(), receiver, sender, nonce); } /** * Encrypt a message for a given key, generating a detached message authentication code. * * @param message The message to encrypt. * @param receiver The public key of the receiver. * @param sender The secret key of the sender. * @param nonce A unique nonce. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptDetached( byte[] message, PublicKey receiver, SecretKey sender, Nonce nonce) { checkArgument(sender.ptr != null, "SecretKey has been destroyed"); byte[] cipherText = new byte[message.length]; long macbytes = Sodium.crypto_box_macbytes(); if (macbytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_box_macbytes: " + macbytes + " is too large"); } byte[] mac = new byte[(int) macbytes]; int rc = Sodium.crypto_box_detached(cipherText, mac, message, message.length, nonce.ptr, receiver.ptr, sender.ptr); if (rc != 0) { throw new SodiumException("crypto_box_detached: failed with result " + rc); } return new DefaultDetachedEncryptionResult(cipherText, mac); } /** * Encrypt a message, generating a detached message authentication code. * * @param message The message to encrypt. * @param nonce A unique nonce. * @return The encrypted data and message authentication code. */ public DetachedEncryptionResult encryptDetached(Bytes message, Nonce nonce) { return encryptDetached(message.toArrayUnsafe(), nonce); } /** * Encrypt a message, generating a detached message authentication code. * * @param message The message to encrypt. * @param nonce A unique nonce. * @return The encrypted data and message authentication code. */ public DetachedEncryptionResult encryptDetached(byte[] message, Nonce nonce) { assertOpen(); byte[] cipherText = new byte[message.length]; long macbytes = Sodium.crypto_box_macbytes(); if (macbytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_box_macbytes: " + macbytes + " is too large"); } byte[] mac = new byte[(int) macbytes]; int rc = Sodium.crypto_box_detached_afternm(cipherText, mac, message, message.length, nonce.ptr, ctx); if (rc != 0) { throw new SodiumException("crypto_box_detached_afternm: failed with result " + rc); } return new DefaultDetachedEncryptionResult(cipherText, mac); } /** * Decrypt a message using a given key. * * @param cipherText The cipher text to decrypt. * @param sender The public key of the sender. * @param receiver The secret key of the receiver. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decrypt(Bytes cipherText, PublicKey sender, SecretKey receiver, Nonce nonce) { byte[] bytes = decrypt(cipherText.toArrayUnsafe(), sender, receiver, nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a given key. * * @param cipherText The cipher text to decrypt. * @param sender The public key of the sender. * @param receiver The secret key of the receiver. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decrypt(byte[] cipherText, PublicKey sender, SecretKey receiver, Nonce nonce) { checkArgument(receiver.ptr != null, "SecretKey has been destroyed"); byte[] clearText = new byte[clearTextLength(cipherText)]; int rc = Sodium.crypto_box_open_easy(clearText, cipherText, cipherText.length, nonce.ptr, sender.ptr, receiver.ptr); if (rc == -1) { return null; } if (rc != 0) { throw new SodiumException("crypto_box_open_easy: failed with result " + rc); } return clearText; } /** * Decrypt a message using a given key. * * @param cipherText The cipher text to decrypt. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public Bytes decrypt(Bytes cipherText, Nonce nonce) { byte[] bytes = decrypt(cipherText.toArrayUnsafe(), nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a given key. * * @param cipherText The cipher text to decrypt. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public byte[] decrypt(byte[] cipherText, Nonce nonce) { assertOpen(); byte[] clearText = new byte[clearTextLength(cipherText)]; int rc = Sodium.crypto_box_open_easy_afternm(clearText, cipherText, cipherText.length, nonce.ptr, ctx); if (rc == -1) { return null; } if (rc != 0) { throw new SodiumException("crypto_box_open_easy_afternm: failed with result " + rc); } return clearText; } private static int clearTextLength(byte[] cipherText) { long macbytes = Sodium.crypto_box_macbytes(); if (macbytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_box_macbytes: " + macbytes + " is too large"); } if (macbytes > cipherText.length) { throw new IllegalArgumentException("cipherText is too short"); } return cipherText.length - ((int) macbytes); } /** * Decrypt a sealed message using a given key. * * @param cipherText The cipher text to decrypt. * @param sender The public key of the sender. * @param receiver The secret key of the receiver. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decryptSealed(Bytes cipherText, PublicKey sender, SecretKey receiver) { byte[] bytes = decryptSealed(cipherText.toArrayUnsafe(), sender, receiver); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a sealed message using a given key. * * @param cipherText The cipher text to decrypt. * @param sender The public key of the sender. * @param receiver The secret key of the receiver. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decryptSealed(byte[] cipherText, PublicKey sender, SecretKey receiver) { checkArgument(receiver.ptr != null, "SecretKey has been destroyed"); long sealbytes = Sodium.crypto_box_sealbytes(); if (sealbytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_box_sealbytes: " + sealbytes + " is too large"); } if (sealbytes > cipherText.length) { throw new IllegalArgumentException("cipherText is too short"); } byte[] clearText = new byte[cipherText.length - ((int) sealbytes)]; int rc = Sodium.crypto_box_seal_open(clearText, cipherText, cipherText.length, sender.ptr, receiver.ptr); if (rc == -1) { return null; } if (rc != 0) { throw new SodiumException("crypto_box_seal_open: failed with result " + rc); } return clearText; } /** * Decrypt a message using a given key and a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param sender The public key of the sender. * @param receiver The secret key of the receiver. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decryptDetached(Bytes cipherText, Bytes mac, PublicKey sender, SecretKey receiver, Nonce nonce) { byte[] bytes = decryptDetached(cipherText.toArrayUnsafe(), mac.toArrayUnsafe(), sender, receiver, nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a given key and a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param sender The public key of the sender. * @param receiver The secret key of the receiver. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decryptDetached( byte[] cipherText, byte[] mac, PublicKey sender, SecretKey receiver, Nonce nonce) { checkArgument(receiver.ptr != null, "SecretKey has been destroyed"); long macbytes = Sodium.crypto_box_macbytes(); if (macbytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_box_macbytes: " + macbytes + " is too large"); } if (mac.length != macbytes) { throw new IllegalArgumentException("mac must be " + macbytes + " bytes, got " + mac.length); } byte[] clearText = new byte[cipherText.length]; int rc = Sodium .crypto_box_open_detached(clearText, cipherText, mac, cipherText.length, nonce.ptr, sender.ptr, receiver.ptr); if (rc == -1) { return null; } if (rc != 0) { throw new SodiumException("crypto_box_open_detached: failed with result " + rc); } return clearText; } /** * Decrypt a message using a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public Bytes decryptDetached(Bytes cipherText, Bytes mac, Nonce nonce) { byte[] bytes = decryptDetached(cipherText.toArrayUnsafe(), mac.toArrayUnsafe(), nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public byte[] decryptDetached(byte[] cipherText, byte[] mac, Nonce nonce) { long macbytes = Sodium.crypto_box_macbytes(); if (macbytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_box_macbytes: " + macbytes + " is too large"); } if (mac.length != macbytes) { throw new IllegalArgumentException("mac must be " + macbytes + " bytes, got " + mac.length); } byte[] clearText = new byte[cipherText.length]; int rc = Sodium.crypto_box_open_detached_afternm(clearText, cipherText, mac, cipherText.length, nonce.ptr, ctx); if (rc == -1) { return null; } if (rc != 0) { throw new SodiumException("crypto_box_open_detached_afternm: failed with result " + rc); } return clearText; } private void assertOpen() { if (ctx == null) { throw new IllegalStateException(getClass().getName() + ": already closed"); } } @Override public void close() { if (ctx != null) { Sodium.sodium_free(ctx); ctx = null; } } @Override protected void finalize() { close(); } } DefaultDetachedEncryptionResult.java000066400000000000000000000023151341750772100340230ustar00rootroot00000000000000cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import net.consensys.cava.bytes.Bytes; final class DefaultDetachedEncryptionResult implements DetachedEncryptionResult { private final byte[] cipherText; private final byte[] mac; DefaultDetachedEncryptionResult(byte[] cipherText, byte[] mac) { this.cipherText = cipherText; this.mac = mac; } @Override public Bytes cipherText() { return Bytes.wrap(cipherText); } @Override public byte[] cipherTextArray() { return cipherText; } @Override public Bytes mac() { return Bytes.wrap(mac); } @Override public byte[] macArray() { return mac; } } cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/DetachedEncryptionResult.java000066400000000000000000000020351341750772100325740ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import net.consensys.cava.bytes.Bytes; /** * The result from a detached encryption. */ public interface DetachedEncryptionResult { /** * @return The cipher text. */ Bytes cipherText(); /** * @return The cipher text. */ byte[] cipherTextArray(); /** * @return The message authentication code. */ Bytes mac(); /** * @return The message authentication code. */ byte[] macArray(); } cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/KeyDerivation.java000066400000000000000000000231451341750772100304030ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import static com.google.common.base.Preconditions.checkState; import static java.nio.charset.StandardCharsets.UTF_8; import net.consensys.cava.bytes.Bytes; import java.util.Arrays; import javax.annotation.Nullable; import javax.security.auth.Destroyable; import jnr.ffi.Pointer; /** * Key derivation. * *

* Multiple secret subkeys can be derived from a single master key. * *

* Given the master key and a key identifier, a subkey can be deterministically computed. However, given a subkey, an * attacker cannot compute the master key nor any other subkeys. */ public final class KeyDerivation { /** * Check if Sodium and key derivation support is available. * *

* Key derivation is supported in sodium native library version >= 10.0.12. * * @return {@code true} if Sodium and key derivation support is available. */ public static boolean isAvailable() { try { return Sodium.supportsVersion(Sodium.VERSION_10_0_12); } catch (UnsatisfiedLinkError e) { return false; } } private static void assertAvailable() { if (!isAvailable()) { throw new UnsupportedOperationException( "Sodium key derivation is not available (requires sodium native library version >= 10.0.12)"); } } /** * A KeyDerivation master key. */ public static final class MasterKey implements Destroyable { @Nullable private Pointer ptr; private final int length; private MasterKey(Pointer ptr, int length) { this.ptr = ptr; this.length = length; } @Override protected void finalize() { destroy(); } @Override public void destroy() { if (ptr != null) { Pointer p = ptr; ptr = null; Sodium.sodium_free(p); } } @Override public boolean isDestroyed() { return ptr == null; } /** * Create a {@link MasterKey} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the key. * @return A key, based on the supplied bytes. */ public static MasterKey fromBytes(Bytes bytes) { return fromBytes(bytes.toArrayUnsafe()); } /** * Create a {@link MasterKey} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the key. * @return A key, based on the supplied bytes. * @throws UnsupportedOperationException If key derivation support is not available. */ public static MasterKey fromBytes(byte[] bytes) { assertAvailable(); if (bytes.length != Sodium.crypto_kdf_keybytes()) { throw new IllegalArgumentException( "key must be " + Sodium.crypto_kdf_keybytes() + " bytes, got " + bytes.length); } return Sodium.dup(bytes, MasterKey::new); } /** * Obtain the length of the key in bytes (32). * * @return The length of the key in bytes (32). * @throws UnsupportedOperationException If key derivation support is not available. */ public static int length() { assertAvailable(); long keybytes = Sodium.crypto_kdf_keybytes(); if (keybytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_kdf_keybytes: " + keybytes + " is too large"); } return (int) keybytes; } /** * Generate a new key using a random generator. * * @return A randomly generated key. * @throws UnsupportedOperationException If key derivation support is not available. */ public static MasterKey random() { assertAvailable(); int length = length(); Pointer ptr = Sodium.malloc(length); try { // When support for 10.0.11 is dropped, use this instead //Sodium.crypto_kdf_keygen(ptr); Sodium.randombytes_buf(ptr, length); return new MasterKey(ptr, length); } catch (Throwable e) { Sodium.sodium_free(ptr); throw e; } } /** * Derive a sub key. * * @param length The length of the sub key, which must be between {@link #minSubKeyLength()} and * {@link #maxSubKeyLength()}. * @param subkeyId The id for the sub key. * @param context The context for the sub key, which must be of length {@link #contextLength()}. * @return The derived sub key. */ public Bytes deriveKey(int length, long subkeyId, byte[] context) { return Bytes.wrap(deriveKeyArray(length, subkeyId, context)); } /** * Derive a sub key. * * @param length The length of the sub key, which must be between {@link #minSubKeyLength()} and * {@link #maxSubKeyLength()}. * @param subkeyId The id for the sub key. * @param context The context for the sub key, which must be of length {@link #contextLength()}. * @return The derived sub key. */ public byte[] deriveKeyArray(int length, long subkeyId, byte[] context) { checkState(ptr != null, "MasterKey has been destroyed"); assertSubKeyLength(length); assertContextLength(context); byte[] subKey = new byte[length]; int rc = Sodium.crypto_kdf_derive_from_key(subKey, subKey.length, subkeyId, context, ptr); if (rc != 0) { throw new SodiumException("crypto_kdf_derive_from_key: failed with result " + rc); } return subKey; } /** * Derive a sub key. * * @param length The length of the subkey. * @param subkeyId The id for the subkey. * @param context The context for the sub key, which must be of length ≤ {@link #contextLength()}. * @return The derived sub key. */ public Bytes deriveKey(int length, long subkeyId, String context) { return Bytes.wrap(deriveKeyArray(length, subkeyId, context)); } /** * Derive a sub key. * * @param length The length of the subkey. * @param subkeyId The id for the subkey. * @param context The context for the sub key, which must be of length ≤ {@link #contextLength()}. * @return The derived sub key. */ public byte[] deriveKeyArray(int length, long subkeyId, String context) { int contextLen = contextLength(); byte[] contextBytes = context.getBytes(UTF_8); if (context.length() > contextLen) { throw new IllegalArgumentException("context must be " + contextLen + " bytes, got " + context.length()); } byte[] ctx; if (contextBytes.length == contextLen) { ctx = contextBytes; } else { ctx = Arrays.copyOf(contextBytes, contextLen); } return deriveKeyArray(length, subkeyId, ctx); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof MasterKey)) { return false; } checkState(ptr != null, "MasterKey has been destroyed"); MasterKey other = (MasterKey) obj; return other.ptr != null && Sodium.sodium_memcmp(this.ptr, other.ptr, length) == 0; } @Override public int hashCode() { checkState(ptr != null, "MasterKey has been destroyed"); return Sodium.hashCode(ptr, length); } /** * @return The bytes of this key. */ public Bytes bytes() { return Bytes.wrap(bytesArray()); } /** * @return The bytes of this key. */ public byte[] bytesArray() { checkState(ptr != null, "MasterKey has been destroyed"); return Sodium.reify(ptr, length); } } /** * @return The required length for the context (8). */ public static int contextLength() { long contextbytes = Sodium.crypto_kdf_contextbytes(); if (contextbytes > Integer.MAX_VALUE) { throw new IllegalArgumentException("crypto_kdf_bytes_min: " + contextbytes + " is too large"); } return (int) contextbytes; } /** * @return The minimum length for a new sub key (16). */ public static int minSubKeyLength() { long length = Sodium.crypto_kdf_bytes_min(); if (length > Integer.MAX_VALUE) { throw new IllegalArgumentException("crypto_kdf_bytes_min: " + length + " is too large"); } return (int) length; } /** * @return The maximum length for a new sub key (64). */ public static int maxSubKeyLength() { long length = Sodium.crypto_kdf_bytes_max(); if (length > Integer.MAX_VALUE) { throw new IllegalArgumentException("crypto_kdf_bytes_max: " + length + " is too large"); } return (int) length; } private static void assertContextLength(byte[] context) { long contextBytes = Sodium.crypto_kdf_contextbytes(); if (context.length != contextBytes) { throw new IllegalArgumentException("context must be " + contextBytes + " bytes, got " + context.length); } } private static void assertSubKeyLength(int length) { long minLength = Sodium.crypto_kdf_bytes_min(); long maxLength = Sodium.crypto_kdf_bytes_max(); if (length < minLength || length > maxLength) { throw new IllegalArgumentException("length is out of range [" + minLength + ", " + maxLength + "]"); } } } cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/KeyExchange.java000066400000000000000000000447601341750772100300270ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import net.consensys.cava.bytes.Bytes; import java.util.Objects; import javax.annotation.Nullable; import javax.security.auth.Destroyable; import jnr.ffi.Pointer; /** * Key exchange. * *

* Allows two parties can securely compute a set of shared keys using their peer's public key and their own secret key. */ public final class KeyExchange { /** * A KeyExchange public key. */ public static final class PublicKey { private final Pointer ptr; private final int length; private PublicKey(Pointer ptr, int length) { this.ptr = ptr; this.length = length; } @Override protected void finalize() { Sodium.sodium_free(ptr); } /** * Create a {@link PublicKey} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the public key. * @return A public key. */ public static PublicKey fromBytes(Bytes bytes) { return fromBytes(bytes.toArrayUnsafe()); } /** * Create a {@link PublicKey} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the public key. * @return A public key. */ public static PublicKey fromBytes(byte[] bytes) { if (bytes.length != Sodium.crypto_kx_publickeybytes()) { throw new IllegalArgumentException( "key must be " + Sodium.crypto_kx_publickeybytes() + " bytes, got " + bytes.length); } return Sodium.dup(bytes, PublicKey::new); } /** * Obtain the length of the key in bytes (32). * * @return The length of the key in bytes (32). */ public static int length() { long keybytes = Sodium.crypto_kx_publickeybytes(); if (keybytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_kx_publickeybytes: " + keybytes + " is too large"); } return (int) keybytes; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof PublicKey)) { return false; } PublicKey other = (PublicKey) obj; return Sodium.sodium_memcmp(this.ptr, other.ptr, length) == 0; } @Override public int hashCode() { return Sodium.hashCode(ptr, length); } /** * @return The bytes of this key. */ public Bytes bytes() { return Bytes.wrap(bytesArray()); } /** * @return The bytes of this key. */ public byte[] bytesArray() { return Sodium.reify(ptr, length); } } /** * A KeyExchange secret key. */ public static final class SecretKey implements Destroyable { @Nullable private Pointer ptr; private final int length; private SecretKey(Pointer ptr, int length) { this.ptr = ptr; this.length = length; } @Override protected void finalize() { destroy(); } @Override public void destroy() { if (ptr != null) { Pointer p = ptr; ptr = null; Sodium.sodium_free(p); } } @Override public boolean isDestroyed() { return ptr == null; } /** * Create a {@link SecretKey} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the secret key. * @return A secret key. */ public static SecretKey fromBytes(Bytes bytes) { return fromBytes(bytes.toArrayUnsafe()); } /** * Create a {@link SecretKey} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the secret key. * @return A secret key. */ public static SecretKey fromBytes(byte[] bytes) { if (bytes.length != Sodium.crypto_kx_secretkeybytes()) { throw new IllegalArgumentException( "key must be " + Sodium.crypto_kx_secretkeybytes() + " bytes, got " + bytes.length); } return Sodium.dup(bytes, SecretKey::new); } /** * Obtain the length of the key in bytes (32). * * @return The length of the key in bytes (32). */ public static int length() { long keybytes = Sodium.crypto_kx_secretkeybytes(); if (keybytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_kx_secretkeybytes: " + keybytes + " is too large"); } return (int) keybytes; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof SecretKey)) { return false; } checkState(ptr != null, "SecretKey has been destroyed"); SecretKey other = (SecretKey) obj; return other.ptr != null && Sodium.sodium_memcmp(this.ptr, other.ptr, length) == 0; } @Override public int hashCode() { checkState(ptr != null, "SecretKey has been destroyed"); return Sodium.hashCode(ptr, length); } /** * @return The bytes of this key. */ public Bytes bytes() { return Bytes.wrap(bytesArray()); } /** * @return The bytes of this key. */ public byte[] bytesArray() { checkState(ptr != null, "SecretKey has been destroyed"); return Sodium.reify(ptr, length); } } /** * A KeyExchange key pair seed. */ public static final class Seed { private final Pointer ptr; private final int length; private Seed(Pointer ptr, int length) { this.ptr = ptr; this.length = length; } @Override protected void finalize() { Sodium.sodium_free(ptr); } /** * Create a {@link Seed} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the seed. * @return A seed. */ public static Seed fromBytes(Bytes bytes) { return fromBytes(bytes.toArrayUnsafe()); } /** * Create a {@link Seed} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the seed. * @return A seed. */ public static Seed fromBytes(byte[] bytes) { if (bytes.length != Sodium.crypto_kx_seedbytes()) { throw new IllegalArgumentException( "key must be " + Sodium.crypto_kx_seedbytes() + " bytes, got " + bytes.length); } return Sodium.dup(bytes, Seed::new); } /** * Obtain the length of the seed in bytes (32). * * @return The length of the seed in bytes (32). */ public static int length() { long seedbytes = Sodium.crypto_kx_seedbytes(); if (seedbytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_kx_seedbytes: " + seedbytes + " is too large"); } return (int) seedbytes; } /** * Generate a new {@link Seed} using a random generator. * * @return A randomly generated seed. */ public static Seed random() { return Sodium.randomBytes(length(), Seed::new); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof Seed)) { return false; } Seed other = (Seed) obj; return Sodium.sodium_memcmp(this.ptr, other.ptr, length) == 0; } @Override public int hashCode() { return Sodium.hashCode(ptr, length); } /** * @return The bytes of this seed. */ public Bytes bytes() { return Bytes.wrap(bytesArray()); } /** * @return The bytes of this seed. */ public byte[] bytesArray() { return Sodium.reify(ptr, length); } } /** * A KeyExchange key pair. */ public static final class KeyPair { private final PublicKey publicKey; private final SecretKey secretKey; /** * Create a {@link KeyPair} from pair of keys. * * @param publicKey The bytes for the public key. * @param secretKey The bytes for the secret key. */ public KeyPair(PublicKey publicKey, SecretKey secretKey) { this.publicKey = publicKey; this.secretKey = secretKey; } /** * Create a {@link KeyPair} from a secret key. * * @param secretKey The secret key. * @return A {@link KeyPair}. */ public static KeyPair forSecretKey(SecretKey secretKey) { checkArgument(secretKey.ptr != null, "SecretKey has been destroyed"); return Sodium.scalarMultBase(secretKey.ptr, SecretKey.length(), (ptr, len) -> { int publicKeyLength = PublicKey.length(); if (len != publicKeyLength) { throw new IllegalStateException( "Public key length " + publicKeyLength + " is not same as generated key length " + len); } return new KeyPair(new PublicKey(ptr, publicKeyLength), secretKey); }); } /** * Generate a new key using a random generator. * * @return A randomly generated key pair. */ public static KeyPair random() { int publicKeyLength = PublicKey.length(); Pointer publicKey = Sodium.malloc(publicKeyLength); Pointer secretKey = null; try { int secretKeyLength = SecretKey.length(); secretKey = Sodium.malloc(secretKeyLength); int rc = Sodium.crypto_kx_keypair(publicKey, secretKey); if (rc != 0) { throw new SodiumException("crypto_kx_keypair: failed with result " + rc); } PublicKey pk = new PublicKey(publicKey, publicKeyLength); publicKey = null; SecretKey sk = new SecretKey(secretKey, secretKeyLength); secretKey = null; return new KeyPair(pk, sk); } catch (Throwable e) { if (publicKey != null) { Sodium.sodium_free(publicKey); } if (secretKey != null) { Sodium.sodium_free(secretKey); } throw e; } } /** * Generate a new key using a seed. * * @param seed A seed. * @return The generated key pair. */ public static KeyPair fromSeed(Seed seed) { int publicKeyLength = PublicKey.length(); Pointer publicKey = Sodium.malloc(publicKeyLength); Pointer secretKey = null; try { int secretKeyLength = SecretKey.length(); secretKey = Sodium.malloc(secretKeyLength); int rc = Sodium.crypto_kx_seed_keypair(publicKey, secretKey, seed.ptr); if (rc != 0) { throw new SodiumException("crypto_kx_seed_keypair: failed with result " + rc); } PublicKey pk = new PublicKey(publicKey, publicKeyLength); publicKey = null; SecretKey sk = new SecretKey(secretKey, secretKeyLength); secretKey = null; return new KeyPair(pk, sk); } catch (Throwable e) { if (publicKey != null) { Sodium.sodium_free(publicKey); } if (secretKey != null) { Sodium.sodium_free(secretKey); } throw e; } } /** * @return The public key of the key pair. */ public PublicKey publicKey() { return publicKey; } /** * @return The secret key of the key pair. */ public SecretKey secretKey() { return secretKey; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof KeyPair)) { return false; } KeyPair other = (KeyPair) obj; return this.publicKey.equals(other.publicKey) && this.secretKey.equals(other.secretKey); } @Override public int hashCode() { return Objects.hash(publicKey, secretKey); } } /** * A KeyExchange session key. */ public static final class SessionKey { private final Pointer ptr; private final int length; private SessionKey(Pointer ptr, int length) { this.ptr = ptr; this.length = length; } @Override protected void finalize() { Sodium.sodium_free(ptr); } /** * Create a {@link SessionKey} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the public key. * @return A public key. */ public static SessionKey fromBytes(Bytes bytes) { return fromBytes(bytes.toArrayUnsafe()); } /** * Create a {@link SessionKey} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the public key. * @return A public key. */ public static SessionKey fromBytes(byte[] bytes) { if (bytes.length != Sodium.crypto_kx_sessionkeybytes()) { throw new IllegalArgumentException( "key must be " + Sodium.crypto_kx_sessionkeybytes() + " bytes, got " + bytes.length); } return Sodium.dup(bytes, SessionKey::new); } /** * Obtain the length of the key in bytes (32). * * @return The length of the key in bytes (32). */ public static int length() { long keybytes = Sodium.crypto_kx_sessionkeybytes(); if (keybytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_kx_sessionkeybytes: " + keybytes + " is too large"); } return (int) keybytes; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof SessionKey)) { return false; } SessionKey other = (SessionKey) obj; return Sodium.sodium_memcmp(this.ptr, other.ptr, length) == 0; } @Override public int hashCode() { return Sodium.hashCode(ptr, length); } /** * @return The bytes of this key. */ public Bytes bytes() { return Bytes.wrap(bytesArray()); } /** * @return The bytes of this key. */ public byte[] bytesArray() { return Sodium.reify(ptr, length); } } /** * A KeyExchange session key pair. */ public static final class SessionKeyPair { private final SessionKey rxKey; private final SessionKey txKey; /** * Create a {@link KeyPair} from pair of keys. * * @param rxKey The bytes for the secret key. * @param txKey The bytes for the public key. */ public SessionKeyPair(SessionKey rxKey, SessionKey txKey) { this.rxKey = rxKey; this.txKey = txKey; } /** @return The session key that will be used to receive data. */ public SessionKey rx() { return rxKey; } /** @return The session key that will be used to send data. */ public SessionKey tx() { return txKey; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof SessionKeyPair)) { return false; } SessionKeyPair other = (SessionKeyPair) obj; return this.rxKey.equals(other.rxKey) && this.txKey.equals(other.txKey); } @Override public int hashCode() { return Objects.hash(rxKey, txKey); } } /** * Computer a pair of session keys for use by a client. * * @param clientKeys The client key pair. * @param serverKey The server public key. * @return A pair of session keys. */ public static SessionKeyPair client(KeyPair clientKeys, PublicKey serverKey) { checkArgument(clientKeys.secretKey.ptr != null, "SecretKey has been destroyed"); long sessionkeybytes = Sodium.crypto_kx_sessionkeybytes(); if (sessionkeybytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_kx_sessionkeybytes: " + sessionkeybytes + " is too large"); } Pointer rxPtr = null; Pointer txPtr = null; try { rxPtr = Sodium.malloc(sessionkeybytes); txPtr = Sodium.malloc(sessionkeybytes); int rc = Sodium.crypto_kx_client_session_keys( rxPtr, txPtr, clientKeys.publicKey.ptr, clientKeys.secretKey.ptr, serverKey.ptr); if (rc != 0) { throw new SodiumException("crypto_kx_client_session_keys: failed with result " + rc); } SessionKey rxKey = new SessionKey(rxPtr, (int) sessionkeybytes); rxPtr = null; SessionKey txKey = new SessionKey(txPtr, (int) sessionkeybytes); txPtr = null; return new SessionKeyPair(rxKey, txKey); } catch (Throwable e) { if (rxPtr != null) { Sodium.sodium_free(rxPtr); } if (txPtr != null) { Sodium.sodium_free(txPtr); } throw e; } } /** * Computer a pair of session keys for use by a client. * * @param serverKeys The server key pair. * @param clientKey The client public key. * @return A pair of session keys. */ public static SessionKeyPair server(KeyPair serverKeys, PublicKey clientKey) { checkArgument(serverKeys.secretKey.ptr != null, "SecretKey has been destroyed"); long sessionkeybytes = Sodium.crypto_kx_sessionkeybytes(); if (sessionkeybytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_kx_sessionkeybytes: " + sessionkeybytes + " is too large"); } Pointer rxPtr = null; Pointer txPtr = null; try { rxPtr = Sodium.malloc(sessionkeybytes); txPtr = Sodium.malloc(sessionkeybytes); int rc = Sodium.crypto_kx_server_session_keys( rxPtr, txPtr, serverKeys.publicKey.ptr, serverKeys.secretKey.ptr, clientKey.ptr); if (rc != 0) { throw new SodiumException("crypto_kx_client_session_keys: failed with result " + rc); } SessionKey rxKey = new SessionKey(rxPtr, (int) sessionkeybytes); rxPtr = null; SessionKey txKey = new SessionKey(txPtr, (int) sessionkeybytes); txPtr = null; return new SessionKeyPair(rxKey, txKey); } catch (Throwable e) { if (rxPtr != null) { Sodium.sodium_free(rxPtr); } if (txPtr != null) { Sodium.sodium_free(txPtr); } throw e; } } } cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/LibSodium.java000066400000000000000000003145611341750772100275220ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import jnr.ffi.Pointer; import jnr.ffi.annotations.In; import jnr.ffi.annotations.Out; import jnr.ffi.byref.ByteByReference; import jnr.ffi.byref.LongLongByReference; import jnr.ffi.types.ssize_t; import jnr.ffi.types.u_int32_t; import jnr.ffi.types.u_int64_t; import jnr.ffi.types.u_int8_t; // Generated with https://gist.github.com/cleishm/39fbad03378f5e1ad82521ad821cd065, then modified public interface LibSodium { // const char * sodium_version_string(void); String sodium_version_string(); // int sodium_library_version_major(void); int sodium_library_version_major(); // int sodium_library_version_minor(void); int sodium_library_version_minor(); // int sodium_library_minimal(void); int sodium_library_minimal(); // int sodium_init(void); int sodium_init(); // int sodium_set_misuse_handler(void * handler); int sodium_set_misuse_handler(/*both*/ Pointer handler); // void sodium_misuse(void); void sodium_misuse(); // int crypto_aead_aes256gcm_is_available(void); int crypto_aead_aes256gcm_is_available(); // size_t crypto_aead_aes256gcm_keybytes(void); @ssize_t long crypto_aead_aes256gcm_keybytes(); // size_t crypto_aead_aes256gcm_nsecbytes(void); @ssize_t long crypto_aead_aes256gcm_nsecbytes(); // size_t crypto_aead_aes256gcm_npubbytes(void); @ssize_t long crypto_aead_aes256gcm_npubbytes(); // size_t crypto_aead_aes256gcm_abytes(void); @ssize_t long crypto_aead_aes256gcm_abytes(); // size_t crypto_aead_aes256gcm_messagebytes_max(void); @ssize_t long crypto_aead_aes256gcm_messagebytes_max(); // size_t crypto_aead_aes256gcm_statebytes(void); @ssize_t long crypto_aead_aes256gcm_statebytes(); // int crypto_aead_aes256gcm_encrypt(unsigned char * c, unsigned long long * clen_p, const unsigned char * m, unsigned long long mlen, const unsigned char * ad, unsigned long long adlen, const unsigned char * nsec, const unsigned char * npub, const unsigned char * k); int crypto_aead_aes256gcm_encrypt( @Out byte[] c, @Out LongLongByReference clen_p, @In byte[] m, @In @u_int64_t long mlen, @In byte[] ad, @In @u_int64_t long adlen, /*null*/ Pointer nsec, @In Pointer npub, @In Pointer k); // int crypto_aead_aes256gcm_decrypt(unsigned char * m, unsigned long long * mlen_p, unsigned char * nsec, const unsigned char * c, unsigned long long clen, const unsigned char * ad, unsigned long long adlen, const unsigned char * npub, const unsigned char * k); int crypto_aead_aes256gcm_decrypt( @Out byte[] m, @Out LongLongByReference mlen_p, /*null*/ Pointer nsec, @In byte[] c, @In @u_int64_t long clen, @In byte[] ad, @In @u_int64_t long adlen, @In Pointer npub, @In Pointer k); // int crypto_aead_aes256gcm_encrypt_detached(unsigned char * c, unsigned char * mac, unsigned long long * maclen_p, const unsigned char * m, unsigned long long mlen, const unsigned char * ad, unsigned long long adlen, const unsigned char * nsec, const unsigned char * npub, const unsigned char * k); int crypto_aead_aes256gcm_encrypt_detached( @Out byte[] c, @Out byte[] mac, @Out LongLongByReference maclen_p, @In byte[] m, @In @u_int64_t long mlen, @In byte[] ad, @In @u_int64_t long adlen, /*null*/ Pointer nsec, @In Pointer npub, @In Pointer k); // int crypto_aead_aes256gcm_decrypt_detached(unsigned char * m, unsigned char * nsec, const unsigned char * c, unsigned long long clen, const unsigned char * mac, const unsigned char * ad, unsigned long long adlen, const unsigned char * npub, const unsigned char * k); int crypto_aead_aes256gcm_decrypt_detached( @Out byte[] m, /*null*/ Pointer nsec, @In byte[] c, @In @u_int64_t long clen, @In byte[] mac, @In byte[] ad, @In @u_int64_t long adlen, @In Pointer npub, @In Pointer k); // int crypto_aead_aes256gcm_beforenm(crypto_aead_aes256gcm_state * ctx_, const unsigned char * k); int crypto_aead_aes256gcm_beforenm(@Out Pointer ctx_, @In Pointer k); // int crypto_aead_aes256gcm_encrypt_afternm(unsigned char * c, unsigned long long * clen_p, const unsigned char * m, unsigned long long mlen, const unsigned char * ad, unsigned long long adlen, const unsigned char * nsec, const unsigned char * npub, const crypto_aead_aes256gcm_state * ctx_); int crypto_aead_aes256gcm_encrypt_afternm( @Out byte[] c, @Out LongLongByReference clen_p, @In byte[] m, @In @u_int64_t long mlen, @In byte[] ad, @In @u_int64_t long adlen, /*null*/ Pointer nsec, @In Pointer npub, @In Pointer ctx_); // int crypto_aead_aes256gcm_decrypt_afternm(unsigned char * m, unsigned long long * mlen_p, unsigned char * nsec, const unsigned char * c, unsigned long long clen, const unsigned char * ad, unsigned long long adlen, const unsigned char * npub, const crypto_aead_aes256gcm_state * ctx_); int crypto_aead_aes256gcm_decrypt_afternm( @Out byte[] m, @Out LongLongByReference mlen_p, /*null*/ Pointer nsec, @In byte[] c, @In @u_int64_t long clen, @In byte[] ad, @In @u_int64_t long adlen, @In Pointer npub, @In Pointer ctx_); // int crypto_aead_aes256gcm_encrypt_detached_afternm(unsigned char * c, unsigned char * mac, unsigned long long * maclen_p, const unsigned char * m, unsigned long long mlen, const unsigned char * ad, unsigned long long adlen, const unsigned char * nsec, const unsigned char * npub, const crypto_aead_aes256gcm_state * ctx_); int crypto_aead_aes256gcm_encrypt_detached_afternm( @Out byte[] c, @Out byte[] mac, @Out LongLongByReference maclen_p, @In byte[] m, @In @u_int64_t long mlen, @In byte[] ad, @In @u_int64_t long adlen, /*null*/ Pointer nsec, @In Pointer npub, @In Pointer ctx_); // int crypto_aead_aes256gcm_decrypt_detached_afternm(unsigned char * m, unsigned char * nsec, const unsigned char * c, unsigned long long clen, const unsigned char * mac, const unsigned char * ad, unsigned long long adlen, const unsigned char * npub, const crypto_aead_aes256gcm_state * ctx_); int crypto_aead_aes256gcm_decrypt_detached_afternm( @Out byte[] m, /*null*/ Pointer nsec, @In byte[] c, @In @u_int64_t long clen, @In byte[] mac, @In byte[] ad, @In @u_int64_t long adlen, @In Pointer npub, @In Pointer ctx_); // void crypto_aead_aes256gcm_keygen(unsigned char[] k); void crypto_aead_aes256gcm_keygen(@Out Pointer k); // size_t crypto_aead_chacha20poly1305_ietf_keybytes(void); @ssize_t long crypto_aead_chacha20poly1305_ietf_keybytes(); // size_t crypto_aead_chacha20poly1305_ietf_nsecbytes(void); @ssize_t long crypto_aead_chacha20poly1305_ietf_nsecbytes(); // size_t crypto_aead_chacha20poly1305_ietf_npubbytes(void); @ssize_t long crypto_aead_chacha20poly1305_ietf_npubbytes(); // size_t crypto_aead_chacha20poly1305_ietf_abytes(void); @ssize_t long crypto_aead_chacha20poly1305_ietf_abytes(); // size_t crypto_aead_chacha20poly1305_ietf_messagebytes_max(void); @ssize_t long crypto_aead_chacha20poly1305_ietf_messagebytes_max(); // int crypto_aead_chacha20poly1305_ietf_encrypt(unsigned char * c, unsigned long long * clen_p, const unsigned char * m, unsigned long long mlen, const unsigned char * ad, unsigned long long adlen, const unsigned char * nsec, const unsigned char * npub, const unsigned char * k); int crypto_aead_chacha20poly1305_ietf_encrypt( @Out byte[] c, @Out LongLongByReference clen_p, @In byte[] m, @In @u_int64_t long mlen, @In byte[] ad, @In @u_int64_t long adlen, @In byte[] nsec, @In byte[] npub, @In byte[] k); // int crypto_aead_chacha20poly1305_ietf_decrypt(unsigned char * m, unsigned long long * mlen_p, unsigned char * nsec, const unsigned char * c, unsigned long long clen, const unsigned char * ad, unsigned long long adlen, const unsigned char * npub, const unsigned char * k); int crypto_aead_chacha20poly1305_ietf_decrypt( @Out byte[] m, @Out LongLongByReference mlen_p, /*null*/ byte[] nsec, @In byte[] c, @In @u_int64_t long clen, @In byte[] ad, @In @u_int64_t long adlen, @In byte[] npub, @In byte[] k); // int crypto_aead_chacha20poly1305_ietf_encrypt_detached(unsigned char * c, unsigned char * mac, unsigned long long * maclen_p, const unsigned char * m, unsigned long long mlen, const unsigned char * ad, unsigned long long adlen, const unsigned char * nsec, const unsigned char * npub, const unsigned char * k); int crypto_aead_chacha20poly1305_ietf_encrypt_detached( @Out byte[] c, @Out byte[] mac, @Out LongLongByReference maclen_p, @In byte[] m, @In @u_int64_t long mlen, @In byte[] ad, @In @u_int64_t long adlen, @In byte[] nsec, @In byte[] npub, @In byte[] k); // int crypto_aead_chacha20poly1305_ietf_decrypt_detached(unsigned char * m, unsigned char * nsec, const unsigned char * c, unsigned long long clen, const unsigned char * mac, const unsigned char * ad, unsigned long long adlen, const unsigned char * npub, const unsigned char * k); int crypto_aead_chacha20poly1305_ietf_decrypt_detached( @Out byte[] m, /*null*/ byte[] nsec, @In byte[] c, @In @u_int64_t long clen, @In byte[] mac, @In byte[] ad, @In @u_int64_t long adlen, @In byte[] npub, @In byte[] k); // void crypto_aead_chacha20poly1305_ietf_keygen(unsigned char[] k); void crypto_aead_chacha20poly1305_ietf_keygen(@Out byte[] k); // size_t crypto_aead_chacha20poly1305_keybytes(void); @ssize_t long crypto_aead_chacha20poly1305_keybytes(); // size_t crypto_aead_chacha20poly1305_nsecbytes(void); @ssize_t long crypto_aead_chacha20poly1305_nsecbytes(); // size_t crypto_aead_chacha20poly1305_npubbytes(void); @ssize_t long crypto_aead_chacha20poly1305_npubbytes(); // size_t crypto_aead_chacha20poly1305_abytes(void); @ssize_t long crypto_aead_chacha20poly1305_abytes(); // size_t crypto_aead_chacha20poly1305_messagebytes_max(void); @ssize_t long crypto_aead_chacha20poly1305_messagebytes_max(); // int crypto_aead_chacha20poly1305_encrypt(unsigned char * c, unsigned long long * clen_p, const unsigned char * m, unsigned long long mlen, const unsigned char * ad, unsigned long long adlen, const unsigned char * nsec, const unsigned char * npub, const unsigned char * k); int crypto_aead_chacha20poly1305_encrypt( @Out byte[] c, @Out LongLongByReference clen_p, @In byte[] m, @In @u_int64_t long mlen, @In byte[] ad, @In @u_int64_t long adlen, @In byte[] nsec, @In byte[] npub, @In byte[] k); // int crypto_aead_chacha20poly1305_decrypt(unsigned char * m, unsigned long long * mlen_p, unsigned char * nsec, const unsigned char * c, unsigned long long clen, const unsigned char * ad, unsigned long long adlen, const unsigned char * npub, const unsigned char * k); int crypto_aead_chacha20poly1305_decrypt( @Out byte[] m, @Out LongLongByReference mlen_p, /*null*/ byte[] nsec, @In byte[] c, @In @u_int64_t long clen, @In byte[] ad, @In @u_int64_t long adlen, @In byte[] npub, @In byte[] k); // int crypto_aead_chacha20poly1305_encrypt_detached(unsigned char * c, unsigned char * mac, unsigned long long * maclen_p, const unsigned char * m, unsigned long long mlen, const unsigned char * ad, unsigned long long adlen, const unsigned char * nsec, const unsigned char * npub, const unsigned char * k); int crypto_aead_chacha20poly1305_encrypt_detached( @Out byte[] c, /*null*/ byte[] mac, @Out LongLongByReference maclen_p, @In byte[] m, @In @u_int64_t long mlen, @In byte[] ad, @In @u_int64_t long adlen, @In byte[] nsec, @In byte[] npub, @In byte[] k); // int crypto_aead_chacha20poly1305_decrypt_detached(unsigned char * m, unsigned char * nsec, const unsigned char * c, unsigned long long clen, const unsigned char * mac, const unsigned char * ad, unsigned long long adlen, const unsigned char * npub, const unsigned char * k); int crypto_aead_chacha20poly1305_decrypt_detached( @Out byte[] m, /*null*/ byte[] nsec, @In byte[] c, @In @u_int64_t long clen, @In byte[] mac, @In byte[] ad, @In @u_int64_t long adlen, @In byte[] npub, @In byte[] k); // void crypto_aead_chacha20poly1305_keygen(unsigned char[] k); void crypto_aead_chacha20poly1305_keygen(@Out byte[] k); // size_t crypto_aead_xchacha20poly1305_ietf_keybytes(void); @ssize_t long crypto_aead_xchacha20poly1305_ietf_keybytes(); // size_t crypto_aead_xchacha20poly1305_ietf_nsecbytes(void); @ssize_t long crypto_aead_xchacha20poly1305_ietf_nsecbytes(); // size_t crypto_aead_xchacha20poly1305_ietf_npubbytes(void); @ssize_t long crypto_aead_xchacha20poly1305_ietf_npubbytes(); // size_t crypto_aead_xchacha20poly1305_ietf_abytes(void); @ssize_t long crypto_aead_xchacha20poly1305_ietf_abytes(); // size_t crypto_aead_xchacha20poly1305_ietf_messagebytes_max(void); @ssize_t long crypto_aead_xchacha20poly1305_ietf_messagebytes_max(); // int crypto_aead_xchacha20poly1305_ietf_encrypt(unsigned char * c, unsigned long long * clen_p, const unsigned char * m, unsigned long long mlen, const unsigned char * ad, unsigned long long adlen, const unsigned char * nsec, const unsigned char * npub, const unsigned char * k); int crypto_aead_xchacha20poly1305_ietf_encrypt( @Out byte[] c, @Out LongLongByReference clen_p, @In byte[] m, @In @u_int64_t long mlen, @In byte[] ad, @In @u_int64_t long adlen, @In byte[] nsec, @In Pointer npub, @In Pointer k); // int crypto_aead_xchacha20poly1305_ietf_decrypt(unsigned char * m, unsigned long long * mlen_p, unsigned char * nsec, const unsigned char * c, unsigned long long clen, const unsigned char * ad, unsigned long long adlen, const unsigned char * npub, const unsigned char * k); int crypto_aead_xchacha20poly1305_ietf_decrypt( @Out byte[] m, @Out LongLongByReference mlen_p, /*null*/ byte[] nsec, @In byte[] c, @In @u_int64_t long clen, @In byte[] ad, @In @u_int64_t long adlen, @In Pointer npub, @In Pointer k); // int crypto_aead_xchacha20poly1305_ietf_encrypt_detached(unsigned char * c, unsigned char * mac, unsigned long long * maclen_p, const unsigned char * m, unsigned long long mlen, const unsigned char * ad, unsigned long long adlen, const unsigned char * nsec, const unsigned char * npub, const unsigned char * k); int crypto_aead_xchacha20poly1305_ietf_encrypt_detached( @Out byte[] c, /*null*/ byte[] mac, @Out LongLongByReference maclen_p, @In byte[] m, @In @u_int64_t long mlen, @In byte[] ad, @In @u_int64_t long adlen, @In byte[] nsec, @In Pointer npub, @In Pointer k); // int crypto_aead_xchacha20poly1305_ietf_decrypt_detached(unsigned char * m, unsigned char * nsec, const unsigned char * c, unsigned long long clen, const unsigned char * mac, const unsigned char * ad, unsigned long long adlen, const unsigned char * npub, const unsigned char * k); int crypto_aead_xchacha20poly1305_ietf_decrypt_detached( @Out byte[] m, /*null*/ byte[] nsec, @In byte[] c, @In @u_int64_t long clen, @In byte[] mac, @In byte[] ad, @In @u_int64_t long adlen, @In Pointer npub, @In Pointer k); // void crypto_aead_xchacha20poly1305_ietf_keygen(unsigned char[] k); void crypto_aead_xchacha20poly1305_ietf_keygen(@Out Pointer k); // size_t crypto_hash_sha512_statebytes(void); @ssize_t long crypto_hash_sha512_statebytes(); // size_t crypto_hash_sha512_bytes(void); @ssize_t long crypto_hash_sha512_bytes(); // int crypto_hash_sha512(unsigned char * out, const unsigned char * in, unsigned long long inlen); int crypto_hash_sha512(@Out byte[] out, @In byte[] in, @In @u_int64_t long inlen); // int crypto_hash_sha512_init(crypto_hash_sha512_state * state); int crypto_hash_sha512_init(@Out Pointer state); // int crypto_hash_sha512_update(crypto_hash_sha512_state * state, const unsigned char * in, unsigned long long inlen); int crypto_hash_sha512_update(/*both*/ Pointer state, @In byte[] in, @In @u_int64_t long inlen); // int crypto_hash_sha512_final(crypto_hash_sha512_state * state, unsigned char * out); int crypto_hash_sha512_final(/*both*/ Pointer state, @Out byte[] out); // size_t crypto_auth_hmacsha512_bytes(void); @ssize_t long crypto_auth_hmacsha512_bytes(); // size_t crypto_auth_hmacsha512_keybytes(void); @ssize_t long crypto_auth_hmacsha512_keybytes(); // int crypto_auth_hmacsha512(unsigned char * out, const unsigned char * in, unsigned long long inlen, const unsigned char * k); int crypto_auth_hmacsha512(@Out byte[] out, @In byte[] in, @In @u_int64_t long inlen, @In byte[] k); // int crypto_auth_hmacsha512_verify(const unsigned char * h, const unsigned char * in, unsigned long long inlen, const unsigned char * k); int crypto_auth_hmacsha512_verify(@In byte[] h, @In byte[] in, @In @u_int64_t long inlen, @In byte[] k); // size_t crypto_auth_hmacsha512_statebytes(void); @ssize_t long crypto_auth_hmacsha512_statebytes(); // int crypto_auth_hmacsha512_init(crypto_auth_hmacsha512_state * state, const unsigned char * key, size_t keylen); int crypto_auth_hmacsha512_init(@Out Pointer state, @In byte[] key, @In @ssize_t long keylen); // int crypto_auth_hmacsha512_update(crypto_auth_hmacsha512_state * state, const unsigned char * in, unsigned long long inlen); int crypto_auth_hmacsha512_update(/*both*/ Pointer state, @In byte[] in, @In @u_int64_t long inlen); // int crypto_auth_hmacsha512_final(crypto_auth_hmacsha512_state * state, unsigned char * out); int crypto_auth_hmacsha512_final(/*both*/ Pointer state, @Out byte[] out); // void crypto_auth_hmacsha512_keygen(unsigned char[] k); void crypto_auth_hmacsha512_keygen(@Out byte[] k); // size_t crypto_auth_hmacsha512256_bytes(void); @ssize_t long crypto_auth_hmacsha512256_bytes(); // size_t crypto_auth_hmacsha512256_keybytes(void); @ssize_t long crypto_auth_hmacsha512256_keybytes(); // int crypto_auth_hmacsha512256(unsigned char * out, const unsigned char * in, unsigned long long inlen, const unsigned char * k); int crypto_auth_hmacsha512256(@Out byte[] out, @In byte[] in, @In @u_int64_t long inlen, @In byte[] k); // int crypto_auth_hmacsha512256_verify(const unsigned char * h, const unsigned char * in, unsigned long long inlen, const unsigned char * k); int crypto_auth_hmacsha512256_verify(@In byte[] h, @In byte[] in, @In @u_int64_t long inlen, @In byte[] k); // size_t crypto_auth_hmacsha512256_statebytes(void); @ssize_t long crypto_auth_hmacsha512256_statebytes(); // int crypto_auth_hmacsha512256_init(crypto_auth_hmacsha512256_state * state, const unsigned char * key, size_t keylen); int crypto_auth_hmacsha512256_init(@Out Pointer state, @In byte[] key, @In @ssize_t long keylen); // int crypto_auth_hmacsha512256_update(crypto_auth_hmacsha512256_state * state, const unsigned char * in, unsigned long long inlen); int crypto_auth_hmacsha512256_update(/*both*/ Pointer state, @In byte[] in, @In @u_int64_t long inlen); // int crypto_auth_hmacsha512256_final(crypto_auth_hmacsha512256_state * state, unsigned char * out); int crypto_auth_hmacsha512256_final(/*both*/ Pointer state, @Out byte[] out); // void crypto_auth_hmacsha512256_keygen(unsigned char[] k); void crypto_auth_hmacsha512256_keygen(@Out byte[] k); // size_t crypto_auth_bytes(void); @ssize_t long crypto_auth_bytes(); // size_t crypto_auth_keybytes(void); @ssize_t long crypto_auth_keybytes(); // const char * crypto_auth_primitive(void); String crypto_auth_primitive(); // int crypto_auth(unsigned char * out, const unsigned char * in, unsigned long long inlen, const unsigned char * k); int crypto_auth(@Out byte[] out, @In byte[] in, @In @u_int64_t long inlen, @In Pointer k); // int crypto_auth_verify(const unsigned char * h, const unsigned char * in, unsigned long long inlen, const unsigned char * k); int crypto_auth_verify(@In byte[] h, @In byte[] in, @In @u_int64_t long inlen, @In Pointer k); // void crypto_auth_keygen(unsigned char[] k); void crypto_auth_keygen(@Out Pointer k); // size_t crypto_hash_sha256_statebytes(void); @ssize_t long crypto_hash_sha256_statebytes(); // size_t crypto_hash_sha256_bytes(void); @ssize_t long crypto_hash_sha256_bytes(); // int crypto_hash_sha256(unsigned char * out, const unsigned char * in, unsigned long long inlen); int crypto_hash_sha256(@Out byte[] out, @In byte[] in, @In @u_int64_t long inlen); // int crypto_hash_sha256_init(crypto_hash_sha256_state * state); int crypto_hash_sha256_init(@Out Pointer state); // int crypto_hash_sha256_update(crypto_hash_sha256_state * state, const unsigned char * in, unsigned long long inlen); int crypto_hash_sha256_update(/*both*/ Pointer state, @In byte[] in, @In @u_int64_t long inlen); // int crypto_hash_sha256_final(crypto_hash_sha256_state * state, unsigned char * out); int crypto_hash_sha256_final(/*both*/ Pointer state, @Out byte[] out); // size_t crypto_auth_hmacsha256_bytes(void); @ssize_t long crypto_auth_hmacsha256_bytes(); // size_t crypto_auth_hmacsha256_keybytes(void); @ssize_t long crypto_auth_hmacsha256_keybytes(); // int crypto_auth_hmacsha256(unsigned char * out, const unsigned char * in, unsigned long long inlen, const unsigned char * k); int crypto_auth_hmacsha256(@Out byte[] out, @In byte[] in, @In @u_int64_t long inlen, @In byte[] k); // int crypto_auth_hmacsha256_verify(const unsigned char * h, const unsigned char * in, unsigned long long inlen, const unsigned char * k); int crypto_auth_hmacsha256_verify(@In byte[] h, @In byte[] in, @In @u_int64_t long inlen, @In byte[] k); // size_t crypto_auth_hmacsha256_statebytes(void); @ssize_t long crypto_auth_hmacsha256_statebytes(); // int crypto_auth_hmacsha256_init(crypto_auth_hmacsha256_state * state, const unsigned char * key, size_t keylen); int crypto_auth_hmacsha256_init(@Out Pointer state, @In byte[] key, @In @ssize_t long keylen); // int crypto_auth_hmacsha256_update(crypto_auth_hmacsha256_state * state, const unsigned char * in, unsigned long long inlen); int crypto_auth_hmacsha256_update(/*both*/ Pointer state, @In byte[] in, @In @u_int64_t long inlen); // int crypto_auth_hmacsha256_final(crypto_auth_hmacsha256_state * state, unsigned char * out); int crypto_auth_hmacsha256_final(/*both*/ Pointer state, @Out byte[] out); // void crypto_auth_hmacsha256_keygen(unsigned char[] k); void crypto_auth_hmacsha256_keygen(@Out byte[] k); // size_t crypto_stream_xsalsa20_keybytes(void); @ssize_t long crypto_stream_xsalsa20_keybytes(); // size_t crypto_stream_xsalsa20_noncebytes(void); @ssize_t long crypto_stream_xsalsa20_noncebytes(); // size_t crypto_stream_xsalsa20_messagebytes_max(void); @ssize_t long crypto_stream_xsalsa20_messagebytes_max(); // int crypto_stream_xsalsa20(unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_stream_xsalsa20(@Out byte[] c, @In @u_int64_t long clen, @In byte[] n, @In byte[] k); // int crypto_stream_xsalsa20_xor(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_stream_xsalsa20_xor(@Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In byte[] k); // int crypto_stream_xsalsa20_xor_ic(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, uint64_t ic, const unsigned char * k); int crypto_stream_xsalsa20_xor_ic( @Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In @u_int64_t long ic, @In byte[] k); // void crypto_stream_xsalsa20_keygen(unsigned char[] k); void crypto_stream_xsalsa20_keygen(@Out byte[] k); // size_t crypto_box_curve25519xsalsa20poly1305_seedbytes(void); @ssize_t long crypto_box_curve25519xsalsa20poly1305_seedbytes(); // size_t crypto_box_curve25519xsalsa20poly1305_publickeybytes(void); @ssize_t long crypto_box_curve25519xsalsa20poly1305_publickeybytes(); // size_t crypto_box_curve25519xsalsa20poly1305_secretkeybytes(void); @ssize_t long crypto_box_curve25519xsalsa20poly1305_secretkeybytes(); // size_t crypto_box_curve25519xsalsa20poly1305_beforenmbytes(void); @ssize_t long crypto_box_curve25519xsalsa20poly1305_beforenmbytes(); // size_t crypto_box_curve25519xsalsa20poly1305_noncebytes(void); @ssize_t long crypto_box_curve25519xsalsa20poly1305_noncebytes(); // size_t crypto_box_curve25519xsalsa20poly1305_macbytes(void); @ssize_t long crypto_box_curve25519xsalsa20poly1305_macbytes(); // size_t crypto_box_curve25519xsalsa20poly1305_messagebytes_max(void); @ssize_t long crypto_box_curve25519xsalsa20poly1305_messagebytes_max(); // int crypto_box_curve25519xsalsa20poly1305_seed_keypair(unsigned char * pk, unsigned char * sk, const unsigned char * seed); int crypto_box_curve25519xsalsa20poly1305_seed_keypair(@Out byte[] pk, @Out byte[] sk, @In byte[] seed); // int crypto_box_curve25519xsalsa20poly1305_keypair(unsigned char * pk, unsigned char * sk); int crypto_box_curve25519xsalsa20poly1305_keypair(@Out byte[] pk, @Out byte[] sk); // int crypto_box_curve25519xsalsa20poly1305_beforenm(unsigned char * k, const unsigned char * pk, const unsigned char * sk); int crypto_box_curve25519xsalsa20poly1305_beforenm(@Out Pointer k, @In byte[] pk, @In byte[] sk); // size_t crypto_box_curve25519xsalsa20poly1305_boxzerobytes(void); @ssize_t long crypto_box_curve25519xsalsa20poly1305_boxzerobytes(); // size_t crypto_box_curve25519xsalsa20poly1305_zerobytes(void); @ssize_t long crypto_box_curve25519xsalsa20poly1305_zerobytes(); // int crypto_box_curve25519xsalsa20poly1305(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * pk, const unsigned char * sk); int crypto_box_curve25519xsalsa20poly1305( @Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In byte[] pk, @In byte[] sk); // int crypto_box_curve25519xsalsa20poly1305_open(unsigned char * m, const unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * pk, const unsigned char * sk); int crypto_box_curve25519xsalsa20poly1305_open( @Out byte[] m, @In byte[] c, @In @u_int64_t long clen, @In byte[] n, @In byte[] pk, @In byte[] sk); // int crypto_box_curve25519xsalsa20poly1305_afternm(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_box_curve25519xsalsa20poly1305_afternm( @Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In Pointer k); // int crypto_box_curve25519xsalsa20poly1305_open_afternm(unsigned char * m, const unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_box_curve25519xsalsa20poly1305_open_afternm( @Out byte[] m, @In byte[] c, @In @u_int64_t long clen, @In byte[] n, @In Pointer k); // size_t crypto_box_seedbytes(void); @ssize_t long crypto_box_seedbytes(); // size_t crypto_box_publickeybytes(void); @ssize_t long crypto_box_publickeybytes(); // size_t crypto_box_secretkeybytes(void); @ssize_t long crypto_box_secretkeybytes(); // size_t crypto_box_noncebytes(void); @ssize_t long crypto_box_noncebytes(); // size_t crypto_box_macbytes(void); @ssize_t long crypto_box_macbytes(); // size_t crypto_box_messagebytes_max(void); @ssize_t long crypto_box_messagebytes_max(); // const char * crypto_box_primitive(void); String crypto_box_primitive(); // int crypto_box_seed_keypair(unsigned char * pk, unsigned char * sk, const unsigned char * seed); int crypto_box_seed_keypair(@Out Pointer pk, @Out Pointer sk, @In Pointer seed); // int crypto_box_keypair(unsigned char * pk, unsigned char * sk); int crypto_box_keypair(@Out Pointer pk, @Out Pointer sk); // int crypto_box_easy(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * pk, const unsigned char * sk); int crypto_box_easy( @Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In Pointer n, @In Pointer pk, @In Pointer sk); // int crypto_box_open_easy(unsigned char * m, const unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * pk, const unsigned char * sk); int crypto_box_open_easy( @Out byte[] m, @In byte[] c, @In @u_int64_t long clen, @In Pointer n, @In Pointer pk, @In Pointer sk); // int crypto_box_detached(unsigned char * c, unsigned char * mac, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * pk, const unsigned char * sk); int crypto_box_detached( @Out byte[] c, @Out byte[] mac, @In byte[] m, @In @u_int64_t long mlen, @In Pointer n, @In Pointer pk, @In Pointer sk); // int crypto_box_open_detached(unsigned char * m, const unsigned char * c, const unsigned char * mac, unsigned long long clen, const unsigned char * n, const unsigned char * pk, const unsigned char * sk); int crypto_box_open_detached( @Out byte[] m, @In byte[] c, @In byte[] mac, @In @u_int64_t long clen, @In Pointer n, @In Pointer pk, @In Pointer sk); // size_t crypto_box_beforenmbytes(void); @ssize_t long crypto_box_beforenmbytes(); // int crypto_box_beforenm(unsigned char * k, const unsigned char * pk, const unsigned char * sk); int crypto_box_beforenm(@Out Pointer k, @In Pointer pk, @In Pointer sk); // int crypto_box_easy_afternm(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_box_easy_afternm(@Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In Pointer n, @In Pointer k); // int crypto_box_open_easy_afternm(unsigned char * m, const unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_box_open_easy_afternm(@Out byte[] m, @In byte[] c, @In @u_int64_t long clen, @In Pointer n, @In Pointer k); // int crypto_box_detached_afternm(unsigned char * c, unsigned char * mac, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_box_detached_afternm( @Out byte[] c, @Out byte[] mac, @In byte[] m, @In @u_int64_t long mlen, @In Pointer n, @In Pointer k); // int crypto_box_open_detached_afternm(unsigned char * m, const unsigned char * c, const unsigned char * mac, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_box_open_detached_afternm( @Out byte[] m, @In byte[] c, @In byte[] mac, @In @u_int64_t long clen, @In Pointer n, @In Pointer k); // size_t crypto_box_sealbytes(void); @ssize_t long crypto_box_sealbytes(); // int crypto_box_seal(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * pk); int crypto_box_seal(@Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In Pointer pk); // int crypto_box_seal_open(unsigned char * m, const unsigned char * c, unsigned long long clen, const unsigned char * pk, const unsigned char * sk); int crypto_box_seal_open(@Out byte[] m, @In byte[] c, @In @u_int64_t long clen, @In Pointer pk, @In Pointer sk); // size_t crypto_box_zerobytes(void); @ssize_t long crypto_box_zerobytes(); // size_t crypto_box_boxzerobytes(void); @ssize_t long crypto_box_boxzerobytes(); // int crypto_box(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * pk, const unsigned char * sk); int crypto_box(@Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In byte[] pk, @In byte[] sk); // int crypto_box_open(unsigned char * m, const unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * pk, const unsigned char * sk); int crypto_box_open( @Out byte[] m, @In byte[] c, @In @u_int64_t long clen, @In byte[] n, @In byte[] pk, @In byte[] sk); // int crypto_box_afternm(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_box_afternm(@Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In Pointer k); // int crypto_box_open_afternm(unsigned char * m, const unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_box_open_afternm(@Out byte[] m, @In byte[] c, @In @u_int64_t long clen, @In byte[] n, @In Pointer k); // size_t crypto_core_hsalsa20_outputbytes(void); @ssize_t long crypto_core_hsalsa20_outputbytes(); // size_t crypto_core_hsalsa20_inputbytes(void); @ssize_t long crypto_core_hsalsa20_inputbytes(); // size_t crypto_core_hsalsa20_keybytes(void); @ssize_t long crypto_core_hsalsa20_keybytes(); // size_t crypto_core_hsalsa20_constbytes(void); @ssize_t long crypto_core_hsalsa20_constbytes(); // int crypto_core_hsalsa20(unsigned char * out, const unsigned char * in, const unsigned char * k, const unsigned char * c); int crypto_core_hsalsa20(@Out byte[] out, @In byte[] in, @In byte[] k, @In byte[] c); // size_t crypto_core_hchacha20_outputbytes(void); @ssize_t long crypto_core_hchacha20_outputbytes(); // size_t crypto_core_hchacha20_inputbytes(void); @ssize_t long crypto_core_hchacha20_inputbytes(); // size_t crypto_core_hchacha20_keybytes(void); @ssize_t long crypto_core_hchacha20_keybytes(); // size_t crypto_core_hchacha20_constbytes(void); @ssize_t long crypto_core_hchacha20_constbytes(); // int crypto_core_hchacha20(unsigned char * out, const unsigned char * in, const unsigned char * k, const unsigned char * c); int crypto_core_hchacha20(@Out byte[] out, @In byte[] in, @In byte[] k, @In byte[] c); // size_t crypto_core_salsa20_outputbytes(void); @ssize_t long crypto_core_salsa20_outputbytes(); // size_t crypto_core_salsa20_inputbytes(void); @ssize_t long crypto_core_salsa20_inputbytes(); // size_t crypto_core_salsa20_keybytes(void); @ssize_t long crypto_core_salsa20_keybytes(); // size_t crypto_core_salsa20_constbytes(void); @ssize_t long crypto_core_salsa20_constbytes(); // int crypto_core_salsa20(unsigned char * out, const unsigned char * in, const unsigned char * k, const unsigned char * c); int crypto_core_salsa20(@Out byte[] out, @In byte[] in, @In byte[] k, @In byte[] c); // size_t crypto_core_salsa2012_outputbytes(void); @ssize_t long crypto_core_salsa2012_outputbytes(); // size_t crypto_core_salsa2012_inputbytes(void); @ssize_t long crypto_core_salsa2012_inputbytes(); // size_t crypto_core_salsa2012_keybytes(void); @ssize_t long crypto_core_salsa2012_keybytes(); // size_t crypto_core_salsa2012_constbytes(void); @ssize_t long crypto_core_salsa2012_constbytes(); // int crypto_core_salsa2012(unsigned char * out, const unsigned char * in, const unsigned char * k, const unsigned char * c); int crypto_core_salsa2012(@Out byte[] out, @In byte[] in, @In byte[] k, @In byte[] c); // size_t crypto_core_salsa208_outputbytes(void); @ssize_t long crypto_core_salsa208_outputbytes(); // size_t crypto_core_salsa208_inputbytes(void); @ssize_t long crypto_core_salsa208_inputbytes(); // size_t crypto_core_salsa208_keybytes(void); @ssize_t long crypto_core_salsa208_keybytes(); // size_t crypto_core_salsa208_constbytes(void); @ssize_t long crypto_core_salsa208_constbytes(); // int crypto_core_salsa208(unsigned char * out, const unsigned char * in, const unsigned char * k, const unsigned char * c); int crypto_core_salsa208(@Out byte[] out, @In byte[] in, @In byte[] k, @In byte[] c); // size_t crypto_generichash_blake2b_bytes_min(void); @ssize_t long crypto_generichash_blake2b_bytes_min(); // size_t crypto_generichash_blake2b_bytes_max(void); @ssize_t long crypto_generichash_blake2b_bytes_max(); // size_t crypto_generichash_blake2b_bytes(void); @ssize_t long crypto_generichash_blake2b_bytes(); // size_t crypto_generichash_blake2b_keybytes_min(void); @ssize_t long crypto_generichash_blake2b_keybytes_min(); // size_t crypto_generichash_blake2b_keybytes_max(void); @ssize_t long crypto_generichash_blake2b_keybytes_max(); // size_t crypto_generichash_blake2b_keybytes(void); @ssize_t long crypto_generichash_blake2b_keybytes(); // size_t crypto_generichash_blake2b_saltbytes(void); @ssize_t long crypto_generichash_blake2b_saltbytes(); // size_t crypto_generichash_blake2b_personalbytes(void); @ssize_t long crypto_generichash_blake2b_personalbytes(); // size_t crypto_generichash_blake2b_statebytes(void); @ssize_t long crypto_generichash_blake2b_statebytes(); // int crypto_generichash_blake2b(unsigned char * out, size_t outlen, const unsigned char * in, unsigned long long inlen, const unsigned char * key, size_t keylen); int crypto_generichash_blake2b( @Out byte[] out, @In @ssize_t long outlen, @In byte[] in, @In @u_int64_t long inlen, @In byte[] key, @In @ssize_t long keylen); // int crypto_generichash_blake2b_salt_personal(unsigned char * out, size_t outlen, const unsigned char * in, unsigned long long inlen, const unsigned char * key, size_t keylen, const unsigned char * salt, const unsigned char * personal); int crypto_generichash_blake2b_salt_personal( @Out byte[] out, @In @ssize_t long outlen, @In byte[] in, @In @u_int64_t long inlen, @In byte[] key, @In @ssize_t long keylen, @In byte[] salt, @In byte[] personal); // int crypto_generichash_blake2b_init(crypto_generichash_blake2b_state * state, const unsigned char * key, const size_t keylen, const size_t outlen); int crypto_generichash_blake2b_init( @Out Pointer state, @In byte[] key, @In @ssize_t long keylen, @In @ssize_t long outlen); // int crypto_generichash_blake2b_init_salt_personal(crypto_generichash_blake2b_state * state, const unsigned char * key, const size_t keylen, const size_t outlen, const unsigned char * salt, const unsigned char * personal); int crypto_generichash_blake2b_init_salt_personal( /*both*/ Pointer state, @In byte[] key, @In @ssize_t long keylen, @In @ssize_t long outlen, @In byte[] salt, @In byte[] personal); // int crypto_generichash_blake2b_update(crypto_generichash_blake2b_state * state, const unsigned char * in, unsigned long long inlen); int crypto_generichash_blake2b_update(/*both*/ Pointer state, @In byte[] in, @In @u_int64_t long inlen); // int crypto_generichash_blake2b_final(crypto_generichash_blake2b_state * state, unsigned char * out, const size_t outlen); int crypto_generichash_blake2b_final(/*both*/ Pointer state, @Out byte[] out, @In @ssize_t long outlen); // void crypto_generichash_blake2b_keygen(unsigned char[] k); void crypto_generichash_blake2b_keygen(@Out byte[] k); // size_t crypto_generichash_bytes_min(void); @ssize_t long crypto_generichash_bytes_min(); // size_t crypto_generichash_bytes_max(void); @ssize_t long crypto_generichash_bytes_max(); // size_t crypto_generichash_bytes(void); @ssize_t long crypto_generichash_bytes(); // size_t crypto_generichash_keybytes_min(void); @ssize_t long crypto_generichash_keybytes_min(); // size_t crypto_generichash_keybytes_max(void); @ssize_t long crypto_generichash_keybytes_max(); // size_t crypto_generichash_keybytes(void); @ssize_t long crypto_generichash_keybytes(); // const char * crypto_generichash_primitive(void); String crypto_generichash_primitive(); // size_t crypto_generichash_statebytes(void); @ssize_t long crypto_generichash_statebytes(); // int crypto_generichash(unsigned char * out, size_t outlen, const unsigned char * in, unsigned long long inlen, const unsigned char * key, size_t keylen); int crypto_generichash( @Out byte[] out, @In @ssize_t long outlen, @In byte[] in, @In @u_int64_t long inlen, @In byte[] key, @In @ssize_t long keylen); // int crypto_generichash_init(crypto_generichash_state * state, const unsigned char * key, const size_t keylen, const size_t outlen); int crypto_generichash_init(@Out Pointer state, @In byte[] key, @In @ssize_t long keylen, @In @ssize_t long outlen); // int crypto_generichash_update(crypto_generichash_state * state, const unsigned char * in, unsigned long long inlen); int crypto_generichash_update(/*both*/ Pointer state, @In byte[] in, @In @u_int64_t long inlen); // int crypto_generichash_final(crypto_generichash_state * state, unsigned char * out, const size_t outlen); int crypto_generichash_final(/*both*/ Pointer state, @Out byte[] out, @In @ssize_t long outlen); // void crypto_generichash_keygen(unsigned char[] k); void crypto_generichash_keygen(@Out byte[] k); // size_t crypto_hash_bytes(void); @ssize_t long crypto_hash_bytes(); // int crypto_hash(unsigned char * out, const unsigned char * in, unsigned long long inlen); int crypto_hash(@Out byte[] out, @In byte[] in, @In @u_int64_t long inlen); // const char * crypto_hash_primitive(void); String crypto_hash_primitive(); // size_t crypto_kdf_blake2b_bytes_min(void); @ssize_t long crypto_kdf_blake2b_bytes_min(); // size_t crypto_kdf_blake2b_bytes_max(void); @ssize_t long crypto_kdf_blake2b_bytes_max(); // size_t crypto_kdf_blake2b_contextbytes(void); @ssize_t long crypto_kdf_blake2b_contextbytes(); // size_t crypto_kdf_blake2b_keybytes(void); @ssize_t long crypto_kdf_blake2b_keybytes(); // int crypto_kdf_blake2b_derive_from_key(unsigned char * subkey, size_t subkey_len, uint64_t subkey_id, const char[] ctx, const unsigned char[] key); int crypto_kdf_blake2b_derive_from_key( @Out byte[] subkey, @In @ssize_t long subkey_len, @In @u_int64_t long subkey_id, @In byte[] ctx, @In Pointer key); // size_t crypto_kdf_bytes_min(void); @ssize_t long crypto_kdf_bytes_min(); // size_t crypto_kdf_bytes_max(void); @ssize_t long crypto_kdf_bytes_max(); // size_t crypto_kdf_contextbytes(void); @ssize_t long crypto_kdf_contextbytes(); // size_t crypto_kdf_keybytes(void); @ssize_t long crypto_kdf_keybytes(); // const char * crypto_kdf_primitive(void); String crypto_kdf_primitive(); // int crypto_kdf_derive_from_key(unsigned char * subkey, size_t subkey_len, uint64_t subkey_id, const char[] ctx, const unsigned char[] key); int crypto_kdf_derive_from_key( @Out byte[] subkey, @In @ssize_t long subkey_len, @In @u_int64_t long subkey_id, @In byte[] ctx, @In Pointer key); // void crypto_kdf_keygen(unsigned char[] k); void crypto_kdf_keygen(@Out Pointer k); // size_t crypto_kx_publickeybytes(void); @ssize_t long crypto_kx_publickeybytes(); // size_t crypto_kx_secretkeybytes(void); @ssize_t long crypto_kx_secretkeybytes(); // size_t crypto_kx_seedbytes(void); @ssize_t long crypto_kx_seedbytes(); // size_t crypto_kx_sessionkeybytes(void); @ssize_t long crypto_kx_sessionkeybytes(); // const char * crypto_kx_primitive(void); String crypto_kx_primitive(); // int crypto_kx_seed_keypair(unsigned char[] pk, unsigned char[] sk, const unsigned char[] seed); int crypto_kx_seed_keypair(@Out Pointer pk, @Out Pointer sk, @In Pointer seed); // int crypto_kx_keypair(unsigned char[] pk, unsigned char[] sk); int crypto_kx_keypair(@Out Pointer pk, @Out Pointer sk); // int crypto_kx_client_session_keys(unsigned char[] rx, unsigned char[] tx, const unsigned char[] client_pk, const unsigned char[] client_sk, const unsigned char[] server_pk); int crypto_kx_client_session_keys( @Out Pointer rx, @Out Pointer tx, @In Pointer client_pk, @In Pointer client_sk, @In Pointer server_pk); // int crypto_kx_server_session_keys(unsigned char[] rx, unsigned char[] tx, const unsigned char[] server_pk, const unsigned char[] server_sk, const unsigned char[] client_pk); int crypto_kx_server_session_keys( @Out Pointer rx, @Out Pointer tx, @In Pointer server_pk, @In Pointer server_sk, @In Pointer client_pk); // size_t crypto_onetimeauth_poly1305_statebytes(void); @ssize_t long crypto_onetimeauth_poly1305_statebytes(); // size_t crypto_onetimeauth_poly1305_bytes(void); @ssize_t long crypto_onetimeauth_poly1305_bytes(); // size_t crypto_onetimeauth_poly1305_keybytes(void); @ssize_t long crypto_onetimeauth_poly1305_keybytes(); // int crypto_onetimeauth_poly1305(unsigned char * out, const unsigned char * in, unsigned long long inlen, const unsigned char * k); int crypto_onetimeauth_poly1305(@Out byte[] out, @In byte[] in, @In @u_int64_t long inlen, @In byte[] k); // int crypto_onetimeauth_poly1305_verify(const unsigned char * h, const unsigned char * in, unsigned long long inlen, const unsigned char * k); int crypto_onetimeauth_poly1305_verify(@In byte[] h, @In byte[] in, @In @u_int64_t long inlen, @In byte[] k); // int crypto_onetimeauth_poly1305_init(crypto_onetimeauth_poly1305_state * state, const unsigned char * key); int crypto_onetimeauth_poly1305_init(@Out Pointer state, @In byte[] key); // int crypto_onetimeauth_poly1305_update(crypto_onetimeauth_poly1305_state * state, const unsigned char * in, unsigned long long inlen); int crypto_onetimeauth_poly1305_update(/*both*/ Pointer state, @In byte[] in, @In @u_int64_t long inlen); // int crypto_onetimeauth_poly1305_final(crypto_onetimeauth_poly1305_state * state, unsigned char * out); int crypto_onetimeauth_poly1305_final(/*both*/ Pointer state, @Out byte[] out); // void crypto_onetimeauth_poly1305_keygen(unsigned char[] k); void crypto_onetimeauth_poly1305_keygen(@Out byte[] k); // size_t crypto_onetimeauth_statebytes(void); @ssize_t long crypto_onetimeauth_statebytes(); // size_t crypto_onetimeauth_bytes(void); @ssize_t long crypto_onetimeauth_bytes(); // size_t crypto_onetimeauth_keybytes(void); @ssize_t long crypto_onetimeauth_keybytes(); // const char * crypto_onetimeauth_primitive(void); String crypto_onetimeauth_primitive(); // int crypto_onetimeauth(unsigned char * out, const unsigned char * in, unsigned long long inlen, const unsigned char * k); int crypto_onetimeauth(@Out byte[] out, @In byte[] in, @In @u_int64_t long inlen, @In byte[] k); // int crypto_onetimeauth_verify(const unsigned char * h, const unsigned char * in, unsigned long long inlen, const unsigned char * k); int crypto_onetimeauth_verify(@In byte[] h, @In byte[] in, @In @u_int64_t long inlen, @In byte[] k); // int crypto_onetimeauth_init(crypto_onetimeauth_state * state, const unsigned char * key); int crypto_onetimeauth_init(@Out Pointer state, @In byte[] key); // int crypto_onetimeauth_update(crypto_onetimeauth_state * state, const unsigned char * in, unsigned long long inlen); int crypto_onetimeauth_update(/*both*/ Pointer state, @In byte[] in, @In @u_int64_t long inlen); // int crypto_onetimeauth_final(crypto_onetimeauth_state * state, unsigned char * out); int crypto_onetimeauth_final(/*both*/ Pointer state, @Out byte[] out); // void crypto_onetimeauth_keygen(unsigned char[] k); void crypto_onetimeauth_keygen(@Out byte[] k); // int crypto_pwhash_argon2i_alg_argon2i13(void); int crypto_pwhash_argon2i_alg_argon2i13(); // size_t crypto_pwhash_argon2i_bytes_min(void); @ssize_t long crypto_pwhash_argon2i_bytes_min(); // size_t crypto_pwhash_argon2i_bytes_max(void); @ssize_t long crypto_pwhash_argon2i_bytes_max(); // size_t crypto_pwhash_argon2i_passwd_min(void); @ssize_t long crypto_pwhash_argon2i_passwd_min(); // size_t crypto_pwhash_argon2i_passwd_max(void); @ssize_t long crypto_pwhash_argon2i_passwd_max(); // size_t crypto_pwhash_argon2i_saltbytes(void); @ssize_t long crypto_pwhash_argon2i_saltbytes(); // size_t crypto_pwhash_argon2i_strbytes(void); @ssize_t long crypto_pwhash_argon2i_strbytes(); // const char * crypto_pwhash_argon2i_strprefix(void); String crypto_pwhash_argon2i_strprefix(); // size_t crypto_pwhash_argon2i_opslimit_min(void); @ssize_t long crypto_pwhash_argon2i_opslimit_min(); // size_t crypto_pwhash_argon2i_opslimit_max(void); @ssize_t long crypto_pwhash_argon2i_opslimit_max(); // size_t crypto_pwhash_argon2i_memlimit_min(void); @ssize_t long crypto_pwhash_argon2i_memlimit_min(); // size_t crypto_pwhash_argon2i_memlimit_max(void); @ssize_t long crypto_pwhash_argon2i_memlimit_max(); // size_t crypto_pwhash_argon2i_opslimit_interactive(void); @ssize_t long crypto_pwhash_argon2i_opslimit_interactive(); // size_t crypto_pwhash_argon2i_memlimit_interactive(void); @ssize_t long crypto_pwhash_argon2i_memlimit_interactive(); // size_t crypto_pwhash_argon2i_opslimit_moderate(void); @ssize_t long crypto_pwhash_argon2i_opslimit_moderate(); // size_t crypto_pwhash_argon2i_memlimit_moderate(void); @ssize_t long crypto_pwhash_argon2i_memlimit_moderate(); // size_t crypto_pwhash_argon2i_opslimit_sensitive(void); @ssize_t long crypto_pwhash_argon2i_opslimit_sensitive(); // size_t crypto_pwhash_argon2i_memlimit_sensitive(void); @ssize_t long crypto_pwhash_argon2i_memlimit_sensitive(); // int crypto_pwhash_argon2i(unsigned char *const out, unsigned long long outlen, const char *const passwd, unsigned long long passwdlen, const unsigned char *const salt, unsigned long long opslimit, size_t memlimit, int alg); int crypto_pwhash_argon2i( @Out byte[] out, @In @u_int64_t long outlen, @In byte[] passwd, @In @u_int64_t long passwdlen, @In byte[] salt, @In @u_int64_t long opslimit, @In @ssize_t long memlimit, @In int alg); // int crypto_pwhash_argon2i_str(char[] out, const char *const passwd, unsigned long long passwdlen, unsigned long long opslimit, size_t memlimit); int crypto_pwhash_argon2i_str( @Out byte[] out, @In byte[] passwd, @In @u_int64_t long passwdlen, @In @u_int64_t long opslimit, @In @ssize_t long memlimit); // int crypto_pwhash_argon2i_str_verify(const char[] str, const char *const passwd, unsigned long long passwdlen); int crypto_pwhash_argon2i_str_verify(@In byte[] str, @In byte[] passwd, @In @u_int64_t long passwdlen); // int crypto_pwhash_argon2i_str_needs_rehash(const char[] str, unsigned long long opslimit, size_t memlimit); int crypto_pwhash_argon2i_str_needs_rehash(@In byte[] str, @In @u_int64_t long opslimit, @In @ssize_t long memlimit); // int crypto_pwhash_argon2id_alg_argon2id13(void); int crypto_pwhash_argon2id_alg_argon2id13(); // size_t crypto_pwhash_argon2id_bytes_min(void); @ssize_t long crypto_pwhash_argon2id_bytes_min(); // size_t crypto_pwhash_argon2id_bytes_max(void); @ssize_t long crypto_pwhash_argon2id_bytes_max(); // size_t crypto_pwhash_argon2id_passwd_min(void); @ssize_t long crypto_pwhash_argon2id_passwd_min(); // size_t crypto_pwhash_argon2id_passwd_max(void); @ssize_t long crypto_pwhash_argon2id_passwd_max(); // size_t crypto_pwhash_argon2id_saltbytes(void); @ssize_t long crypto_pwhash_argon2id_saltbytes(); // size_t crypto_pwhash_argon2id_strbytes(void); @ssize_t long crypto_pwhash_argon2id_strbytes(); // const char * crypto_pwhash_argon2id_strprefix(void); String crypto_pwhash_argon2id_strprefix(); // size_t crypto_pwhash_argon2id_opslimit_min(void); @ssize_t long crypto_pwhash_argon2id_opslimit_min(); // size_t crypto_pwhash_argon2id_opslimit_max(void); @ssize_t long crypto_pwhash_argon2id_opslimit_max(); // size_t crypto_pwhash_argon2id_memlimit_min(void); @ssize_t long crypto_pwhash_argon2id_memlimit_min(); // size_t crypto_pwhash_argon2id_memlimit_max(void); @ssize_t long crypto_pwhash_argon2id_memlimit_max(); // size_t crypto_pwhash_argon2id_opslimit_interactive(void); @ssize_t long crypto_pwhash_argon2id_opslimit_interactive(); // size_t crypto_pwhash_argon2id_memlimit_interactive(void); @ssize_t long crypto_pwhash_argon2id_memlimit_interactive(); // size_t crypto_pwhash_argon2id_opslimit_moderate(void); @ssize_t long crypto_pwhash_argon2id_opslimit_moderate(); // size_t crypto_pwhash_argon2id_memlimit_moderate(void); @ssize_t long crypto_pwhash_argon2id_memlimit_moderate(); // size_t crypto_pwhash_argon2id_opslimit_sensitive(void); @ssize_t long crypto_pwhash_argon2id_opslimit_sensitive(); // size_t crypto_pwhash_argon2id_memlimit_sensitive(void); @ssize_t long crypto_pwhash_argon2id_memlimit_sensitive(); // int crypto_pwhash_argon2id(unsigned char *const out, unsigned long long outlen, const char *const passwd, unsigned long long passwdlen, const unsigned char *const salt, unsigned long long opslimit, size_t memlimit, int alg); int crypto_pwhash_argon2id( @Out byte[] out, @In @u_int64_t long outlen, @In byte[] passwd, @In @u_int64_t long passwdlen, @In byte[] salt, @In @u_int64_t long opslimit, @In @ssize_t long memlimit, @In int alg); // int crypto_pwhash_argon2id_str(char[] out, const char *const passwd, unsigned long long passwdlen, unsigned long long opslimit, size_t memlimit); int crypto_pwhash_argon2id_str( @Out byte[] out, @In byte[] passwd, @In @u_int64_t long passwdlen, @In @u_int64_t long opslimit, @In @ssize_t long memlimit); // int crypto_pwhash_argon2id_str_verify(const char[] str, const char *const passwd, unsigned long long passwdlen); int crypto_pwhash_argon2id_str_verify(@In byte[] str, @In byte[] passwd, @In @u_int64_t long passwdlen); // int crypto_pwhash_argon2id_str_needs_rehash(const char[] str, unsigned long long opslimit, size_t memlimit); int crypto_pwhash_argon2id_str_needs_rehash(@In byte[] str, @In @u_int64_t long opslimit, @In @ssize_t long memlimit); // int crypto_pwhash_alg_argon2i13(void); int crypto_pwhash_alg_argon2i13(); // int crypto_pwhash_alg_argon2id13(void); int crypto_pwhash_alg_argon2id13(); // int crypto_pwhash_alg_default(void); int crypto_pwhash_alg_default(); // size_t crypto_pwhash_bytes_min(void); @ssize_t long crypto_pwhash_bytes_min(); // size_t crypto_pwhash_bytes_max(void); @ssize_t long crypto_pwhash_bytes_max(); // size_t crypto_pwhash_passwd_min(void); @ssize_t long crypto_pwhash_passwd_min(); // size_t crypto_pwhash_passwd_max(void); @ssize_t long crypto_pwhash_passwd_max(); // size_t crypto_pwhash_saltbytes(void); @ssize_t long crypto_pwhash_saltbytes(); // size_t crypto_pwhash_strbytes(void); @ssize_t long crypto_pwhash_strbytes(); // const char * crypto_pwhash_strprefix(void); String crypto_pwhash_strprefix(); // size_t crypto_pwhash_opslimit_min(void); @ssize_t long crypto_pwhash_opslimit_min(); // size_t crypto_pwhash_opslimit_max(void); @ssize_t long crypto_pwhash_opslimit_max(); // size_t crypto_pwhash_memlimit_min(void); @ssize_t long crypto_pwhash_memlimit_min(); // size_t crypto_pwhash_memlimit_max(void); @ssize_t long crypto_pwhash_memlimit_max(); // size_t crypto_pwhash_opslimit_interactive(void); @ssize_t long crypto_pwhash_opslimit_interactive(); // size_t crypto_pwhash_memlimit_interactive(void); @ssize_t long crypto_pwhash_memlimit_interactive(); // size_t crypto_pwhash_opslimit_moderate(void); @ssize_t long crypto_pwhash_opslimit_moderate(); // size_t crypto_pwhash_memlimit_moderate(void); @ssize_t long crypto_pwhash_memlimit_moderate(); // size_t crypto_pwhash_opslimit_sensitive(void); @ssize_t long crypto_pwhash_opslimit_sensitive(); // size_t crypto_pwhash_memlimit_sensitive(void); @ssize_t long crypto_pwhash_memlimit_sensitive(); // int crypto_pwhash(unsigned char *const out, unsigned long long outlen, const char *const passwd, unsigned long long passwdlen, const unsigned char *const salt, unsigned long long opslimit, size_t memlimit, int alg); int crypto_pwhash( @Out byte[] out, @In @u_int64_t long outlen, @In byte[] passwd, @In @u_int64_t long passwdlen, @In Pointer salt, @In @u_int64_t long opslimit, @In @ssize_t long memlimit, @In int alg); // int crypto_pwhash_str(char[] out, const char *const passwd, unsigned long long passwdlen, unsigned long long opslimit, size_t memlimit); int crypto_pwhash_str( @Out byte[] out, @In byte[] passwd, @In @u_int64_t long passwdlen, @In @u_int64_t long opslimit, @In @ssize_t long memlimit); // int crypto_pwhash_str_alg(char[] out, const char *const passwd, unsigned long long passwdlen, unsigned long long opslimit, size_t memlimit, int alg); int crypto_pwhash_str_alg( @Out byte[] out, @In byte[] passwd, @In @u_int64_t long passwdlen, @In @u_int64_t long opslimit, @In @ssize_t long memlimit, @In int alg); // int crypto_pwhash_str_verify(const char[] str, const char *const passwd, unsigned long long passwdlen); int crypto_pwhash_str_verify(@In Pointer str, @In byte[] passwd, @In @u_int64_t long passwdlen); // int crypto_pwhash_str_needs_rehash(const char[] str, unsigned long long opslimit, size_t memlimit); int crypto_pwhash_str_needs_rehash(@In Pointer str, @In @u_int64_t long opslimit, @In @ssize_t long memlimit); // const char * crypto_pwhash_primitive(void); String crypto_pwhash_primitive(); // size_t crypto_scalarmult_curve25519_bytes(void); @ssize_t long crypto_scalarmult_curve25519_bytes(); // size_t crypto_scalarmult_curve25519_scalarbytes(void); @ssize_t long crypto_scalarmult_curve25519_scalarbytes(); // int crypto_scalarmult_curve25519(unsigned char * q, const unsigned char * n, const unsigned char * p); int crypto_scalarmult_curve25519(@Out byte[] q, @In byte[] n, @In byte[] p); // int crypto_scalarmult_curve25519_base(unsigned char * q, const unsigned char * n); int crypto_scalarmult_curve25519_base(@Out byte[] q, @In byte[] n); // size_t crypto_scalarmult_bytes(void); @ssize_t long crypto_scalarmult_bytes(); // size_t crypto_scalarmult_scalarbytes(void); @ssize_t long crypto_scalarmult_scalarbytes(); // const char * crypto_scalarmult_primitive(void); String crypto_scalarmult_primitive(); // int crypto_scalarmult_base(unsigned char * q, const unsigned char * n); int crypto_scalarmult_base(@Out Pointer q, @In Pointer n); // int crypto_scalarmult(unsigned char * q, const unsigned char * n, const unsigned char * p); int crypto_scalarmult(@Out byte[] q, @In byte[] n, @In byte[] p); // size_t crypto_secretbox_xsalsa20poly1305_keybytes(void); @ssize_t long crypto_secretbox_xsalsa20poly1305_keybytes(); // size_t crypto_secretbox_xsalsa20poly1305_noncebytes(void); @ssize_t long crypto_secretbox_xsalsa20poly1305_noncebytes(); // size_t crypto_secretbox_xsalsa20poly1305_macbytes(void); @ssize_t long crypto_secretbox_xsalsa20poly1305_macbytes(); // size_t crypto_secretbox_xsalsa20poly1305_messagebytes_max(void); @ssize_t long crypto_secretbox_xsalsa20poly1305_messagebytes_max(); // int crypto_secretbox_xsalsa20poly1305(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_secretbox_xsalsa20poly1305( @Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In byte[] k); // int crypto_secretbox_xsalsa20poly1305_open(unsigned char * m, const unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_secretbox_xsalsa20poly1305_open( @Out byte[] m, @In byte[] c, @In @u_int64_t long clen, @In byte[] n, @In byte[] k); // void crypto_secretbox_xsalsa20poly1305_keygen(unsigned char[] k); void crypto_secretbox_xsalsa20poly1305_keygen(@Out byte[] k); // size_t crypto_secretbox_xsalsa20poly1305_boxzerobytes(void); @ssize_t long crypto_secretbox_xsalsa20poly1305_boxzerobytes(); // size_t crypto_secretbox_xsalsa20poly1305_zerobytes(void); @ssize_t long crypto_secretbox_xsalsa20poly1305_zerobytes(); // size_t crypto_secretbox_keybytes(void); @ssize_t long crypto_secretbox_keybytes(); // size_t crypto_secretbox_noncebytes(void); @ssize_t long crypto_secretbox_noncebytes(); // size_t crypto_secretbox_macbytes(void); @ssize_t long crypto_secretbox_macbytes(); // const char * crypto_secretbox_primitive(void); String crypto_secretbox_primitive(); // size_t crypto_secretbox_messagebytes_max(void); @ssize_t long crypto_secretbox_messagebytes_max(); // int crypto_secretbox_easy(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_secretbox_easy(@Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In Pointer n, @In Pointer k); // int crypto_secretbox_open_easy(unsigned char * m, const unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_secretbox_open_easy(@Out byte[] m, @In byte[] c, @In @u_int64_t long clen, @In Pointer n, @In Pointer k); // int crypto_secretbox_detached(unsigned char * c, unsigned char * mac, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_secretbox_detached( @Out byte[] c, @Out byte[] mac, @In byte[] m, @In @u_int64_t long mlen, @In Pointer n, @In Pointer k); // int crypto_secretbox_open_detached(unsigned char * m, const unsigned char * c, const unsigned char * mac, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_secretbox_open_detached( @Out byte[] m, @In byte[] c, @In byte[] mac, @In @u_int64_t long clen, @In Pointer n, @In Pointer k); // void crypto_secretbox_keygen(unsigned char[] k); void crypto_secretbox_keygen(@Out Pointer k); // size_t crypto_secretbox_zerobytes(void); @ssize_t long crypto_secretbox_zerobytes(); // size_t crypto_secretbox_boxzerobytes(void); @ssize_t long crypto_secretbox_boxzerobytes(); // int crypto_secretbox(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_secretbox(@Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In byte[] k); // int crypto_secretbox_open(unsigned char * m, const unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_secretbox_open(@Out byte[] m, @In byte[] c, @In @u_int64_t long clen, @In byte[] n, @In byte[] k); // size_t crypto_stream_chacha20_keybytes(void); @ssize_t long crypto_stream_chacha20_keybytes(); // size_t crypto_stream_chacha20_noncebytes(void); @ssize_t long crypto_stream_chacha20_noncebytes(); // size_t crypto_stream_chacha20_messagebytes_max(void); @ssize_t long crypto_stream_chacha20_messagebytes_max(); // int crypto_stream_chacha20(unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_stream_chacha20(@Out byte[] c, @In @u_int64_t long clen, @In byte[] n, @In byte[] k); // int crypto_stream_chacha20_xor(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_stream_chacha20_xor(@Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In byte[] k); // int crypto_stream_chacha20_xor_ic(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, uint64_t ic, const unsigned char * k); int crypto_stream_chacha20_xor_ic( @Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In @u_int64_t long ic, @In byte[] k); // void crypto_stream_chacha20_keygen(unsigned char[] k); void crypto_stream_chacha20_keygen(@Out byte[] k); // size_t crypto_stream_chacha20_ietf_keybytes(void); @ssize_t long crypto_stream_chacha20_ietf_keybytes(); // size_t crypto_stream_chacha20_ietf_noncebytes(void); @ssize_t long crypto_stream_chacha20_ietf_noncebytes(); // size_t crypto_stream_chacha20_ietf_messagebytes_max(void); @ssize_t long crypto_stream_chacha20_ietf_messagebytes_max(); // int crypto_stream_chacha20_ietf(unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_stream_chacha20_ietf(@Out byte[] c, @In @u_int64_t long clen, @In byte[] n, @In byte[] k); // int crypto_stream_chacha20_ietf_xor(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_stream_chacha20_ietf_xor( @Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In byte[] k); // int crypto_stream_chacha20_ietf_xor_ic(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, uint32_t ic, const unsigned char * k); int crypto_stream_chacha20_ietf_xor_ic( @Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In @u_int32_t int ic, @In byte[] k); // void crypto_stream_chacha20_ietf_keygen(unsigned char[] k); void crypto_stream_chacha20_ietf_keygen(@Out byte[] k); // size_t crypto_secretstream_xchacha20poly1305_abytes(void); @ssize_t long crypto_secretstream_xchacha20poly1305_abytes(); // size_t crypto_secretstream_xchacha20poly1305_headerbytes(void); @ssize_t long crypto_secretstream_xchacha20poly1305_headerbytes(); // size_t crypto_secretstream_xchacha20poly1305_keybytes(void); @ssize_t long crypto_secretstream_xchacha20poly1305_keybytes(); // size_t crypto_secretstream_xchacha20poly1305_messagebytes_max(void); @ssize_t long crypto_secretstream_xchacha20poly1305_messagebytes_max(); // unsigned char crypto_secretstream_xchacha20poly1305_tag_message(void); @u_int8_t char crypto_secretstream_xchacha20poly1305_tag_message(); // unsigned char crypto_secretstream_xchacha20poly1305_tag_push(void); @u_int8_t char crypto_secretstream_xchacha20poly1305_tag_push(); // unsigned char crypto_secretstream_xchacha20poly1305_tag_rekey(void); @u_int8_t char crypto_secretstream_xchacha20poly1305_tag_rekey(); // unsigned char crypto_secretstream_xchacha20poly1305_tag_final(void); @u_int8_t char crypto_secretstream_xchacha20poly1305_tag_final(); // size_t crypto_secretstream_xchacha20poly1305_statebytes(void); @ssize_t long crypto_secretstream_xchacha20poly1305_statebytes(); // void crypto_secretstream_xchacha20poly1305_keygen(unsigned char[] k); void crypto_secretstream_xchacha20poly1305_keygen(@Out Pointer k); // int crypto_secretstream_xchacha20poly1305_init_push(crypto_secretstream_xchacha20poly1305_state * state, unsigned char[] header, const unsigned char[] k); int crypto_secretstream_xchacha20poly1305_init_push(/*both*/ Pointer state, @Out byte[] header, @In Pointer k); // int crypto_secretstream_xchacha20poly1305_push(crypto_secretstream_xchacha20poly1305_state * state, unsigned char * c, unsigned long long * clen_p, const unsigned char * m, unsigned long long mlen, const unsigned char * ad, unsigned long long adlen, unsigned char tag); int crypto_secretstream_xchacha20poly1305_push( /*both*/ Pointer state, @Out byte[] c, @Out LongLongByReference clen_p, @In byte[] m, @In @u_int64_t long mlen, @In byte[] ad, @In @u_int64_t long adlen, @In @u_int8_t byte tag); // int crypto_secretstream_xchacha20poly1305_init_pull(crypto_secretstream_xchacha20poly1305_state * state, const unsigned char[] header, const unsigned char[] k); int crypto_secretstream_xchacha20poly1305_init_pull(/*both*/ Pointer state, @In byte[] header, @In Pointer k); // int crypto_secretstream_xchacha20poly1305_pull(crypto_secretstream_xchacha20poly1305_state * state, unsigned char * m, unsigned long long * mlen_p, unsigned char * tag_p, const unsigned char * c, unsigned long long clen, const unsigned char * ad, unsigned long long adlen); int crypto_secretstream_xchacha20poly1305_pull( /*both*/ Pointer state, @Out byte[] m, @Out LongLongByReference mlen_p, @Out ByteByReference tag_p, @In byte[] c, @In @u_int64_t long clen, @In byte[] ad, @In @u_int64_t long adlen); // void crypto_secretstream_xchacha20poly1305_rekey(crypto_secretstream_xchacha20poly1305_state * state); void crypto_secretstream_xchacha20poly1305_rekey(/*both*/ Pointer state); // size_t crypto_shorthash_siphash24_bytes(void); @ssize_t long crypto_shorthash_siphash24_bytes(); // size_t crypto_shorthash_siphash24_keybytes(void); @ssize_t long crypto_shorthash_siphash24_keybytes(); // int crypto_shorthash_siphash24(unsigned char * out, const unsigned char * in, unsigned long long inlen, const unsigned char * k); int crypto_shorthash_siphash24(@Out byte[] out, @In byte[] in, @In @u_int64_t long inlen, @In byte[] k); // size_t crypto_shorthash_siphashx24_bytes(void); @ssize_t long crypto_shorthash_siphashx24_bytes(); // size_t crypto_shorthash_siphashx24_keybytes(void); @ssize_t long crypto_shorthash_siphashx24_keybytes(); // int crypto_shorthash_siphashx24(unsigned char * out, const unsigned char * in, unsigned long long inlen, const unsigned char * k); int crypto_shorthash_siphashx24(@Out byte[] out, @In byte[] in, @In @u_int64_t long inlen, @In byte[] k); // size_t crypto_shorthash_bytes(void); @ssize_t long crypto_shorthash_bytes(); // size_t crypto_shorthash_keybytes(void); @ssize_t long crypto_shorthash_keybytes(); // const char * crypto_shorthash_primitive(void); String crypto_shorthash_primitive(); // int crypto_shorthash(unsigned char * out, const unsigned char * in, unsigned long long inlen, const unsigned char * k); int crypto_shorthash(@Out byte[] out, @In byte[] in, @In @u_int64_t long inlen, @In byte[] k); // void crypto_shorthash_keygen(unsigned char[] k); void crypto_shorthash_keygen(@Out byte[] k); // size_t crypto_sign_ed25519ph_statebytes(void); @ssize_t long crypto_sign_ed25519ph_statebytes(); // size_t crypto_sign_ed25519_bytes(void); @ssize_t long crypto_sign_ed25519_bytes(); // size_t crypto_sign_ed25519_seedbytes(void); @ssize_t long crypto_sign_ed25519_seedbytes(); // size_t crypto_sign_ed25519_publickeybytes(void); @ssize_t long crypto_sign_ed25519_publickeybytes(); // size_t crypto_sign_ed25519_secretkeybytes(void); @ssize_t long crypto_sign_ed25519_secretkeybytes(); // size_t crypto_sign_ed25519_messagebytes_max(void); @ssize_t long crypto_sign_ed25519_messagebytes_max(); // int crypto_sign_ed25519(unsigned char * sm, unsigned long long * smlen_p, const unsigned char * m, unsigned long long mlen, const unsigned char * sk); int crypto_sign_ed25519( @Out byte[] sm, @Out LongLongByReference smlen_p, @In byte[] m, @In @u_int64_t long mlen, @In byte[] sk); // int crypto_sign_ed25519_open(unsigned char * m, unsigned long long * mlen_p, const unsigned char * sm, unsigned long long smlen, const unsigned char * pk); int crypto_sign_ed25519_open( @Out byte[] m, @Out LongLongByReference mlen_p, @In byte[] sm, @In @u_int64_t long smlen, @In byte[] pk); // int crypto_sign_ed25519_detached(unsigned char * sig, unsigned long long * siglen_p, const unsigned char * m, unsigned long long mlen, const unsigned char * sk); int crypto_sign_ed25519_detached( @Out byte[] sig, @Out LongLongByReference siglen_p, @In byte[] m, @In @u_int64_t long mlen, @In byte[] sk); // int crypto_sign_ed25519_verify_detached(const unsigned char * sig, const unsigned char * m, unsigned long long mlen, const unsigned char * pk); int crypto_sign_ed25519_verify_detached(@In byte[] sig, @In byte[] m, @In @u_int64_t long mlen, @In byte[] pk); // int crypto_sign_ed25519_keypair(unsigned char * pk, unsigned char * sk); int crypto_sign_ed25519_keypair(@Out byte[] pk, @Out byte[] sk); // int crypto_sign_ed25519_seed_keypair(unsigned char * pk, unsigned char * sk, const unsigned char * seed); int crypto_sign_ed25519_seed_keypair(@Out byte[] pk, @Out byte[] sk, @In byte[] seed); // int crypto_sign_ed25519_pk_to_curve25519(unsigned char * curve25519_pk, const unsigned char * ed25519_pk); int crypto_sign_ed25519_pk_to_curve25519(@Out byte[] curve25519_pk, @In byte[] ed25519_pk); // int crypto_sign_ed25519_sk_to_curve25519(unsigned char * curve25519_sk, const unsigned char * ed25519_sk); int crypto_sign_ed25519_sk_to_curve25519(@Out byte[] curve25519_sk, @In byte[] ed25519_sk); // int crypto_sign_ed25519_sk_to_seed(unsigned char * seed, const unsigned char * sk); int crypto_sign_ed25519_sk_to_seed(@Out byte[] seed, @In byte[] sk); // int crypto_sign_ed25519_sk_to_pk(unsigned char * pk, const unsigned char * sk); int crypto_sign_ed25519_sk_to_pk(@Out byte[] pk, @In byte[] sk); // int crypto_sign_ed25519ph_init(crypto_sign_ed25519ph_state * state); int crypto_sign_ed25519ph_init(@Out Pointer state); // int crypto_sign_ed25519ph_update(crypto_sign_ed25519ph_state * state, const unsigned char * m, unsigned long long mlen); int crypto_sign_ed25519ph_update(/*both*/ Pointer state, @In byte[] m, @In @u_int64_t long mlen); // int crypto_sign_ed25519ph_final_create(crypto_sign_ed25519ph_state * state, unsigned char * sig, unsigned long long * siglen_p, const unsigned char * sk); int crypto_sign_ed25519ph_final_create( /*both*/ Pointer state, @Out byte[] sig, @Out LongLongByReference siglen_p, @In byte[] sk); // int crypto_sign_ed25519ph_final_verify(crypto_sign_ed25519ph_state * state, unsigned char * sig, const unsigned char * pk); int crypto_sign_ed25519ph_final_verify(/*both*/ Pointer state, @In byte[] sig, @In byte[] pk); // size_t crypto_sign_statebytes(void); @ssize_t long crypto_sign_statebytes(); // size_t crypto_sign_bytes(void); @ssize_t long crypto_sign_bytes(); // size_t crypto_sign_seedbytes(void); @ssize_t long crypto_sign_seedbytes(); // size_t crypto_sign_publickeybytes(void); @ssize_t long crypto_sign_publickeybytes(); // size_t crypto_sign_secretkeybytes(void); @ssize_t long crypto_sign_secretkeybytes(); // size_t crypto_sign_messagebytes_max(void); @ssize_t long crypto_sign_messagebytes_max(); // const char * crypto_sign_primitive(void); String crypto_sign_primitive(); // int crypto_sign_seed_keypair(unsigned char * pk, unsigned char * sk, const unsigned char * seed); int crypto_sign_seed_keypair(@Out byte[] pk, @Out byte[] sk, @In byte[] seed); // int crypto_sign_keypair(unsigned char * pk, unsigned char * sk); int crypto_sign_keypair(@Out byte[] pk, @Out byte[] sk); // int crypto_sign(unsigned char * sm, unsigned long long * smlen_p, const unsigned char * m, unsigned long long mlen, const unsigned char * sk); int crypto_sign( @Out byte[] sm, @Out LongLongByReference smlen_p, @In byte[] m, @In @u_int64_t long mlen, @In byte[] sk); // int crypto_sign_open(unsigned char * m, unsigned long long * mlen_p, const unsigned char * sm, unsigned long long smlen, const unsigned char * pk); int crypto_sign_open( @Out byte[] m, @Out LongLongByReference mlen_p, @In byte[] sm, @In @u_int64_t long smlen, @In byte[] pk); // int crypto_sign_detached(unsigned char * sig, unsigned long long * siglen_p, const unsigned char * m, unsigned long long mlen, const unsigned char * sk); int crypto_sign_detached( @Out byte[] sig, @Out LongLongByReference siglen_p, @In byte[] m, @In @u_int64_t long mlen, @In byte[] sk); // int crypto_sign_verify_detached(const unsigned char * sig, const unsigned char * m, unsigned long long mlen, const unsigned char * pk); int crypto_sign_verify_detached(@In byte[] sig, @In byte[] m, @In @u_int64_t long mlen, @In byte[] pk); // int crypto_sign_init(crypto_sign_state * state); int crypto_sign_init(@Out Pointer state); // int crypto_sign_update(crypto_sign_state * state, const unsigned char * m, unsigned long long mlen); int crypto_sign_update(/*both*/ Pointer state, @In byte[] m, @In @u_int64_t long mlen); // int crypto_sign_final_create(crypto_sign_state * state, unsigned char * sig, unsigned long long * siglen_p, const unsigned char * sk); int crypto_sign_final_create( /*both*/ Pointer state, @Out byte[] sig, @Out LongLongByReference siglen_p, @In byte[] sk); // int crypto_sign_final_verify(crypto_sign_state * state, unsigned char * sig, const unsigned char * pk); int crypto_sign_final_verify(/*both*/ Pointer state, @In byte[] sig, @In byte[] pk); // size_t crypto_stream_keybytes(void); @ssize_t long crypto_stream_keybytes(); // size_t crypto_stream_noncebytes(void); @ssize_t long crypto_stream_noncebytes(); // size_t crypto_stream_messagebytes_max(void); @ssize_t long crypto_stream_messagebytes_max(); // const char * crypto_stream_primitive(void); String crypto_stream_primitive(); // int crypto_stream(unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_stream(@Out byte[] c, @In @u_int64_t long clen, @In byte[] n, @In byte[] k); // int crypto_stream_xor(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_stream_xor(@Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In byte[] k); // void crypto_stream_keygen(unsigned char[] k); void crypto_stream_keygen(@Out byte[] k); // size_t crypto_stream_salsa20_keybytes(void); @ssize_t long crypto_stream_salsa20_keybytes(); // size_t crypto_stream_salsa20_noncebytes(void); @ssize_t long crypto_stream_salsa20_noncebytes(); // size_t crypto_stream_salsa20_messagebytes_max(void); @ssize_t long crypto_stream_salsa20_messagebytes_max(); // int crypto_stream_salsa20(unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_stream_salsa20(@Out byte[] c, @In @u_int64_t long clen, @In byte[] n, @In byte[] k); // int crypto_stream_salsa20_xor(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_stream_salsa20_xor(@Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In byte[] k); // int crypto_stream_salsa20_xor_ic(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, uint64_t ic, const unsigned char * k); int crypto_stream_salsa20_xor_ic( @Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In @u_int64_t long ic, @In byte[] k); // void crypto_stream_salsa20_keygen(unsigned char[] k); void crypto_stream_salsa20_keygen(@Out byte[] k); // size_t crypto_verify_16_bytes(void); @ssize_t long crypto_verify_16_bytes(); // int crypto_verify_16(const unsigned char * x, const unsigned char * y); int crypto_verify_16(@In byte[] x, @In byte[] y); // size_t crypto_verify_32_bytes(void); @ssize_t long crypto_verify_32_bytes(); // int crypto_verify_32(const unsigned char * x, const unsigned char * y); int crypto_verify_32(@In byte[] x, @In byte[] y); // size_t crypto_verify_64_bytes(void); @ssize_t long crypto_verify_64_bytes(); // int crypto_verify_64(const unsigned char * x, const unsigned char * y); int crypto_verify_64(@In byte[] x, @In byte[] y); // const char * implementation_name(void); String implementation_name(); // uint32_t random(void); @u_int32_t int random(); // void stir(void); void stir(); // uint32_t uniform(const uint32_t upper_bound); @u_int32_t int uniform(@In @u_int32_t int upper_bound); // void buf(void *const buf, const size_t size); void buf(/*@In @Out*/ byte[] buf, @In @ssize_t long size); // int close(void); int close(); // size_t randombytes_seedbytes(void); @ssize_t long randombytes_seedbytes(); // void randombytes_buf(void *const buf, const size_t size); void randombytes_buf(@Out Pointer buf, @In @ssize_t long size); // void randombytes_buf_deterministic(void *const buf, const size_t size, const unsigned char[] seed); void randombytes_buf_deterministic(@Out byte[] buf, @In @ssize_t long size, @In byte[] seed); // uint32_t randombytes_random(void); @u_int32_t int randombytes_random(); // uint32_t randombytes_uniform(const uint32_t upper_bound); @u_int32_t int randombytes_uniform(@In @u_int32_t int upper_bound); // void randombytes_stir(void); void randombytes_stir(); // int randombytes_close(void); int randombytes_close(); // int randombytes_set_implementation(randombytes_implementation * impl); int randombytes_set_implementation(/*@In @Out*/ Pointer impl); // const char * randombytes_implementation_name(void); String randombytes_implementation_name(); // void randombytes(unsigned char *const buf, const unsigned long long buf_len); void randombytes(@Out byte[] buf, @In @u_int64_t long buf_len); // int sodium_runtime_has_neon(void); int sodium_runtime_has_neon(); // int sodium_runtime_has_sse2(void); int sodium_runtime_has_sse2(); // int sodium_runtime_has_sse3(void); int sodium_runtime_has_sse3(); // int sodium_runtime_has_ssse3(void); int sodium_runtime_has_ssse3(); // int sodium_runtime_has_sse41(void); int sodium_runtime_has_sse41(); // int sodium_runtime_has_avx(void); int sodium_runtime_has_avx(); // int sodium_runtime_has_avx2(void); int sodium_runtime_has_avx2(); // int sodium_runtime_has_avx512f(void); int sodium_runtime_has_avx512f(); // int sodium_runtime_has_pclmul(void); int sodium_runtime_has_pclmul(); // int sodium_runtime_has_aesni(void); int sodium_runtime_has_aesni(); // int sodium_runtime_has_rdrand(void); int sodium_runtime_has_rdrand(); // void sodium_memzero(void *const pnt, const size_t len); void sodium_memzero(/*@In @Out*/ Pointer pnt, @In @ssize_t long len); // void sodium_stackzero(const size_t len); void sodium_stackzero(@In @ssize_t long len); // int sodium_memcmp(const void *const b1_, const void *const b2_, size_t len); int sodium_memcmp(@In Pointer b1_, @In Pointer b2_, @In @ssize_t long len); // int sodium_compare(const unsigned char * b1_, const unsigned char * b2_, size_t len); int sodium_compare(@In Pointer b1_, @In Pointer b2_, @In @ssize_t long len); // int sodium_is_zero(const unsigned char * n, const size_t nlen); int sodium_is_zero(@In Pointer n, @In @ssize_t long nlen); // void sodium_increment(unsigned char * n, const size_t nlen); void sodium_increment(/*@In @Out*/ Pointer n, @In @ssize_t long nlen); // void sodium_add(unsigned char * a, const unsigned char * b, const size_t len); void sodium_add(/*@In @Out*/ Pointer a, @In Pointer b, @In @ssize_t long len); // char * sodium_bin2hex(char *const hex, const size_t hex_maxlen, const unsigned char *const bin, const size_t bin_len); // FIXME: JNR-FFI code generation fails for this method // byte[] sodium_bin2hex(@Out byte[] hex, @In @ssize_t long hex_maxlen, @In byte[] bin, @In @ssize_t long bin_len); // int sodium_hex2bin(unsigned char *const bin, const size_t bin_maxlen, const char *const hex, const size_t hex_len, const char *const ignore, size_t *const bin_len, const char * *const hex_end); int sodium_hex2bin( @Out byte[] bin, @In @ssize_t long bin_maxlen, @In byte[] hex, @In @ssize_t long hex_len, @In byte[] ignore, @Out LongLongByReference bin_len, /*@In @Out*/ Pointer hex_end); // size_t sodium_base64_encoded_len(const size_t bin_len, const int variant); @ssize_t long sodium_base64_encoded_len(@In @ssize_t long bin_len, @In int variant); // char * sodium_bin2base64(char *const b64, const size_t b64_maxlen, const unsigned char *const bin, const size_t bin_len, const int variant); // FIXME: JNR-FFI code generation fails for this method // byte[] sodium_bin2base64( // @Out byte[] b64, // @In @ssize_t long b64_maxlen, // @In byte[] bin, // @In @ssize_t long bin_len, // @In int variant); // int sodium_base642bin(unsigned char *const bin, const size_t bin_maxlen, const char *const b64, const size_t b64_len, const char *const ignore, size_t *const bin_len, const char * *const b64_end, const int variant); int sodium_base642bin( @Out byte[] bin, @In @ssize_t long bin_maxlen, @In byte[] b64, @In @ssize_t long b64_len, @In byte[] ignore, @Out LongLongByReference bin_len, /*@In @Out*/ Pointer b64_end, @In int variant); // int sodium_mlock(void *const addr, const size_t len); int sodium_mlock(/*@In @Out*/ Pointer addr, @In @ssize_t long len); // int sodium_munlock(void *const addr, const size_t len); int sodium_munlock(/*@In @Out*/ Pointer addr, @In @ssize_t long len); // void * sodium_malloc(const size_t size); Pointer sodium_malloc(@In @ssize_t long size); // void * sodium_allocarray(size_t count, size_t size); Pointer sodium_allocarray(@In @ssize_t long count, @In @ssize_t long size); // void sodium_free(void * ptr); void sodium_free(/*@In @Out*/ Pointer ptr); // int sodium_mprotect_noaccess(void * ptr); int sodium_mprotect_noaccess(/*@In @Out*/ Pointer ptr); // int sodium_mprotect_readonly(void * ptr); int sodium_mprotect_readonly(/*@In @Out*/ Pointer ptr); // int sodium_mprotect_readwrite(void * ptr); int sodium_mprotect_readwrite(/*@In @Out*/ Pointer ptr); // int sodium_pad(size_t * padded_buflen_p, unsigned char * buf, size_t unpadded_buflen, size_t blocksize, size_t max_buflen); int sodium_pad( @Out LongLongByReference padded_buflen_p, /*@In @Out*/ byte[] buf, @In @ssize_t long unpadded_buflen, @In @ssize_t long blocksize, @In @ssize_t long max_buflen); // int sodium_unpad(size_t * unpadded_buflen_p, const unsigned char * buf, size_t padded_buflen, size_t blocksize); int sodium_unpad( @Out LongLongByReference unpadded_buflen_p, @In byte[] buf, @In @ssize_t long padded_buflen, @In @ssize_t long blocksize); // size_t crypto_stream_xchacha20_keybytes(void); @ssize_t long crypto_stream_xchacha20_keybytes(); // size_t crypto_stream_xchacha20_noncebytes(void); @ssize_t long crypto_stream_xchacha20_noncebytes(); // size_t crypto_stream_xchacha20_messagebytes_max(void); @ssize_t long crypto_stream_xchacha20_messagebytes_max(); // int crypto_stream_xchacha20(unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_stream_xchacha20(@Out byte[] c, @In @u_int64_t long clen, @In byte[] n, @In byte[] k); // int crypto_stream_xchacha20_xor(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_stream_xchacha20_xor(@Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In byte[] k); // int crypto_stream_xchacha20_xor_ic(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, uint64_t ic, const unsigned char * k); int crypto_stream_xchacha20_xor_ic( @Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In @u_int64_t long ic, @In byte[] k); // void crypto_stream_xchacha20_keygen(unsigned char[] k); void crypto_stream_xchacha20_keygen(@Out byte[] k); // size_t crypto_box_curve25519xchacha20poly1305_seedbytes(void); @ssize_t long crypto_box_curve25519xchacha20poly1305_seedbytes(); // size_t crypto_box_curve25519xchacha20poly1305_publickeybytes(void); @ssize_t long crypto_box_curve25519xchacha20poly1305_publickeybytes(); // size_t crypto_box_curve25519xchacha20poly1305_secretkeybytes(void); @ssize_t long crypto_box_curve25519xchacha20poly1305_secretkeybytes(); // size_t crypto_box_curve25519xchacha20poly1305_beforenmbytes(void); @ssize_t long crypto_box_curve25519xchacha20poly1305_beforenmbytes(); // size_t crypto_box_curve25519xchacha20poly1305_noncebytes(void); @ssize_t long crypto_box_curve25519xchacha20poly1305_noncebytes(); // size_t crypto_box_curve25519xchacha20poly1305_macbytes(void); @ssize_t long crypto_box_curve25519xchacha20poly1305_macbytes(); // size_t crypto_box_curve25519xchacha20poly1305_messagebytes_max(void); @ssize_t long crypto_box_curve25519xchacha20poly1305_messagebytes_max(); // int crypto_box_curve25519xchacha20poly1305_seed_keypair(unsigned char * pk, unsigned char * sk, const unsigned char * seed); int crypto_box_curve25519xchacha20poly1305_seed_keypair(@Out byte[] pk, @Out byte[] sk, @In byte[] seed); // int crypto_box_curve25519xchacha20poly1305_keypair(unsigned char * pk, unsigned char * sk); int crypto_box_curve25519xchacha20poly1305_keypair(@Out byte[] pk, @Out byte[] sk); // int crypto_box_curve25519xchacha20poly1305_easy(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * pk, const unsigned char * sk); int crypto_box_curve25519xchacha20poly1305_easy( @Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In byte[] pk, @In byte[] sk); // int crypto_box_curve25519xchacha20poly1305_open_easy(unsigned char * m, const unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * pk, const unsigned char * sk); int crypto_box_curve25519xchacha20poly1305_open_easy( @Out byte[] m, @In byte[] c, @In @u_int64_t long clen, @In byte[] n, @In byte[] pk, @In byte[] sk); // int crypto_box_curve25519xchacha20poly1305_detached(unsigned char * c, unsigned char * mac, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * pk, const unsigned char * sk); int crypto_box_curve25519xchacha20poly1305_detached( @Out byte[] c, /*@In @Out*/ byte[] mac, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In byte[] pk, @In byte[] sk); // int crypto_box_curve25519xchacha20poly1305_open_detached(unsigned char * m, const unsigned char * c, const unsigned char * mac, unsigned long long clen, const unsigned char * n, const unsigned char * pk, const unsigned char * sk); int crypto_box_curve25519xchacha20poly1305_open_detached( @Out byte[] m, @In byte[] c, @In byte[] mac, @In @u_int64_t long clen, @In byte[] n, @In byte[] pk, @In byte[] sk); // int crypto_box_curve25519xchacha20poly1305_beforenm(unsigned char * k, const unsigned char * pk, const unsigned char * sk); int crypto_box_curve25519xchacha20poly1305_beforenm(@Out Pointer k, @In byte[] pk, @In byte[] sk); // int crypto_box_curve25519xchacha20poly1305_easy_afternm(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_box_curve25519xchacha20poly1305_easy_afternm( @Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In Pointer k); // int crypto_box_curve25519xchacha20poly1305_open_easy_afternm(unsigned char * m, const unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_box_curve25519xchacha20poly1305_open_easy_afternm( @Out byte[] m, @In byte[] c, @In @u_int64_t long clen, @In byte[] n, @In Pointer k); // int crypto_box_curve25519xchacha20poly1305_detached_afternm(unsigned char * c, unsigned char * mac, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_box_curve25519xchacha20poly1305_detached_afternm( @Out byte[] c, /*@In @Out*/ byte[] mac, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In Pointer k); // int crypto_box_curve25519xchacha20poly1305_open_detached_afternm(unsigned char * m, const unsigned char * c, const unsigned char * mac, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_box_curve25519xchacha20poly1305_open_detached_afternm( @Out byte[] m, @In byte[] c, @In byte[] mac, @In @u_int64_t long clen, @In byte[] n, @In Pointer k); // size_t crypto_box_curve25519xchacha20poly1305_sealbytes(void); @ssize_t long crypto_box_curve25519xchacha20poly1305_sealbytes(); // int crypto_box_curve25519xchacha20poly1305_seal(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * pk); int crypto_box_curve25519xchacha20poly1305_seal(@Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] pk); // int crypto_box_curve25519xchacha20poly1305_seal_open(unsigned char * m, const unsigned char * c, unsigned long long clen, const unsigned char * pk, const unsigned char * sk); int crypto_box_curve25519xchacha20poly1305_seal_open( @Out byte[] m, @In byte[] c, @In @u_int64_t long clen, @In byte[] pk, @In byte[] sk); // size_t crypto_core_ed25519_bytes(void); @ssize_t long crypto_core_ed25519_bytes(); // size_t crypto_core_ed25519_uniformbytes(void); @ssize_t long crypto_core_ed25519_uniformbytes(); // int crypto_core_ed25519_is_valid_point(const unsigned char * p); int crypto_core_ed25519_is_valid_point(@In byte[] p); // int crypto_core_ed25519_add(unsigned char * r, const unsigned char * p, const unsigned char * q); int crypto_core_ed25519_add(@Out byte[] r, @In byte[] p, @In byte[] q); // int crypto_core_ed25519_sub(unsigned char * r, const unsigned char * p, const unsigned char * q); int crypto_core_ed25519_sub(@Out byte[] r, @In byte[] p, @In byte[] q); // int crypto_core_ed25519_from_uniform(unsigned char * p, const unsigned char * r); int crypto_core_ed25519_from_uniform(@Out byte[] p, @In byte[] r); // size_t crypto_scalarmult_ed25519_bytes(void); @ssize_t long crypto_scalarmult_ed25519_bytes(); // size_t crypto_scalarmult_ed25519_scalarbytes(void); @ssize_t long crypto_scalarmult_ed25519_scalarbytes(); // int crypto_scalarmult_ed25519(unsigned char * q, const unsigned char * n, const unsigned char * p); int crypto_scalarmult_ed25519(@Out byte[] q, @In byte[] n, @In byte[] p); // int crypto_scalarmult_ed25519_base(unsigned char * q, const unsigned char * n); int crypto_scalarmult_ed25519_base(@Out byte[] q, @In byte[] n); // size_t crypto_secretbox_xchacha20poly1305_keybytes(void); @ssize_t long crypto_secretbox_xchacha20poly1305_keybytes(); // size_t crypto_secretbox_xchacha20poly1305_noncebytes(void); @ssize_t long crypto_secretbox_xchacha20poly1305_noncebytes(); // size_t crypto_secretbox_xchacha20poly1305_macbytes(void); @ssize_t long crypto_secretbox_xchacha20poly1305_macbytes(); // size_t crypto_secretbox_xchacha20poly1305_messagebytes_max(void); @ssize_t long crypto_secretbox_xchacha20poly1305_messagebytes_max(); // int crypto_secretbox_xchacha20poly1305_easy(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_secretbox_xchacha20poly1305_easy( @Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In byte[] k); // int crypto_secretbox_xchacha20poly1305_open_easy(unsigned char * m, const unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_secretbox_xchacha20poly1305_open_easy( @Out byte[] m, @In byte[] c, @In @u_int64_t long clen, @In byte[] n, @In byte[] k); // int crypto_secretbox_xchacha20poly1305_detached(unsigned char * c, unsigned char * mac, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_secretbox_xchacha20poly1305_detached( @Out byte[] c, @Out byte[] mac, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In byte[] k); // int crypto_secretbox_xchacha20poly1305_open_detached(unsigned char * m, const unsigned char * c, const unsigned char * mac, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_secretbox_xchacha20poly1305_open_detached( @Out byte[] m, @In byte[] c, @In byte[] mac, @In @u_int64_t long clen, @In byte[] n, @In byte[] k); // size_t crypto_pwhash_scryptsalsa208sha256_bytes_min(void); @ssize_t long crypto_pwhash_scryptsalsa208sha256_bytes_min(); // size_t crypto_pwhash_scryptsalsa208sha256_bytes_max(void); @ssize_t long crypto_pwhash_scryptsalsa208sha256_bytes_max(); // size_t crypto_pwhash_scryptsalsa208sha256_passwd_min(void); @ssize_t long crypto_pwhash_scryptsalsa208sha256_passwd_min(); // size_t crypto_pwhash_scryptsalsa208sha256_passwd_max(void); @ssize_t long crypto_pwhash_scryptsalsa208sha256_passwd_max(); // size_t crypto_pwhash_scryptsalsa208sha256_saltbytes(void); @ssize_t long crypto_pwhash_scryptsalsa208sha256_saltbytes(); // size_t crypto_pwhash_scryptsalsa208sha256_strbytes(void); @ssize_t long crypto_pwhash_scryptsalsa208sha256_strbytes(); // const char * crypto_pwhash_scryptsalsa208sha256_strprefix(void); String crypto_pwhash_scryptsalsa208sha256_strprefix(); // size_t crypto_pwhash_scryptsalsa208sha256_opslimit_min(void); @ssize_t long crypto_pwhash_scryptsalsa208sha256_opslimit_min(); // size_t crypto_pwhash_scryptsalsa208sha256_opslimit_max(void); @ssize_t long crypto_pwhash_scryptsalsa208sha256_opslimit_max(); // size_t crypto_pwhash_scryptsalsa208sha256_memlimit_min(void); @ssize_t long crypto_pwhash_scryptsalsa208sha256_memlimit_min(); // size_t crypto_pwhash_scryptsalsa208sha256_memlimit_max(void); @ssize_t long crypto_pwhash_scryptsalsa208sha256_memlimit_max(); // size_t crypto_pwhash_scryptsalsa208sha256_opslimit_interactive(void); @ssize_t long crypto_pwhash_scryptsalsa208sha256_opslimit_interactive(); // size_t crypto_pwhash_scryptsalsa208sha256_memlimit_interactive(void); @ssize_t long crypto_pwhash_scryptsalsa208sha256_memlimit_interactive(); // size_t crypto_pwhash_scryptsalsa208sha256_opslimit_sensitive(void); @ssize_t long crypto_pwhash_scryptsalsa208sha256_opslimit_sensitive(); // size_t crypto_pwhash_scryptsalsa208sha256_memlimit_sensitive(void); @ssize_t long crypto_pwhash_scryptsalsa208sha256_memlimit_sensitive(); // int crypto_pwhash_scryptsalsa208sha256(unsigned char *const out, unsigned long long outlen, const char *const passwd, unsigned long long passwdlen, const unsigned char *const salt, unsigned long long opslimit, size_t memlimit); int crypto_pwhash_scryptsalsa208sha256( @Out byte[] out, @In @u_int64_t long outlen, @In byte[] passwd, @In @u_int64_t long passwdlen, @In byte[] salt, @In @u_int64_t long opslimit, @In @ssize_t long memlimit); // int crypto_pwhash_scryptsalsa208sha256_str(char[] out, const char *const passwd, unsigned long long passwdlen, unsigned long long opslimit, size_t memlimit); int crypto_pwhash_scryptsalsa208sha256_str( @Out byte[] out, @In byte[] passwd, @In @u_int64_t long passwdlen, @In @u_int64_t long opslimit, @In @ssize_t long memlimit); // int crypto_pwhash_scryptsalsa208sha256_str_verify(const char[] str, const char *const passwd, unsigned long long passwdlen); int crypto_pwhash_scryptsalsa208sha256_str_verify(@In byte[] str, @In byte[] passwd, @In @u_int64_t long passwdlen); // int crypto_pwhash_scryptsalsa208sha256_ll(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, size_t saltlen, uint64_t N, uint32_t r, uint32_t p, uint8_t * buf, size_t buflen); int crypto_pwhash_scryptsalsa208sha256_ll( @In byte[] passwd, @In @ssize_t long passwdlen, @In byte[] salt, @In @ssize_t long saltlen, @In @u_int64_t long N, @In @u_int32_t int r, @In @u_int32_t int p, @Out byte[] buf, @In @ssize_t long buflen); // int crypto_pwhash_scryptsalsa208sha256_str_needs_rehash(const char[] str, unsigned long long opslimit, size_t memlimit); int crypto_pwhash_scryptsalsa208sha256_str_needs_rehash( @In byte[] str, @In @u_int64_t long opslimit, @In @ssize_t long memlimit); // size_t crypto_stream_salsa2012_keybytes(void); @ssize_t long crypto_stream_salsa2012_keybytes(); // size_t crypto_stream_salsa2012_noncebytes(void); @ssize_t long crypto_stream_salsa2012_noncebytes(); // size_t crypto_stream_salsa2012_messagebytes_max(void); @ssize_t long crypto_stream_salsa2012_messagebytes_max(); // int crypto_stream_salsa2012(unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_stream_salsa2012(@Out byte[] c, @In @u_int64_t long clen, @In byte[] n, @In byte[] k); // int crypto_stream_salsa2012_xor(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_stream_salsa2012_xor(@Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In byte[] k); // void crypto_stream_salsa2012_keygen(unsigned char[] k); void crypto_stream_salsa2012_keygen(@Out byte[] k); // size_t crypto_stream_salsa208_keybytes(void); @ssize_t long crypto_stream_salsa208_keybytes(); // size_t crypto_stream_salsa208_noncebytes(void); @ssize_t long crypto_stream_salsa208_noncebytes(); // size_t crypto_stream_salsa208_messagebytes_max(void); @ssize_t long crypto_stream_salsa208_messagebytes_max(); // int crypto_stream_salsa208(unsigned char * c, unsigned long long clen, const unsigned char * n, const unsigned char * k); int crypto_stream_salsa208(@Out byte[] c, @In @u_int64_t long clen, @In byte[] n, @In byte[] k); // int crypto_stream_salsa208_xor(unsigned char * c, const unsigned char * m, unsigned long long mlen, const unsigned char * n, const unsigned char * k); int crypto_stream_salsa208_xor(@Out byte[] c, @In byte[] m, @In @u_int64_t long mlen, @In byte[] n, @In byte[] k); // void crypto_stream_salsa208_keygen(unsigned char[] k); void crypto_stream_salsa208_keygen(@Out byte[] k); } cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/PasswordHash.java000066400000000000000000001022671341750772100302370ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import static java.nio.charset.StandardCharsets.UTF_8; import net.consensys.cava.bytes.Bytes; import javax.annotation.Nullable; import jnr.ffi.Pointer; // Documentation copied under the ISC License, from // https://github.com/jedisct1/libsodium-doc/blob/424b7480562c2e063bc8c52c452ef891621c8480/password_hashing/the_argon2i_function.md /** * The Argon2 memory-hard hashing function. * *

* Argon2 summarizes the state of the art in the design of memory-hard functions. * *

* It aims at the highest memory filling rate and effective use of multiple computing units, while still providing * defense against tradeoff attacks. * *

* It prevents ASICs from having a significant advantage over software implementations. * *

Guidelines for choosing the parameters

* *

* Start by determining how much memory the function can use. What will be the highest number of threads/processes * evaluating the function simultaneously (ideally, no more than 1 per CPU core)? How much physical memory is guaranteed * to be available? * *

* Set memlimit to the amount of memory you want to reserve for password hashing. * *

* Then, set opslimit to 3 and measure the time it takes to hash a password. * *

* If this it is way too long for your application, reduce memlimit, but keep opslimit set to 3. * *

* If the function is so fast that you can afford it to be more computationally intensive without any usability issues, * increase opslimit. * *

* For online use (e.g. login in on a website), a 1 second computation is likely to be the acceptable maximum. * *

* For interactive use (e.g. a desktop application), a 5 second pause after having entered a password is acceptable if * the password doesn't need to be entered more than once per session. * *

* For non-interactive use and infrequent use (e.g. restoring an encrypted backup), an even slower computation can be an * option. * *

* This class depends 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'. */ public final class PasswordHash { /** * A PasswordHash salt. */ public static final class Salt { private final Pointer ptr; private final int length; private Salt(Pointer ptr, int length) { this.ptr = ptr; this.length = length; } @Override protected void finalize() { Sodium.sodium_free(ptr); } /** * Create a {@link Salt} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the seed. * @return A seed. */ public static Salt fromBytes(Bytes bytes) { return fromBytes(bytes.toArrayUnsafe()); } /** * Create a {@link Salt} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the seed. * @return A seed. */ public static Salt fromBytes(byte[] bytes) { if (bytes.length != Sodium.crypto_pwhash_saltbytes()) { throw new IllegalArgumentException( "key must be " + Sodium.crypto_pwhash_saltbytes() + " bytes, got " + bytes.length); } return Sodium.dup(bytes, Salt::new); } /** * Obtain the length of the salt in bytes (32). * * @return The length of the salt in bytes (32). */ public static int length() { long saltLength = Sodium.crypto_pwhash_saltbytes(); if (saltLength > Integer.MAX_VALUE) { throw new SodiumException("crypto_pwhash_saltbytes: " + saltLength + " is too large"); } return (int) saltLength; } /** * Generate a new salt using a random generator. * * @return A randomly generated salt. */ public static Salt random() { return Sodium.randomBytes(length(), Salt::new); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof Salt)) { return false; } Salt other = (Salt) obj; return Sodium.sodium_memcmp(this.ptr, other.ptr, length) == 0; } @Override public int hashCode() { return Sodium.hashCode(ptr, length); } /** * @return The bytes of this salt. */ public Bytes bytes() { return Bytes.wrap(bytesArray()); } /** * @return The bytes of this salt. */ public byte[] bytesArray() { return Sodium.reify(ptr, length); } } /** * A PasswordHash algorithm. */ public static final class Algorithm { private static Algorithm ARGON2I13 = new Algorithm("argon2i13", 1, 3, true); private static Algorithm ARGON2ID13 = new Algorithm("argon2id13", 2, 1, Sodium.supportsVersion(Sodium.VERSION_10_0_13)); private final String name; private final int id; private final long minOps; private final boolean supported; private Algorithm(String name, int id, long minOps, boolean supported) { this.name = name; this.id = id; this.minOps = minOps; this.supported = supported; } /** * @return The currently recommended algorithm. */ public static Algorithm recommended() { return ARGON2ID13.isSupported() ? ARGON2ID13 : ARGON2I13; } /** * @return Version 1.3 of the Argon2i algorithm. */ public static Algorithm argon2i13() { return ARGON2I13; } /** * @return Version 1.3 of the Argon2id algorithm. */ public static Algorithm argon2id13() { return ARGON2ID13; } @Nullable static Algorithm fromId(int id) { if (ARGON2ID13.id == id) { return ARGON2ID13; } else if (ARGON2I13.id == id) { return ARGON2I13; } return null; } public String name() { return name; } int id() { return id; } public boolean isSupported() { return supported; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof Algorithm)) { return false; } Algorithm other = (Algorithm) obj; return this.id == other.id; } @Override public int hashCode() { return Integer.hashCode(id); } @Override public String toString() { return name; } } /** * Compute a key from a password, using the currently recommended algorithm and limits on operations and memory that * are suitable for most use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @return The derived key. */ public static Bytes hash(String password, int length, Salt salt) { return Bytes.wrap(hash(password.getBytes(UTF_8), length, salt)); } /** * Compute a key from a password, using the currently recommended algorithm and limits on operations and memory that * are suitable for most use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @return The derived key. */ public static Bytes hash(Bytes password, int length, Salt salt) { return Bytes.wrap(hash(password.toArrayUnsafe(), length, salt)); } /** * Compute a key from a password, using the currently recommended algorithm and limits on operations and memory that * are suitable for most use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @return The derived key. */ public static byte[] hash(byte[] password, int length, Salt salt) { return hash(password, length, salt, moderateOpsLimit(), moderateMemLimit(), Algorithm.recommended()); } /** * Compute a key from a password, using limits on operations and memory that are suitable for most use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @param algorithm The algorithm to use. * @return The derived key. */ public static Bytes hash(String password, int length, Salt salt, Algorithm algorithm) { return Bytes.wrap(hash(password.getBytes(UTF_8), length, salt, algorithm)); } /** * Compute a key from a password, using limits on operations and memory that are suitable for most use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @param algorithm The algorithm to use. * @return The derived key. */ public static Bytes hash(Bytes password, int length, Salt salt, Algorithm algorithm) { return Bytes.wrap(hash(password.toArrayUnsafe(), length, salt, algorithm)); } /** * Compute a key from a password, using limits on operations and memory that are suitable for most use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @param algorithm The algorithm to use. * @return The derived key. */ public static byte[] hash(byte[] password, int length, Salt salt, Algorithm algorithm) { return hash(password, length, salt, moderateOpsLimit(), moderateMemLimit(), algorithm); } /** * Compute a key from a password, using the currently recommended algorithm and limits on operations and memory that * are suitable for interactive use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @return The derived key. */ public static Bytes hashInteractive(String password, int length, Salt salt) { return Bytes.wrap(hash(password.getBytes(UTF_8), length, salt, Algorithm.recommended())); } /** * Compute a key from a password, using the currently recommended algorithm and limits on operations and memory that * are suitable for interactive use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @return The derived key. */ public static Bytes hashInteractive(Bytes password, int length, Salt salt) { return Bytes.wrap(hash(password.toArrayUnsafe(), length, salt, Algorithm.recommended())); } /** * Compute a key from a password, using the currently recommended algorithm and limits on operations and memory that * are suitable for interactive use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @return The derived key. */ public static byte[] hashInteractive(byte[] password, int length, Salt salt) { return hash(password, length, salt, interactiveOpsLimit(), interactiveMemLimit(), Algorithm.recommended()); } /** * Compute a key from a password, using limits on operations and memory that are suitable for interactive use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @param algorithm The algorithm to use. * @return The derived key. */ public static Bytes hashInteractive(String password, int length, Salt salt, Algorithm algorithm) { return Bytes.wrap(hash(password.getBytes(UTF_8), length, salt, algorithm)); } /** * Compute a key from a password, using limits on operations and memory that are suitable for interactive use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @param algorithm The algorithm to use. * @return The derived key. */ public static Bytes hashInteractive(Bytes password, int length, Salt salt, Algorithm algorithm) { return Bytes.wrap(hash(password.toArrayUnsafe(), length, salt, algorithm)); } /** * Compute a key from a password, using limits on operations and memory that are suitable for interactive use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @param algorithm The algorithm to use. * @return The derived key. */ public static byte[] hashInteractive(byte[] password, int length, Salt salt, Algorithm algorithm) { return hash(password, length, salt, interactiveOpsLimit(), interactiveMemLimit(), algorithm); } /** * Compute a key from a password, using the currently recommended algorithm and limits on operations and memory that * are suitable for sensitive use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @return The derived key. */ public static Bytes hashSensitive(String password, int length, Salt salt) { return Bytes.wrap(hash(password.getBytes(UTF_8), length, salt, Algorithm.recommended())); } /** * Compute a key from a password, using the currently recommended algorithm and limits on operations and memory that * are suitable for sensitive use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @return The derived key. */ public static Bytes hashSensitive(Bytes password, int length, Salt salt) { return Bytes.wrap(hash(password.toArrayUnsafe(), length, salt, Algorithm.recommended())); } /** * Compute a key from a password, using the currently recommended algorithm and limits on operations and memory that * are suitable for sensitive use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @return The derived key. */ public static byte[] hashSensitive(byte[] password, int length, Salt salt) { return hash(password, length, salt, sensitiveOpsLimit(), sensitiveMemLimit(), Algorithm.recommended()); } /** * Compute a key from a password, using limits on operations and memory that are suitable for sensitive use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @param algorithm The algorithm to use. * @return The derived key. */ public static Bytes hashSensitive(String password, int length, Salt salt, Algorithm algorithm) { return Bytes.wrap(hash(password.getBytes(UTF_8), length, salt, algorithm)); } /** * Compute a key from a password, using limits on operations and memory that are suitable for sensitive use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @param algorithm The algorithm to use. * @return The derived key. */ public static Bytes hashSensitive(Bytes password, int length, Salt salt, Algorithm algorithm) { return Bytes.wrap(hash(password.toArrayUnsafe(), length, salt, algorithm)); } /** * Compute a key from a password, using limits on operations and memory that are suitable for sensitive use-cases. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @param algorithm The algorithm to use. * @return The derived key. */ public static byte[] hashSensitive(byte[] password, int length, Salt salt, Algorithm algorithm) { return hash(password, length, salt, sensitiveOpsLimit(), sensitiveMemLimit(), algorithm); } /** * Compute a key from a password. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @param opsLimit The operations limit, which must be in the range {@link #minOpsLimit()} to {@link #maxOpsLimit()}. * @param memLimit The memory limit, which must be in the range {@link #minMemLimit()} to {@link #maxMemLimit()}. * @param algorithm The algorithm to use. * @return The derived key. */ public static Bytes hash(String password, int length, Salt salt, long opsLimit, long memLimit, Algorithm algorithm) { return Bytes.wrap(hash(password.getBytes(UTF_8), length, salt, opsLimit, memLimit, algorithm)); } /** * Compute a key from a password. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @param opsLimit The operations limit, which must be in the range {@link #minOpsLimit()} to {@link #maxOpsLimit()}. * @param memLimit The memory limit, which must be in the range {@link #minMemLimit()} to {@link #maxMemLimit()}. * @param algorithm The algorithm to use. * @return The derived key. */ public static Bytes hash(Bytes password, int length, Salt salt, long opsLimit, long memLimit, Algorithm algorithm) { return Bytes.wrap(hash(password.toArrayUnsafe(), length, salt, opsLimit, memLimit, algorithm)); } /** * Compute a key from a password. * * @param password The password to hash. * @param length The key length to generate. * @param salt A salt. * @param opsLimit The operations limit, which must be in the range {@link #minOpsLimit()} to {@link #maxOpsLimit()}. * @param memLimit The memory limit, which must be in the range {@link #minMemLimit()} to {@link #maxMemLimit()}. * @param algorithm The algorithm to use. * @return The derived key. * @throws IllegalArgumentException If the opsLimit is too low for the specified algorithm. * @throws UnsupportedOperationException If the specified algorithm is not supported by the currently loaded sodium * native library. */ public static byte[] hash(byte[] password, int length, Salt salt, long opsLimit, long memLimit, Algorithm algorithm) { assertHashLength(length); assertOpsLimit(opsLimit); assertMemLimit(memLimit); if (opsLimit < algorithm.minOps) { throw new IllegalArgumentException("opsLimit " + opsLimit + " too low for specified algorithm"); } if (!algorithm.isSupported()) { throw new UnsupportedOperationException( algorithm.name() + " is not supported by the currently loaded sodium native library"); } byte[] out = new byte[length]; int rc = Sodium.crypto_pwhash(out, length, password, password.length, salt.ptr, opsLimit, memLimit, algorithm.id); if (rc != 0) { throw new SodiumException("crypto_pwhash: failed with result " + rc); } return out; } /** * @return The minimum hash length (16). */ public static int minHashLength() { // When support for 10.0.11 is dropped, remove this if (!Sodium.supportsVersion(Sodium.VERSION_10_0_12)) { return 16; } long len = Sodium.crypto_pwhash_bytes_min(); if (len > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_pwhash_bytes_min: " + len + " is too large"); } return (int) len; } /** * @return The maximum hash length. */ public static int maxHashLength() { // When support for 10.0.11 is dropped, remove this if (!Sodium.supportsVersion(Sodium.VERSION_10_0_12)) { return Integer.MAX_VALUE; } long len = Sodium.crypto_pwhash_bytes_max(); if (len > Integer.MAX_VALUE) { return Integer.MAX_VALUE; } return (int) len; } private static void assertHashLength(int length) { // When support for 10.0.11 is dropped, remove this if (!Sodium.supportsVersion(Sodium.VERSION_10_0_12)) { if (length < 16) { throw new IllegalArgumentException("length out of range"); } return; } if (length < Sodium.crypto_pwhash_bytes_min() || length > Sodium.crypto_pwhash_bytes_max()) { throw new IllegalArgumentException("length out of range"); } } /** * Compute a hash from a password, using limits on operations and memory that are suitable for most use-cases. * *

* Equivalent to {@code hash(password, moderateOpsLimit(), moderateMemLimit())}. * * @param password The password to hash. * @return The hash string. */ public static String hash(String password) { return hash(password, moderateOpsLimit(), moderateMemLimit()); } /** * Compute a hash from a password, using limits on operations and memory that are suitable for interactive use-cases. * *

* Equivalent to {@code hash(password, sensitiveOpsLimit(), sensitiveMemLimit())}. * * @param password The password to hash. * @return The hash string. */ public static String hashInteractive(String password) { return hash(password, interactiveOpsLimit(), interactiveMemLimit()); } /** * Compute a hash from a password, using limits on operations and memory that are suitable for sensitive use-cases. * *

* Equivalent to {@code hash(password, sensitiveOpsLimit(), sensitiveMemLimit())}. * * @param password The password to hash. * @return The hash string. */ public static String hashSensitive(String password) { return hash(password, sensitiveOpsLimit(), sensitiveMemLimit()); } /** * Compute a hash from a password. * * @param password The password to hash. * @param opsLimit The operations limit, which must be in the range {@link #minOpsLimit()} to {@link #maxOpsLimit()}. * @param memLimit The memory limit, which must be in the range {@link #minMemLimit()} to {@link #maxMemLimit()}. * @return The hash string. */ public static String hash(String password, long opsLimit, long memLimit) { assertOpsLimit(opsLimit); assertMemLimit(memLimit); byte[] out = new byte[hashStringLength()]; byte[] pwBytes = password.getBytes(UTF_8); int rc = Sodium.crypto_pwhash_str(out, pwBytes, pwBytes.length, opsLimit, memLimit); if (rc != 0) { throw new SodiumException("crypto_pwhash_str: failed with result " + rc); } int i = 0; while (i < out.length && out[i] != 0) { ++i; } return new String(out, 0, i, UTF_8); } /** * Verify a password against a hash. * * @param hash The hash. * @param password The password to verify. * @return {@code true} if the password matches the hash. */ public static boolean verify(String hash, String password) { byte[] hashBytes = hash.getBytes(UTF_8); int hashLength = hashStringLength(); if (hashBytes.length >= hashLength) { return false; } Pointer str = Sodium.malloc(hashLength); try { str.put(0, hashBytes, 0, hashBytes.length); str.putByte(hashBytes.length, (byte) 0); byte[] pwBytes = password.getBytes(UTF_8); return Sodium.crypto_pwhash_str_verify(str, pwBytes, pwBytes.length) == 0; } finally { Sodium.sodium_free(str); } } private static void assertCheckRehashAvailable() { if (!Sodium.supportsVersion(Sodium.VERSION_10_0_14)) { throw new UnsupportedOperationException( "Sodium re-hash checking is not available (requires sodium native library version >= 10.0.14)"); } } /** * A hash verification result. * *

* Note: methods returning this result are only supported when the sodium native library version >= 10.0.14 is * available. */ public enum VerificationResult { /** The hash verification failed. */ FAILED, /** The hash verification passed. */ PASSED, /** * The hash verification passed, but the hash is out-of-date and should be regenerated. */ NEEDS_REHASH; /** * @return {@code true} if the verification passed. */ public boolean passed() { return this != FAILED; } /** * @return {@code true} if the hash should be regenerated. */ public boolean needsRehash() { return this == NEEDS_REHASH; } } /** * Verify a password against a hash and check the hash is suitable for normal use-cases. * *

* Equivalent to {@code verify(hash, password, moderateOpsLimit(), moderateMemLimit())}. * *

* Note: only supported when the sodium native library version >= 10.0.14 is available. * * @param hash The hash. * @param password The password to verify. * @return The result of verification. */ public static VerificationResult checkHash(String hash, String password) { return checkHash(hash, password, moderateOpsLimit(), moderateMemLimit()); } /** * Verify a password against a hash and check the hash is suitable for interactive use-cases. * *

* Equivalent to {@code verify(hash, password, interactiveOpsLimit(), interactiveMemLimit())}. * *

* Note: only supported when the sodium native library version >= 10.0.14 is available. * * @param hash The hash. * @param password The password to verify. * @return The result of verification. */ public static VerificationResult checkHashForInteractive(String hash, String password) { return checkHash(hash, password, interactiveOpsLimit(), interactiveMemLimit()); } /** * Verify a password against a hash and check the hash is suitable for sensitive use-cases. * *

* Equivalent to {@code verify(hash, password, sensitiveOpsLimit(), sensitiveMemLimit())}. * *

* Note: only supported when the sodium native library version >= 10.0.14 is available. * * @param hash The hash. * @param password The password to verify. * @return The result of verification. */ public static VerificationResult checkHashForSensitive(String hash, String password) { return checkHash(hash, password, sensitiveOpsLimit(), sensitiveMemLimit()); } /** * Verify a password against a hash. * *

* Note: only supported when the sodium native library version >= 10.0.14 is available. * * @param hash The hash. * @param password The password to verify. * @param opsLimit The operations limit, which must be in the range {@link #minOpsLimit()} to {@link #maxOpsLimit()}. * @param memLimit The memory limit, which must be in the range {@link #minMemLimit()} to {@link #maxMemLimit()}. * @return The result of verification. */ public static VerificationResult checkHash(String hash, String password, long opsLimit, long memLimit) { assertCheckRehashAvailable(); assertOpsLimit(opsLimit); assertMemLimit(memLimit); byte[] hashBytes = hash.getBytes(UTF_8); int hashLength = hashStringLength(); if (hashBytes.length >= hashLength) { return VerificationResult.FAILED; } Pointer str = Sodium.malloc(hashLength); try { str.put(0, hashBytes, 0, hashBytes.length); str.putByte(hashBytes.length, (byte) 0); byte[] pwBytes = password.getBytes(UTF_8); if (Sodium.crypto_pwhash_str_verify(str, pwBytes, pwBytes.length) != 0) { return VerificationResult.FAILED; } int rc = Sodium.crypto_pwhash_str_needs_rehash(str, opsLimit, memLimit); if (rc < 0) { throw new SodiumException("crypto_pwhash_str_needs_rehash: failed with result " + rc); } return (rc == 0) ? VerificationResult.PASSED : VerificationResult.NEEDS_REHASH; } finally { Sodium.sodium_free(str); } } /** * Check if a hash needs to be regenerated using limits on operations and memory that are suitable for most use-cases. * *

* Equivalent to {@code needsRehash(hash, moderateOpsLimit(), moderateMemLimit())}. * *

* Note: only supported when the sodium native library version >= 10.0.14 is available. * * @param hash The hash. * @return {@code true} if the hash should be regenerated. */ public static boolean needsRehash(String hash) { return needsRehash(hash, moderateOpsLimit(), moderateMemLimit()); } /** * Check if a hash needs to be regenerated using limits on operations and memory that are suitable for interactive * use-cases. * *

* Equivalent to {@code needsRehash(hash, interactiveOpsLimit(), interactiveMemLimit())}. * *

* Note: only supported when the sodium native library version >= 10.0.14 is available. * * @param hash The hash. * @return {@code true} if the hash should be regenerated. */ public static boolean needsRehashForInteractive(String hash) { return needsRehash(hash, interactiveOpsLimit(), interactiveMemLimit()); } /** * Check if a hash needs to be regenerated using limits on operations and memory that are suitable for sensitive * use-cases. * *

* Equivalent to {@code needsRehash(hash, sensitiveOpsLimit(), sensitiveMemLimit())}. * *

* Note: only supported when the sodium native library version >= 10.0.14 is available. * * @param hash The hash. * @return {@code true} if the hash should be regenerated. */ public static boolean needsRehashForSensitive(String hash) { return needsRehash(hash, sensitiveOpsLimit(), sensitiveMemLimit()); } /** * Check if a hash needs to be regenerated. * *

* Check if a hash matches the parameters opslimit and memlimit, and the current default algorithm. * *

* Note: only supported when the sodium native library version >= 10.0.14 is available. * * @param hash The hash. * @param opsLimit The operations limit, which must be in the range {@link #minOpsLimit()} to {@link #maxOpsLimit()}. * @param memLimit The memory limit, which must be in the range {@link #minMemLimit()} to {@link #maxMemLimit()}. * @return {@code true} if the hash should be regenerated. */ public static boolean needsRehash(String hash, long opsLimit, long memLimit) { assertCheckRehashAvailable(); assertOpsLimit(opsLimit); assertMemLimit(memLimit); byte[] hashBytes = hash.getBytes(UTF_8); int hashLength = hashStringLength(); if (hashBytes.length >= hashLength) { throw new IllegalArgumentException("hash is too long"); } Pointer str = Sodium.malloc(hashLength); try { str.put(0, hashBytes, 0, hashBytes.length); str.putByte(hashBytes.length, (byte) 0); int rc = Sodium.crypto_pwhash_str_needs_rehash(str, opsLimit, memLimit); if (rc < 0) { throw new SodiumException("crypto_pwhash_str_needs_rehash: failed with result " + rc); } return (rc != 0); } finally { Sodium.sodium_free(str); } } private static int hashStringLength() { long hashLength = Sodium.crypto_pwhash_strbytes(); if (hashLength > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_pwhash_strbytes: " + hashLength + " is too large"); } return (int) hashLength; } /** * @return The minimum operations limit (1). */ public static long minOpsLimit() { // When support for 10.0.11 is dropped, remove this if (!Sodium.supportsVersion(Sodium.VERSION_10_0_12)) { return 3; } return Sodium.crypto_pwhash_opslimit_min(); } /** * @return An operations limit suitable for interactive use-cases (2). */ public static long interactiveOpsLimit() { return Sodium.crypto_pwhash_opslimit_interactive(); } /** * @return An operations limit suitable for most use-cases (3). */ public static long moderateOpsLimit() { return Sodium.crypto_pwhash_opslimit_moderate(); } /** * @return An operations limit for sensitive use-cases (4). */ public static long sensitiveOpsLimit() { return Sodium.crypto_pwhash_opslimit_sensitive(); } /** * @return The maximum operations limit (4294967295). */ public static long maxOpsLimit() { // When support for 10.0.11 is dropped, remove this if (!Sodium.supportsVersion(Sodium.VERSION_10_0_12)) { return 4294967295L; } return Sodium.crypto_pwhash_opslimit_max(); } private static void assertOpsLimit(long opsLimit) { if (opsLimit < minOpsLimit() || opsLimit > maxOpsLimit()) { throw new IllegalArgumentException("opsLimit out of range"); } } /** * @return The minimum memory limit (8192). */ public static long minMemLimit() { // When support for 10.0.11 is dropped, remove this if (!Sodium.supportsVersion(Sodium.VERSION_10_0_12)) { return 8192; } return Sodium.crypto_pwhash_memlimit_min(); } /** * @return A memory limit suitable for interactive use-cases (67108864). */ public static long interactiveMemLimit() { return Sodium.crypto_pwhash_memlimit_interactive(); } /** * @return A memory limit suitable for most use-cases (268435456). */ public static long moderateMemLimit() { return Sodium.crypto_pwhash_memlimit_moderate(); } /** * @return A memory limit suitable for sensitive use-cases (1073741824). */ public static long sensitiveMemLimit() { return Sodium.crypto_pwhash_memlimit_sensitive(); } /** * @return The maximum memory limit (4398046510080). */ public static long maxMemLimit() { // When support for 10.0.11 is dropped, remove this if (!Sodium.supportsVersion(Sodium.VERSION_10_0_12)) { return 4398046510080L; } return Sodium.crypto_pwhash_memlimit_max(); } private static void assertMemLimit(long memLimit) { if (memLimit < minMemLimit() || memLimit > maxMemLimit()) { throw new IllegalArgumentException("memLimit out of range"); } } } cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/SecretBox.java000066400000000000000000001712551341750772100275320ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; import net.consensys.cava.bytes.Bytes; import java.util.Arrays; import javax.annotation.Nullable; import javax.security.auth.Destroyable; import jnr.ffi.Pointer; // Documentation copied under the ISC License, from // https://github.com/jedisct1/libsodium-doc/blob/424b7480562c2e063bc8c52c452ef891621c8480/secret-key_cryptography/authenticated_encryption.md /** * Secret-key authenticated encryption. * *

* Encrypts a message with a key and a nonce to keep it confidential, and computes an authentication tag. The tag is * used to make sure that the message hasn't been tampered with before decrypting it. * *

* A single key is used both to encrypt/sign and verify/decrypt messages. For this reason, it is critical to keep the * key confidential. * *

* The nonce doesn't have to be confidential, but it should never ever be reused with the same key. The easiest way to * generate a nonce is to use randombytes_buf(). * *

* Messages encrypted are assumed to be independent. If multiple messages are sent using this API and random nonces, * there will be no way to detect if a message has been received twice, or if messages have been reordered. * *

* This class depends 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'. */ public final class SecretBox { private SecretBox() {} /** * A SecretBox key. */ public static final class Key implements Destroyable { @Nullable private Pointer ptr; private final int length; private Key(Pointer ptr, int length) { this.ptr = ptr; this.length = length; } @Override protected void finalize() { destroy(); } @Override public void destroy() { if (ptr != null) { Pointer p = ptr; ptr = null; Sodium.sodium_free(p); } } @Override public boolean isDestroyed() { return ptr == null; } /** * Create a {@link Key} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the key. * @return A key, based on the supplied bytes. */ public static Key fromBytes(Bytes bytes) { return fromBytes(bytes.toArrayUnsafe()); } /** * Create a {@link Key} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the key. * @return A key, based on the supplied bytes. */ public static Key fromBytes(byte[] bytes) { if (bytes.length != Sodium.crypto_secretbox_keybytes()) { throw new IllegalArgumentException( "key must be " + Sodium.crypto_secretbox_keybytes() + " bytes, got " + bytes.length); } return Sodium.dup(bytes, Key::new); } /** * Obtain the length of the key in bytes (32). * * @return The length of the key in bytes (32). */ public static int length() { long keybytes = Sodium.crypto_secretbox_keybytes(); if (keybytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_secretbox_keybytes: " + keybytes + " is too large"); } return (int) keybytes; } /** * Generate a new key using a random generator. * * @return A randomly generated key. */ public static Key random() { int length = length(); Pointer ptr = Sodium.malloc(length); try { // When support for 10.0.11 is dropped, use this instead //Sodium.crypto_secretbox_keygen(ptr); Sodium.randombytes_buf(ptr, length); return new Key(ptr, length); } catch (Throwable e) { Sodium.sodium_free(ptr); throw e; } } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof Key)) { return false; } checkState(ptr != null, "Key has been destroyed"); Key other = (Key) obj; return other.ptr != null && Sodium.sodium_memcmp(this.ptr, other.ptr, length) == 0; } @Override public int hashCode() { checkState(ptr != null, "Key has been destroyed"); return Sodium.hashCode(ptr, length); } /** * @return The bytes of this key. */ public Bytes bytes() { return Bytes.wrap(bytesArray()); } /** * @return The bytes of this key. */ public byte[] bytesArray() { checkState(ptr != null, "Key has been destroyed"); return Sodium.reify(ptr, length); } } /** * A SecretBox nonce. */ public static final class Nonce { private final Pointer ptr; private final int length; private Nonce(Pointer ptr, int length) { this.ptr = ptr; this.length = length; } @Override protected void finalize() { Sodium.sodium_free(ptr); } /** * Create a {@link Nonce} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the nonce. * @return A nonce, based on these bytes. */ public static Nonce fromBytes(Bytes bytes) { return fromBytes(bytes.toArrayUnsafe()); } /** * Create a {@link Nonce} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the nonce. * @return A nonce, based on these bytes. */ public static Nonce fromBytes(byte[] bytes) { if (bytes.length != Sodium.crypto_secretbox_noncebytes()) { throw new IllegalArgumentException( "nonce must be " + Sodium.crypto_secretbox_noncebytes() + " bytes, got " + bytes.length); } return Sodium.dup(bytes, Nonce::new); } /** * Obtain the length of the nonce in bytes (24). * * @return The length of the nonce in bytes (24). */ public static int length() { long noncebytes = Sodium.crypto_secretbox_noncebytes(); if (noncebytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_secretbox_noncebytes: " + noncebytes + " is too large"); } return (int) noncebytes; } /** * Generate a new {@link Nonce} using a random generator. * * @return A randomly generated nonce. */ public static Nonce random() { return Sodium.randomBytes(length(), Nonce::new); } /** * Increment this nonce. * *

* Note that this is not synchronized. If multiple threads are creating encrypted messages and incrementing this * nonce, then external synchronization is required to ensure no two encrypt operations use the same nonce. * * @return A new {@link Nonce}. */ public Nonce increment() { return Sodium.dupAndIncrement(ptr, length(), Nonce::new); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof Nonce)) { return false; } Nonce other = (Nonce) obj; return Sodium.sodium_memcmp(this.ptr, other.ptr, length) == 0; } @Override public int hashCode() { return Sodium.hashCode(ptr, length); } /** * @return The bytes of this nonce. */ public Bytes bytes() { return Bytes.wrap(bytesArray()); } /** * @return The bytes of this nonce. */ public byte[] bytesArray() { return Sodium.reify(ptr, length); } } /** * Encrypt a message with a key. * * @param message The message to encrypt. * @param key The key to use for encryption. * @param nonce A unique nonce. * @return The encrypted data. */ public static Bytes encrypt(Bytes message, Key key, Nonce nonce) { return Bytes.wrap(encrypt(message.toArrayUnsafe(), key, nonce)); } /** * Encrypt a message with a key. * * @param message The message to encrypt. * @param key The key to use for encryption. * @param nonce A unique nonce. * @return The encrypted data. */ public static byte[] encrypt(byte[] message, Key key, Nonce nonce) { checkArgument(key.ptr != null, "Key has been destroyed"); int macbytes = macLength(); byte[] cipherText = new byte[macbytes + message.length]; int rc = Sodium.crypto_secretbox_easy(cipherText, message, message.length, nonce.ptr, key.ptr); if (rc != 0) { throw new SodiumException("crypto_secretbox_easy: failed with result " + rc); } return cipherText; } /** * Encrypt a message with a key, generating a detached message authentication code. * * @param message The message to encrypt. * @param key The key to use for encryption. * @param nonce A unique nonce. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptDetached(Bytes message, Key key, Nonce nonce) { return encryptDetached(message.toArrayUnsafe(), key, nonce); } /** * Encrypt a message with a key, generating a detached message authentication code. * * @param message The message to encrypt. * @param key The key to use for encryption. * @param nonce A unique nonce. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptDetached(byte[] message, Key key, Nonce nonce) { checkArgument(key.ptr != null, "Key has been destroyed"); int macbytes = macLength(); byte[] cipherText = new byte[message.length]; byte[] mac = new byte[macbytes]; int rc = Sodium.crypto_secretbox_detached(cipherText, mac, message, message.length, nonce.ptr, key.ptr); if (rc != 0) { throw new SodiumException("crypto_secretbox_detached: failed with result " + rc); } return new DefaultDetachedEncryptionResult(cipherText, mac); } /** * Decrypt a message using a key. * * @param cipherText The cipher text to decrypt. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decrypt(Bytes cipherText, Key key, Nonce nonce) { byte[] bytes = decrypt(cipherText.toArrayUnsafe(), key, nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a key. * * @param cipherText The cipher text to decrypt. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decrypt(byte[] cipherText, Key key, Nonce nonce) { checkArgument(key.ptr != null, "Key has been destroyed"); int macLength = macLength(); if (macLength > cipherText.length) { throw new IllegalArgumentException("cipherText is too short"); } byte[] clearText = new byte[cipherText.length - macLength]; int rc = Sodium.crypto_secretbox_open_easy(clearText, cipherText, cipherText.length, nonce.ptr, key.ptr); if (rc == -1) { return null; } if (rc != 0) { throw new SodiumException("crypto_secretbox_open_easy: failed with result " + rc); } return clearText; } /** * Decrypt a message using a key and a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decryptDetached(Bytes cipherText, Bytes mac, Key key, Nonce nonce) { byte[] bytes = decryptDetached(cipherText.toArrayUnsafe(), mac.toArrayUnsafe(), key, nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a key and a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decryptDetached(byte[] cipherText, byte[] mac, Key key, Nonce nonce) { checkArgument(key.ptr != null, "Key has been destroyed"); int macLength = macLength(); if (macLength != mac.length) { throw new IllegalArgumentException("mac must be " + macLength + " bytes, got " + mac.length); } byte[] clearText = new byte[cipherText.length]; int rc = Sodium.crypto_secretbox_open_detached(clearText, cipherText, mac, cipherText.length, nonce.ptr, key.ptr); if (rc == -1) { return null; } if (rc != 0) { throw new SodiumException("crypto_secretbox_open_detached: failed with result " + rc); } return clearText; } /** * Encrypt a message with a password, using {@link PasswordHash} for the key generation (with the currently * recommended algorithm and limits on operations and memory that are suitable for most use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @return The encrypted data. */ public static Bytes encrypt(Bytes message, String password) { return encrypt( message, password, PasswordHash.moderateOpsLimit(), PasswordHash.moderateMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Encrypt a message with a password, using {@link PasswordHash} for the key generation (with the currently * recommended algorithm and limits on operations and memory that are suitable for most use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @return The encrypted data. */ public static byte[] encrypt(byte[] message, String password) { return encrypt( message, password, PasswordHash.moderateOpsLimit(), PasswordHash.moderateMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Encrypt a message with a password, using {@link PasswordHash} for the key generation (with limits on operations and * memory that are suitable for most use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @param algorithm The algorithm to use. * @return The encrypted data. */ public static Bytes encrypt(Bytes message, String password, PasswordHash.Algorithm algorithm) { return encrypt(message, password, PasswordHash.moderateOpsLimit(), PasswordHash.moderateMemLimit(), algorithm); } /** * Encrypt a message with a password, using {@link PasswordHash} for the key generation (with limits on operations and * memory that are suitable for most use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @param algorithm The algorithm to use. * @return The encrypted data. */ public static byte[] encrypt(byte[] message, String password, PasswordHash.Algorithm algorithm) { return encrypt(message, password, PasswordHash.moderateOpsLimit(), PasswordHash.moderateMemLimit(), algorithm); } /** * Encrypt a message with a password, using {@link PasswordHash} for the key generation (with the currently * recommended algorithm and limits on operations and memory that are suitable for interactive use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @return The encrypted data. */ public static Bytes encryptInteractive(Bytes message, String password) { return encrypt( message, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Encrypt a message with a password, using {@link PasswordHash} for the key generation (with the currently * recommended algorithm and limits on operations and memory that are suitable for interactive use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @return The encrypted data. */ public static byte[] encryptInteractive(byte[] message, String password) { return encrypt( message, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Encrypt a message with a password, using {@link PasswordHash} for the key generation (with limits on operations and * memory that are suitable for interactive use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @param algorithm The algorithm to use. * @return The encrypted data. */ public static Bytes encryptInteractive(Bytes message, String password, PasswordHash.Algorithm algorithm) { return encrypt( message, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), algorithm); } /** * Encrypt a message with a password, using {@link PasswordHash} for the key generation (with limits on operations and * memory that are suitable for interactive use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @param algorithm The algorithm to use. * @return The encrypted data. */ public static byte[] encryptInteractive(byte[] message, String password, PasswordHash.Algorithm algorithm) { return encrypt( message, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), algorithm); } /** * Encrypt a message with a password, using {@link PasswordHash} for the key generation (with the currently * recommended algorithm and limits on operations and memory that are suitable for sensitive use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @return The encrypted data. */ public static Bytes encryptSensitive(Bytes message, String password) { return encrypt( message, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Encrypt a message with a password, using {@link PasswordHash} for the key generation (with the currently * recommended algorithm and limits on operations and memory that are suitable for sensitive use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @return The encrypted data. */ public static byte[] encryptSensitive(byte[] message, String password) { return encrypt( message, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Encrypt a message with a password, using {@link PasswordHash} for the key generation (with limits on operations and * memory that are suitable for sensitive use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @param algorithm The algorithm to use. * @return The encrypted data. */ public static Bytes encryptSensitive(Bytes message, String password, PasswordHash.Algorithm algorithm) { return encrypt(message, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), algorithm); } /** * Encrypt a message with a password, using {@link PasswordHash} for the key generation (with limits on operations and * memory that are suitable for sensitive use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @param algorithm The algorithm to use. * @return The encrypted data. */ public static byte[] encryptSensitive(byte[] message, String password, PasswordHash.Algorithm algorithm) { return encrypt(message, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), algorithm); } /** * Encrypt a message with a password, using {@link PasswordHash} for the key generation. * * @param message The message to encrypt. * @param password The password to use for encryption. * @param opsLimit The operations limit, which must be in the range {@link PasswordHash#minOpsLimit()} to * {@link PasswordHash#maxOpsLimit()}. * @param memLimit The memory limit, which must be in the range {@link PasswordHash#minMemLimit()} to * {@link PasswordHash#maxMemLimit()}. * @param algorithm The algorithm to use. * @return The encrypted data. */ public static Bytes encrypt( Bytes message, String password, long opsLimit, long memLimit, PasswordHash.Algorithm algorithm) { return Bytes.wrap(encrypt(message.toArrayUnsafe(), password, opsLimit, memLimit, algorithm)); } /** * Encrypt a message with a password, using {@link PasswordHash} for the key generation. * * @param message The message to encrypt. * @param password The password to use for encryption. * @param opsLimit The operations limit, which must be in the range {@link PasswordHash#minOpsLimit()} to * {@link PasswordHash#maxOpsLimit()}. * @param memLimit The memory limit, which must be in the range {@link PasswordHash#minMemLimit()} to * {@link PasswordHash#maxMemLimit()}. * @param algorithm The algorithm to use. * @return The encrypted data. * @throws UnsupportedOperationException If the specified algorithm is not supported by the currently loaded sodium * native library. */ public static byte[] encrypt( byte[] message, String password, long opsLimit, long memLimit, PasswordHash.Algorithm algorithm) { requireNonNull(message); requireNonNull(password); if (!algorithm.isSupported()) { throw new UnsupportedOperationException( algorithm.name() + " is not supported by the currently loaded sodium native library"); } int macLength = macLength(); byte[] cipherText = new byte[macLength + message.length]; Nonce nonce = Nonce.random(); Key key = deriveKeyFromPassword(password, nonce, opsLimit, memLimit, algorithm); assert key.ptr != null; int rc; try { rc = Sodium.crypto_secretbox_easy(cipherText, message, message.length, nonce.ptr, key.ptr); } finally { key.destroy(); } if (rc != 0) { throw new SodiumException("crypto_secretbox_easy: failed with result " + rc); } return prependNonce(nonce, cipherText); } /** * Encrypt a message with a password, generating a detached message authentication code, using {@link PasswordHash} * for the key generation (with the currently recommended algorithm and limits on operations and memory that are * suitable for most use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptDetached(Bytes message, String password) { return encryptDetached( message, password, PasswordHash.moderateOpsLimit(), PasswordHash.moderateMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Encrypt a message with a password, generating a detached message authentication code, using {@link PasswordHash} * for the key generation (with the currently recommended algorithm and limits on operations and memory that are * suitable for most use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptDetached(byte[] message, String password) { return encryptDetached( message, password, PasswordHash.moderateOpsLimit(), PasswordHash.moderateMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Encrypt a message with a password, generating a detached message authentication code, using {@link PasswordHash} * for the key generation (with limits on operations and memory that are suitable for most use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @param algorithm The algorithm to use. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptDetached( Bytes message, String password, PasswordHash.Algorithm algorithm) { return encryptDetached( message, password, PasswordHash.moderateOpsLimit(), PasswordHash.moderateMemLimit(), algorithm); } /** * Encrypt a message with a password, generating a detached message authentication code, using {@link PasswordHash} * for the key generation (with limits on operations and memory that are suitable for most use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @param algorithm The algorithm to use. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptDetached( byte[] message, String password, PasswordHash.Algorithm algorithm) { return encryptDetached( message, password, PasswordHash.moderateOpsLimit(), PasswordHash.moderateMemLimit(), algorithm); } /** * Encrypt a message with a password, generating a detached message authentication code, using {@link PasswordHash} * for the key generation (with the currently recommended algorithm and limits on operations and memory that are * suitable for interactive use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptInteractiveDetached(Bytes message, String password) { return encryptDetached( message, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Encrypt a message with a password, generating a detached message authentication code, using {@link PasswordHash} * for the key generation (with the currently recommended algorithm and limits on operations and memory that are * suitable for interactive use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptInteractiveDetached(byte[] message, String password) { return encryptDetached( message, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Encrypt a message with a password, generating a detached message authentication code, using {@link PasswordHash} * for the key generation (with limits on operations and memory that are suitable for interactive use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @param algorithm The algorithm to use. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptInteractiveDetached( Bytes message, String password, PasswordHash.Algorithm algorithm) { return encryptDetached( message, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), algorithm); } /** * Encrypt a message with a password, generating a detached message authentication code, using {@link PasswordHash} * for the key generation (with limits on operations and memory that are suitable for interactive use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @param algorithm The algorithm to use. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptInteractiveDetached( byte[] message, String password, PasswordHash.Algorithm algorithm) { return encryptDetached( message, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), algorithm); } /** * Encrypt a message with a password, generating a detached message authentication code, using {@link PasswordHash} * for the key generation (with the currently recommended algorithm and limits on operations and memory that are * suitable for sensitive use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptSensitiveDetached(Bytes message, String password) { return encryptDetached( message, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Encrypt a message with a password, generating a detached message authentication code, using {@link PasswordHash} * for the key generation (with the currently recommended algorithm and limits on operations and memory that are * suitable for sensitive use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptSensitiveDetached(byte[] message, String password) { return encryptDetached( message, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Encrypt a message with a password, generating a detached message authentication code, using {@link PasswordHash} * for the key generation (with limits on operations and memory that are suitable for sensitive use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @param algorithm The algorithm to use. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptSensitiveDetached( Bytes message, String password, PasswordHash.Algorithm algorithm) { return encryptDetached( message, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), algorithm); } /** * Encrypt a message with a password, generating a detached message authentication code, using {@link PasswordHash} * for the key generation (with limits on operations and memory that are suitable for sensitive use-cases). * * @param message The message to encrypt. * @param password The password to use for encryption. * @param algorithm The algorithm to use. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptSensitiveDetached( byte[] message, String password, PasswordHash.Algorithm algorithm) { return encryptDetached( message, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), algorithm); } /** * Encrypt a message with a password, generating a detached message authentication code, using {@link PasswordHash} * for the key generation * * @param message The message to encrypt. * @param password The password to use for encryption. * @param opsLimit The operations limit, which must be in the range {@link PasswordHash#minOpsLimit()} to * {@link PasswordHash#maxOpsLimit()}. * @param memLimit The memory limit, which must be in the range {@link PasswordHash#minMemLimit()} to * {@link PasswordHash#maxMemLimit()}. * @param algorithm The algorithm to use. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptDetached( Bytes message, String password, long opsLimit, long memLimit, PasswordHash.Algorithm algorithm) { return encryptDetached(message.toArrayUnsafe(), password, opsLimit, memLimit, algorithm); } /** * Encrypt a message with a password, generating a detached message authentication code, using {@link PasswordHash} * for the key generation. * * @param message The message to encrypt. * @param password The password to use for encryption. * @param opsLimit The operations limit, which must be in the range {@link PasswordHash#minOpsLimit()} to * {@link PasswordHash#maxOpsLimit()}. * @param memLimit The memory limit, which must be in the range {@link PasswordHash#minMemLimit()} to * {@link PasswordHash#maxMemLimit()}. * @param algorithm The algorithm to use. * @return The encrypted data and message authentication code. */ public static DetachedEncryptionResult encryptDetached( byte[] message, String password, long opsLimit, long memLimit, PasswordHash.Algorithm algorithm) { requireNonNull(message); requireNonNull(password); if (!algorithm.isSupported()) { throw new UnsupportedOperationException( algorithm.name() + " is not supported by the currently loaded sodium native library"); } int macLength = macLength(); byte[] cipherText = new byte[message.length]; byte[] mac = new byte[macLength]; Nonce nonce = Nonce.random(); Key key = deriveKeyFromPassword(password, nonce, opsLimit, memLimit, algorithm); assert key.ptr != null; int rc; try { rc = Sodium.crypto_secretbox_detached(cipherText, mac, message, message.length, nonce.ptr, key.ptr); } finally { key.destroy(); } if (rc != 0) { throw new SodiumException("crypto_secretbox_detached: failed with result " + rc); } return new DefaultDetachedEncryptionResult(cipherText, prependNonce(nonce, mac)); } /** * Decrypt a message using a password, using {@link PasswordHash} for the key generation (with the currently * recommended algorithm and limits on operations and memory that are suitable for most use-cases). * * @param cipherText The cipher text to decrypt. * @param password The password that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decrypt(Bytes cipherText, String password) { return decrypt( cipherText, password, PasswordHash.moderateOpsLimit(), PasswordHash.moderateMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Decrypt a message using a password, using {@link PasswordHash} for the key generation (with the currently * recommended algorithm and limits on operations and memory that are suitable for most use-cases). * * @param cipherText The cipher text to decrypt. * @param password The password that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decrypt(byte[] cipherText, String password) { return decrypt( cipherText, password, PasswordHash.moderateOpsLimit(), PasswordHash.moderateMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Decrypt a message using a password, using {@link PasswordHash} for the key generation (with limits on operations * and memory that are suitable for most use-cases). * * @param cipherText The cipher text to decrypt. * @param password The password that was used for encryption. * @param algorithm The algorithm that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decrypt(Bytes cipherText, String password, PasswordHash.Algorithm algorithm) { return decrypt(cipherText, password, PasswordHash.moderateOpsLimit(), PasswordHash.moderateMemLimit(), algorithm); } /** * Decrypt a message using a password, using {@link PasswordHash} for the key generation (with limits on operations * and memory that are suitable for most use-cases). * * @param cipherText The cipher text to decrypt. * @param password The password that was used for encryption. * @param algorithm The algorithm that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decrypt(byte[] cipherText, String password, PasswordHash.Algorithm algorithm) { return decrypt(cipherText, password, PasswordHash.moderateOpsLimit(), PasswordHash.moderateMemLimit(), algorithm); } /** * Decrypt a message using a password, using {@link PasswordHash} for the key generation (with the currently * recommended algorithm and limits on operations and memory that are suitable for interactive use-cases). * * @param cipherText The cipher text to decrypt. * @param password The password that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decryptInteractive(Bytes cipherText, String password) { return decrypt( cipherText, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Decrypt a message using a password, using {@link PasswordHash} for the key generation (with the currently * recommended algorithm and limits on operations and memory that are suitable for interactive use-cases). * * @param cipherText The cipher text to decrypt. * @param password The password that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decryptInteractive(byte[] cipherText, String password) { return decrypt( cipherText, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Decrypt a message using a password, using {@link PasswordHash} for the key generation (with limits on operations * and memory that are suitable for interactive use-cases). * * @param cipherText The cipher text to decrypt. * @param password The password that was used for encryption. * @param algorithm The algorithm that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decryptInteractive(Bytes cipherText, String password, PasswordHash.Algorithm algorithm) { return decrypt( cipherText, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), algorithm); } /** * Decrypt a message using a password, using {@link PasswordHash} for the key generation (with limits on operations * and memory that are suitable for interactive use-cases). * * @param cipherText The cipher text to decrypt. * @param password The password that was used for encryption. * @param algorithm The algorithm that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decryptInteractive(byte[] cipherText, String password, PasswordHash.Algorithm algorithm) { return decrypt( cipherText, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), algorithm); } /** * Decrypt a message using a password, using {@link PasswordHash} for the key generation (with the currently * recommended algorithm and limits on operations and memory that are suitable for sensitive use-cases). * * @param cipherText The cipher text to decrypt. * @param password The password that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decryptSensitive(Bytes cipherText, String password) { return decrypt( cipherText, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Decrypt a message using a password, using {@link PasswordHash} for the key generation (with the currently * recommended algorithm and limits on operations and memory that are suitable for sensitive use-cases). * * @param cipherText The cipher text to decrypt. * @param password The password that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decryptSensitive(byte[] cipherText, String password) { return decrypt( cipherText, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Decrypt a message using a password, using {@link PasswordHash} for the key generation (with limits on operations * and memory that are suitable for sensitive use-cases). * * @param cipherText The cipher text to decrypt. * @param password The password that was used for encryption. * @param algorithm The algorithm that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decryptSensitive(Bytes cipherText, String password, PasswordHash.Algorithm algorithm) { return decrypt(cipherText, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), algorithm); } /** * Decrypt a message using a password, using {@link PasswordHash} for the key generation (with limits on operations * and memory that are suitable for sensitive use-cases). * * @param cipherText The cipher text to decrypt. * @param password The password that was used for encryption. * @param algorithm The algorithm that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decryptSensitive(byte[] cipherText, String password, PasswordHash.Algorithm algorithm) { return decrypt(cipherText, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), algorithm); } /** * Decrypt a message using a password, using {@link PasswordHash} for the key generation. * * @param cipherText The cipher text to decrypt. * @param password The password that was used for encryption. * @param opsLimit The opsLimit that was used for encryption. * @param memLimit The memLimit that was used for encryption. * @param algorithm The algorithm that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decrypt( Bytes cipherText, String password, long opsLimit, long memLimit, PasswordHash.Algorithm algorithm) { byte[] bytes = decrypt(cipherText.toArrayUnsafe(), password, opsLimit, memLimit, algorithm); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a password, using {@link PasswordHash} for the key generation. * * @param cipherText The cipher text to decrypt. * @param password The password that was used for encryption. * @param opsLimit The opsLimit that was used for encryption. * @param memLimit The memLimit that was used for encryption. * @param algorithm The algorithm that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. * @throws UnsupportedOperationException If the specified algorithm is not supported by the currently loaded sodium * native library. */ @Nullable public static byte[] decrypt( byte[] cipherText, String password, long opsLimit, long memLimit, PasswordHash.Algorithm algorithm) { requireNonNull(cipherText); requireNonNull(password); if (!algorithm.isSupported()) { throw new UnsupportedOperationException( algorithm.name() + " is not supported by the currently loaded sodium native library"); } int noncebytes = Nonce.length(); int macLength = macLength(); if ((noncebytes + macLength) > cipherText.length) { throw new IllegalArgumentException("cipherText is too short"); } byte[] clearText = new byte[cipherText.length - noncebytes - macLength]; Nonce nonce = Nonce.fromBytes(Arrays.copyOf(cipherText, noncebytes)); Key key = deriveKeyFromPassword(password, nonce, opsLimit, memLimit, algorithm); assert key.ptr != null; int rc; try { rc = Sodium.crypto_secretbox_open_easy( clearText, Arrays.copyOfRange(cipherText, noncebytes, cipherText.length), cipherText.length - noncebytes, nonce.ptr, key.ptr); } finally { key.destroy(); } if (rc == -1) { return null; } if (rc != 0) { throw new SodiumException("crypto_secretbox_open_easy: failed with result " + rc); } return clearText; } /** * Decrypt a message using a password and a detached message authentication code, using {@link PasswordHash} for the * key generation (with the currently recommended algorithm and limits on operations and memory that are suitable for * most use-cases). * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param password The password that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decryptDetached(Bytes cipherText, Bytes mac, String password) { return decryptDetached( cipherText, mac, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Decrypt a message using a password and a detached message authentication code, using {@link PasswordHash} for the * key generation (with the currently recommended algorithm and limits on operations and memory that are suitable for * most use-cases). * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param password The password that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decryptDetached(byte[] cipherText, byte[] mac, String password) { return decryptDetached( cipherText, mac, password, PasswordHash.moderateOpsLimit(), PasswordHash.moderateMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Decrypt a message using a password and a detached message authentication code, using {@link PasswordHash} for the * key generation (with limits on operations and memory that are suitable for most use-cases). * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param password The password that was used for encryption. * @param algorithm The algorithm that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decryptDetached(Bytes cipherText, Bytes mac, String password, PasswordHash.Algorithm algorithm) { return decryptDetached( cipherText, mac, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), algorithm); } /** * Decrypt a message using a password and a detached message authentication code, using {@link PasswordHash} for the * key generation (with limits on operations and memory that are suitable for most use-cases). * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param password The password that was used for encryption. * @param algorithm The algorithm that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decryptDetached( byte[] cipherText, byte[] mac, String password, PasswordHash.Algorithm algorithm) { return decryptDetached( cipherText, mac, password, PasswordHash.moderateOpsLimit(), PasswordHash.moderateMemLimit(), algorithm); } /** * Decrypt a message using a password and a detached message authentication code, using {@link PasswordHash} for the * key generation (with the currently recommended algorithm and limits on operations and memory that are suitable for * interactive use-cases). * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param password The password that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decryptInteractiveDetached(Bytes cipherText, Bytes mac, String password) { return decryptDetached( cipherText, mac, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Decrypt a message using a password and a detached message authentication code, using {@link PasswordHash} for the * key generation (with the currently recommended algorithm and limits on operations and memory that are suitable for * interactive use-cases). * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param password The password that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decryptInteractiveDetached(byte[] cipherText, byte[] mac, String password) { return decryptDetached( cipherText, mac, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Decrypt a message using a password and a detached message authentication code, using {@link PasswordHash} for the * key generation (with limits on operations and memory that are suitable for interactive use-cases). * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param password The password that was used for encryption. * @param algorithm The algorithm that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decryptInteractiveDetached( Bytes cipherText, Bytes mac, String password, PasswordHash.Algorithm algorithm) { return decryptDetached( cipherText, mac, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), algorithm); } /** * Decrypt a message using a password and a detached message authentication code, using {@link PasswordHash} for the * key generation (with limits on operations and memory that are suitable for interactive use-cases). * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param password The password that was used for encryption. * @param algorithm The algorithm that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decryptInteractiveDetached( byte[] cipherText, byte[] mac, String password, PasswordHash.Algorithm algorithm) { return decryptDetached( cipherText, mac, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), algorithm); } /** * Decrypt a message using a password and a detached message authentication code, using {@link PasswordHash} for the * key generation (with the currently recommended algorithm and limits on operations and memory that are suitable for * sensitive use-cases). * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param password The password that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decryptSensitiveDetached(Bytes cipherText, Bytes mac, String password) { return decryptDetached( cipherText, mac, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Decrypt a message using a password and a detached message authentication code, using {@link PasswordHash} for the * key generation (with the currently recommended algorithm and limits on operations and memory that are suitable for * sensitive use-cases). * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param password The password that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decryptSensitiveDetached(byte[] cipherText, byte[] mac, String password) { return decryptDetached( cipherText, mac, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), PasswordHash.Algorithm.recommended()); } /** * Decrypt a message using a password and a detached message authentication code, using {@link PasswordHash} for the * key generation (with limits on operations and memory that are suitable for sensitive use-cases). * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param password The password that was used for encryption. * @param algorithm The algorithm that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decryptSensitiveDetached( Bytes cipherText, Bytes mac, String password, PasswordHash.Algorithm algorithm) { return decryptDetached( cipherText, mac, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), algorithm); } /** * Decrypt a message using a password and a detached message authentication code, using {@link PasswordHash} for the * key generation (with limits on operations and memory that are suitable for sensitive use-cases). * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param password The password that was used for encryption. * @param algorithm The algorithm that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decryptSensitiveDetached( byte[] cipherText, byte[] mac, String password, PasswordHash.Algorithm algorithm) { return decryptDetached( cipherText, mac, password, PasswordHash.sensitiveOpsLimit(), PasswordHash.sensitiveMemLimit(), algorithm); } /** * Decrypt a message using a password and a detached message authentication code, using {@link PasswordHash} for the * key generation. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param password The password that was used for encryption. * @param opsLimit The opsLimit that was used for encryption. * @param memLimit The memLimit that was used for encryption. * @param algorithm The algorithm that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decryptDetached( Bytes cipherText, Bytes mac, String password, long opsLimit, long memLimit, PasswordHash.Algorithm algorithm) { byte[] bytes = decryptDetached(cipherText.toArrayUnsafe(), mac.toArrayUnsafe(), password, opsLimit, memLimit, algorithm); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a password and a detached message authentication code, using {@link PasswordHash} for the * key generation. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param password The password that was used for encryption. * @param opsLimit The opsLimit that was used for encryption. * @param memLimit The memLimit that was used for encryption. * @param algorithm The algorithm that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decryptDetached( byte[] cipherText, byte[] mac, String password, long opsLimit, long memLimit, PasswordHash.Algorithm algorithm) { requireNonNull(cipherText); requireNonNull(mac); requireNonNull(password); if (!algorithm.isSupported()) { throw new UnsupportedOperationException( algorithm.name() + " is not supported by the currently loaded sodium native library"); } int noncebytes = Nonce.length(); int macLength = macLength(); if ((noncebytes + macLength) != mac.length) { throw new IllegalArgumentException("mac must be " + (noncebytes + macLength) + " bytes, got " + mac.length); } byte[] clearText = new byte[cipherText.length]; Nonce nonce = Nonce.fromBytes(Arrays.copyOf(mac, noncebytes)); Key key = deriveKeyFromPassword(password, nonce, opsLimit, memLimit, algorithm); assert key.ptr != null; int rc; try { rc = Sodium.crypto_secretbox_open_detached( clearText, cipherText, Arrays.copyOfRange(mac, noncebytes, mac.length), cipherText.length, nonce.ptr, key.ptr); } finally { key.destroy(); } if (rc == -1) { return null; } if (rc != 0) { throw new SodiumException("crypto_secretbox_open_detached: failed with result " + rc); } return clearText; } private static int macLength() { long macbytes = Sodium.crypto_secretbox_macbytes(); if (macbytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_secretbox_macbytes: " + macbytes + " is too large"); } return (int) macbytes; } private static Key deriveKeyFromPassword( String password, Nonce nonce, long opsLimit, long memLimit, PasswordHash.Algorithm algorithm) { assert Nonce.length() >= PasswordHash.Salt .length() : "SecretBox.Nonce has insufficient length for deriving a PasswordHash.Salt (" + Nonce.length() + " < " + PasswordHash.Salt.length() + ")"; PasswordHash.Salt salt = PasswordHash.Salt.fromBytes(Arrays.copyOfRange(nonce.bytesArray(), 0, PasswordHash.Salt.length())); byte[] passwordBytes = password.getBytes(UTF_8); try { byte[] keyBytes = PasswordHash.hash(passwordBytes, Key.length(), salt, opsLimit, memLimit, algorithm); try { return Key.fromBytes(keyBytes); } finally { Arrays.fill(keyBytes, (byte) 0); } } finally { Arrays.fill(passwordBytes, (byte) 0); } } private static byte[] prependNonce(Nonce nonce, byte[] bytes) { int nonceLength = Nonce.length(); byte[] data = new byte[nonceLength + bytes.length]; nonce.ptr.get(0, data, 0, nonceLength); System.arraycopy(bytes, 0, data, nonceLength, bytes.length); return data; } } cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/SecretDecryptionStream.java000066400000000000000000000025621341750772100322700ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import net.consensys.cava.bytes.Bytes; import javax.security.auth.Destroyable; /** * Used to decrypt a sequence of messages, or a single message split into arbitrary chunks. */ public interface SecretDecryptionStream extends Destroyable { /** * Pull a message from this secret stream. * * @param cipherText The encrypted message. * @return The clear text. */ default Bytes pull(Bytes cipherText) { return Bytes.wrap(pull(cipherText.toArrayUnsafe())); } /** * Pull a message from this secret stream. * * @param cipherText The encrypted message. * @return The clear text. */ byte[] pull(byte[] cipherText); /** @return {@code true} if no more messages should be decrypted by this stream */ boolean isComplete(); } cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/SecretEncryptionStream.java000066400000000000000000000050771341750772100323060ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import net.consensys.cava.bytes.Bytes; import javax.security.auth.Destroyable; /** * Used to encrypt a sequence of messages, or a single message split into arbitrary chunks. */ public interface SecretEncryptionStream extends Destroyable { /** @return The header for the stream. */ default Bytes header() { return Bytes.wrap(headerArray()); } /** @return The header for the stream. */ byte[] headerArray(); /** * Push a message to this secret stream. * * @param clearText The message to encrypt. * @return The encrypted message. */ default Bytes push(Bytes clearText) { return push(clearText, false); } /** * Push a message to this secret stream. * * @param clearText The message to encrypt. * @return The encrypted message. */ default byte[] push(byte[] clearText) { return push(clearText, false); } /** * Push the final message to this secret stream. * * @param clearText The message to encrypt. * @return The encrypted message. */ default Bytes pushLast(Bytes clearText) { return push(clearText, true); } /** * Push the final message to this secret stream. * * @param clearText The message to encrypt. * @return The encrypted message. */ default byte[] pushLast(byte[] clearText) { return push(clearText, true); } /** * Push a message to this secret stream. * * @param clearText The message to encrypt. * @param isFinal {@code true} if this is the final message that will be sent on this stream. * @return The encrypted message. */ default Bytes push(Bytes clearText, boolean isFinal) { return Bytes.wrap(push(clearText.toArrayUnsafe(), isFinal)); } /** * Push a message to this secret stream. * * @param clearText The message to encrypt. * @param isFinal {@code true} if this is the final message that will be sent on this stream. * @return The encrypted message. */ byte[] push(byte[] clearText, boolean isFinal); } cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/Sodium.java000066400000000000000000002702731341750772100270740ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import static java.util.Objects.requireNonNull; import java.nio.file.Files; import java.nio.file.Path; import java.util.function.BiFunction; import javax.annotation.Nullable; import jnr.ffi.LibraryLoader; import jnr.ffi.Platform; import jnr.ffi.Pointer; import jnr.ffi.byref.ByteByReference; import jnr.ffi.byref.LongLongByReference; /** * Access to the sodium native library. * *

* This class provides static methods for checking or loading the sodium native library. */ public final class Sodium { private Sodium() {} static final SodiumVersion VERSION_10_0_11 = new SodiumVersion(9, 3, "10.0.11"); static final SodiumVersion VERSION_10_0_12 = new SodiumVersion(9, 4, "10.0.12"); static final SodiumVersion VERSION_10_0_13 = new SodiumVersion(9, 5, "10.0.13"); static final SodiumVersion VERSION_10_0_14 = new SodiumVersion(9, 6, "10.0.14"); static final SodiumVersion VERSION_10_0_15 = new SodiumVersion(10, 0, "10.0.15"); static final SodiumVersion VERSION_10_0_16 = new SodiumVersion(10, 1, "10.0.16"); /** * The minimum version of the sodium native library that this binding supports. * * @return The minimum version of the sodium native library that this binding supports. */ public static SodiumVersion minSupportedVersion() { return VERSION_10_0_11; } /** * The version of the loaded sodium native library. * * @return The version of the loaded sodium library. */ public static SodiumVersion version() { return version(libSodium()); } private static SodiumVersion version(LibSodium lib) { return new SodiumVersion( lib.sodium_library_version_major(), lib.sodium_library_version_minor(), lib.sodium_version_string()); } /** * Check if the loaded sodium native library is the same or later than the specified version. * * @param requiredVersion The version to compare to. * @return {@code true} if the loaded sodium native library is the same or a later version. */ public static boolean supportsVersion(SodiumVersion requiredVersion) { return supportsVersion(requiredVersion, libSodium()); } private static boolean supportsVersion(SodiumVersion requiredVersion, LibSodium lib) { return version(lib).compareTo(requiredVersion) >= 0; } private static final String LIBRARY_NAME; static { try { Class.forName("jnr.ffi.Platform"); } catch (ClassNotFoundException e) { throw new IllegalStateException("JNR-FFI is not available on the classpath, see https://github.com/jnr/jnr-ffi"); } switch (Platform.getNativePlatform().getOS()) { case WINDOWS: LIBRARY_NAME = "libsodium"; break; default: LIBRARY_NAME = "sodium"; break; } } private static volatile LibSodium libSodium = null; /** * Load and initialize the native libsodium shared library. * *

* If this method returns successfully (without throwing a {@link LinkageError}), then all future calls to methods * provided by this class will use the loaded library. * * @param path The path to the shared library. * @throws LinkageError If the library cannot be found, dependent libraries are missing, or cannot be initialized. */ public static void loadLibrary(Path path) { requireNonNull(path); if (!Files.exists(path)) { throw new IllegalArgumentException("Non-existent path"); } Path dir = path.getParent(); Path library = path.getFileName(); LibSodium lib = LibraryLoader.create(LibSodium.class).search(dir.toFile().getAbsolutePath()).load(library.toString()); initializeLibrary(lib); synchronized (LibSodium.class) { Sodium.libSodium = lib; } } /** * Search for, then load and initialize the native libsodium shared library. * *

* The library will be searched for in all the provided locations, using the library name {@code "sodium"}. If this * method returns successfully (without throwing a {@link LinkageError}), then all future calls to methods provided by * this class will use the loaded library. * * @param paths A set of directories to search for the library in. * @throws LinkageError If the library cannot be found, dependent libraries are missing, or cannot be initialized. */ public static void searchLibrary(Path... paths) { searchLibrary(LIBRARY_NAME, paths); } /** * Search for, then load and initialize the native libsodium shared library. * *

* The library will be searched for in all the provided locations, using the provided library name. If this method * returns successfully (without throwing a {@link LinkageError}), then all future calls to methods provided by this * class will use the loaded library. * * @param libraryName The name of the library (e.g. {@code "sodium"}). * @param paths A set of directories to search for the library in. * @throws LinkageError If the library cannot be found, dependent libraries are missing, or cannot be initialized. */ public static void searchLibrary(String libraryName, Path... paths) { LibraryLoader loader = LibraryLoader.create(LibSodium.class); for (Path path : paths) { loader = loader.search(path.toFile().getAbsolutePath()); } LibSodium lib = loader.load(libraryName); initializeLibrary(lib); synchronized (LibSodium.class) { Sodium.libSodium = lib; } } private static LibSodium libSodium() { if (libSodium == null) { synchronized (LibSodium.class) { if (libSodium == null) { LibSodium lib = LibraryLoader .create(LibSodium.class) .search("/usr/local/lib") .search("/opt/local/lib") .search("/usr/lib") .search("/lib") .load(LIBRARY_NAME); libSodium = initializeLibrary(lib); } } } return libSodium; } private static LibSodium initializeLibrary(LibSodium lib) { if (!supportsVersion(minSupportedVersion(), lib)) { throw new LinkageError( String.format( "Unsupported libsodium version %s (%s:%s)", lib.sodium_version_string(), lib.sodium_library_version_major(), lib.sodium_library_version_minor())); } int result = lib.sodium_init(); if (result == -1) { throw new LinkageError("Failed to initialize libsodium: sodium_init returned " + result); } return lib; } /** * Check if the sodium library is available. * *

* If the sodium library has not already been loaded, this will attempt to load and initialize it before returning. * * @return {@code true} if the library is loaded and available. */ public static boolean isAvailable() { try { libSodium(); } catch (LinkageError e) { return false; } return true; } static Pointer malloc(long length) { Pointer ptr = sodium_malloc(length); if (ptr == null) { throw new OutOfMemoryError("Sodium.sodium_malloc failed allocating " + length); } return ptr; } static Pointer dup(Pointer src, int length) { Pointer ptr = malloc(length); try { ptr.transferFrom(0, src, 0, length); return ptr; } catch (Throwable e) { sodium_free(ptr); throw e; } } static Pointer dupAndIncrement(Pointer src, int length) { Pointer ptr = dup(src, length); try { sodium_increment(ptr, length); return ptr; } catch (Throwable e) { sodium_free(ptr); throw e; } } static T dupAndIncrement(Pointer src, int length, BiFunction ctr) { Pointer ptr = Sodium.dupAndIncrement(src, length); try { return ctr.apply(ptr, length); } catch (Throwable e) { sodium_free(ptr); throw e; } } static Pointer dup(byte[] bytes) { Pointer ptr = malloc(bytes.length); try { ptr.put(0, bytes, 0, bytes.length); return ptr; } catch (Throwable e) { sodium_free(ptr); throw e; } } static T dup(byte[] bytes, BiFunction ctr) { Pointer ptr = Sodium.dup(bytes); try { return ctr.apply(ptr, bytes.length); } catch (Throwable e) { sodium_free(ptr); throw e; } } static byte[] reify(Pointer ptr, int length) { byte[] bytes = new byte[length]; ptr.get(0, bytes, 0, bytes.length); return bytes; } static Pointer randomBytes(int length) { Pointer ptr = malloc(length); try { randombytes_buf(ptr, length); return ptr; } catch (Throwable e) { sodium_free(ptr); throw e; } } static T randomBytes(int length, BiFunction ctr) { Pointer ptr = Sodium.randomBytes(length); try { return ctr.apply(ptr, length); } catch (Throwable e) { sodium_free(ptr); throw e; } } static int hashCode(Pointer ptr, int length) { int result = 1; for (int i = 0; i < length; ++i) { result = 31 * result + ((int) ptr.getByte(i)); } return result; } static T scalarMultBase(Pointer src, long length, BiFunction ctr) { if (length != Sodium.crypto_scalarmult_scalarbytes()) { throw new IllegalArgumentException( "key length is " + length + " but required " + Sodium.crypto_scalarmult_scalarbytes()); } long sbytes = Sodium.crypto_scalarmult_bytes(); Pointer dst = malloc(Sodium.crypto_scalarmult_bytes()); try { int rc = Sodium.crypto_scalarmult_base(dst, src); if (rc != 0) { throw new SodiumException("crypto_scalarmult_base: failed with result " + rc); } return ctr.apply(dst, sbytes); } catch (Throwable e) { sodium_free(dst); throw e; } } ///////// // Generated with https://gist.github.com/cleishm/39fbad03378f5e1ad82521ad821cd065, then modified static String sodium_version_string() { return libSodium().sodium_version_string(); } static int sodium_library_version_major() { return libSodium().sodium_library_version_major(); } static int sodium_library_version_minor() { return libSodium().sodium_library_version_minor(); } static int sodium_library_minimal() { return libSodium().sodium_library_minimal(); } static int sodium_set_misuse_handler(Pointer handler) { return libSodium().sodium_set_misuse_handler(handler); } static void sodium_misuse() { libSodium().sodium_misuse(); } static int crypto_aead_aes256gcm_is_available() { return libSodium().crypto_aead_aes256gcm_is_available(); } static long crypto_aead_aes256gcm_keybytes() { return libSodium().crypto_aead_aes256gcm_keybytes(); } static long crypto_aead_aes256gcm_nsecbytes() { return libSodium().crypto_aead_aes256gcm_nsecbytes(); } static long crypto_aead_aes256gcm_npubbytes() { return libSodium().crypto_aead_aes256gcm_npubbytes(); } static long crypto_aead_aes256gcm_abytes() { return libSodium().crypto_aead_aes256gcm_abytes(); } static long crypto_aead_aes256gcm_messagebytes_max() { return libSodium().crypto_aead_aes256gcm_messagebytes_max(); } static long crypto_aead_aes256gcm_statebytes() { return libSodium().crypto_aead_aes256gcm_statebytes(); } static int crypto_aead_aes256gcm_encrypt( byte[] c, LongLongByReference clen_p, byte[] m, long mlen, byte[] ad, long adlen, @Nullable Pointer nsec, Pointer npub, Pointer k) { return libSodium().crypto_aead_aes256gcm_encrypt(c, clen_p, m, mlen, ad, adlen, nsec, npub, k); } static int crypto_aead_aes256gcm_decrypt( byte[] m, LongLongByReference mlen_p, @Nullable Pointer nsec, byte[] c, long clen, byte[] ad, long adlen, Pointer npub, Pointer k) { return libSodium().crypto_aead_aes256gcm_decrypt(m, mlen_p, nsec, c, clen, ad, adlen, npub, k); } static int crypto_aead_aes256gcm_encrypt_detached( byte[] c, byte[] mac, LongLongByReference maclen_p, byte[] m, long mlen, byte[] ad, long adlen, @Nullable Pointer nsec, Pointer npub, Pointer k) { return libSodium().crypto_aead_aes256gcm_encrypt_detached(c, mac, maclen_p, m, mlen, ad, adlen, nsec, npub, k); } static int crypto_aead_aes256gcm_decrypt_detached( byte[] m, @Nullable Pointer nsec, byte[] c, long clen, byte[] mac, byte[] ad, long adlen, Pointer npub, Pointer k) { return libSodium().crypto_aead_aes256gcm_decrypt_detached(m, nsec, c, clen, mac, ad, adlen, npub, k); } static int crypto_aead_aes256gcm_beforenm(Pointer ctx_, Pointer k) { return libSodium().crypto_aead_aes256gcm_beforenm(ctx_, k); } static int crypto_aead_aes256gcm_encrypt_afternm( byte[] c, LongLongByReference clen_p, byte[] m, long mlen, byte[] ad, long adlen, @Nullable Pointer nsec, Pointer npub, Pointer ctx_) { return libSodium().crypto_aead_aes256gcm_encrypt_afternm(c, clen_p, m, mlen, ad, adlen, nsec, npub, ctx_); } static int crypto_aead_aes256gcm_decrypt_afternm( byte[] m, LongLongByReference mlen_p, @Nullable Pointer nsec, byte[] c, long clen, byte[] ad, long adlen, Pointer npub, Pointer ctx_) { return libSodium().crypto_aead_aes256gcm_decrypt_afternm(m, mlen_p, nsec, c, clen, ad, adlen, npub, ctx_); } static int crypto_aead_aes256gcm_encrypt_detached_afternm( byte[] c, byte[] mac, LongLongByReference maclen_p, byte[] m, long mlen, byte[] ad, long adlen, @Nullable Pointer nsec, Pointer npub, Pointer ctx_) { return libSodium() .crypto_aead_aes256gcm_encrypt_detached_afternm(c, mac, maclen_p, m, mlen, ad, adlen, nsec, npub, ctx_); } static int crypto_aead_aes256gcm_decrypt_detached_afternm( byte[] m, @Nullable Pointer nsec, byte[] c, long clen, byte[] mac, byte[] ad, long adlen, Pointer npub, Pointer ctx_) { return libSodium().crypto_aead_aes256gcm_decrypt_detached_afternm(m, nsec, c, clen, mac, ad, adlen, npub, ctx_); } static void crypto_aead_aes256gcm_keygen(Pointer k) { libSodium().crypto_aead_aes256gcm_keygen(k); } static long crypto_aead_chacha20poly1305_ietf_keybytes() { return libSodium().crypto_aead_chacha20poly1305_ietf_keybytes(); } static long crypto_aead_chacha20poly1305_ietf_nsecbytes() { return libSodium().crypto_aead_chacha20poly1305_ietf_nsecbytes(); } static long crypto_aead_chacha20poly1305_ietf_npubbytes() { return libSodium().crypto_aead_chacha20poly1305_ietf_npubbytes(); } static long crypto_aead_chacha20poly1305_ietf_abytes() { return libSodium().crypto_aead_chacha20poly1305_ietf_abytes(); } static long crypto_aead_chacha20poly1305_ietf_messagebytes_max() { return libSodium().crypto_aead_chacha20poly1305_ietf_messagebytes_max(); } static int crypto_aead_chacha20poly1305_ietf_encrypt( byte[] c, LongLongByReference clen_p, byte[] m, long mlen, byte[] ad, long adlen, byte[] nsec, byte[] npub, byte[] k) { return libSodium().crypto_aead_chacha20poly1305_ietf_encrypt(c, clen_p, m, mlen, ad, adlen, nsec, npub, k); } static int crypto_aead_chacha20poly1305_ietf_decrypt( byte[] m, LongLongByReference mlen_p, byte[] nsec, byte[] c, long clen, byte[] ad, long adlen, byte[] npub, byte[] k) { return libSodium().crypto_aead_chacha20poly1305_ietf_decrypt(m, mlen_p, nsec, c, clen, ad, adlen, npub, k); } static int crypto_aead_chacha20poly1305_ietf_encrypt_detached( byte[] c, byte[] mac, LongLongByReference maclen_p, byte[] m, long mlen, byte[] ad, long adlen, byte[] nsec, byte[] npub, byte[] k) { return libSodium() .crypto_aead_chacha20poly1305_ietf_encrypt_detached(c, mac, maclen_p, m, mlen, ad, adlen, nsec, npub, k); } static int crypto_aead_chacha20poly1305_ietf_decrypt_detached( byte[] m, byte[] nsec, byte[] c, long clen, byte[] mac, byte[] ad, long adlen, byte[] npub, byte[] k) { return libSodium().crypto_aead_chacha20poly1305_ietf_decrypt_detached(m, nsec, c, clen, mac, ad, adlen, npub, k); } static void crypto_aead_chacha20poly1305_ietf_keygen(byte[] k) { libSodium().crypto_aead_chacha20poly1305_ietf_keygen(k); } static long crypto_aead_chacha20poly1305_keybytes() { return libSodium().crypto_aead_chacha20poly1305_keybytes(); } static long crypto_aead_chacha20poly1305_nsecbytes() { return libSodium().crypto_aead_chacha20poly1305_nsecbytes(); } static long crypto_aead_chacha20poly1305_npubbytes() { return libSodium().crypto_aead_chacha20poly1305_npubbytes(); } static long crypto_aead_chacha20poly1305_abytes() { return libSodium().crypto_aead_chacha20poly1305_abytes(); } static long crypto_aead_chacha20poly1305_messagebytes_max() { return libSodium().crypto_aead_chacha20poly1305_messagebytes_max(); } static int crypto_aead_chacha20poly1305_encrypt( byte[] c, LongLongByReference clen_p, byte[] m, long mlen, byte[] ad, long adlen, byte[] nsec, byte[] npub, byte[] k) { return libSodium().crypto_aead_chacha20poly1305_encrypt(c, clen_p, m, mlen, ad, adlen, nsec, npub, k); } static int crypto_aead_chacha20poly1305_decrypt( byte[] m, LongLongByReference mlen_p, byte[] nsec, byte[] c, long clen, byte[] ad, long adlen, byte[] npub, byte[] k) { return libSodium().crypto_aead_chacha20poly1305_decrypt(m, mlen_p, nsec, c, clen, ad, adlen, npub, k); } static int crypto_aead_chacha20poly1305_encrypt_detached( byte[] c, byte[] mac, LongLongByReference maclen_p, byte[] m, long mlen, byte[] ad, long adlen, byte[] nsec, byte[] npub, byte[] k) { return libSodium() .crypto_aead_chacha20poly1305_encrypt_detached(c, mac, maclen_p, m, mlen, ad, adlen, nsec, npub, k); } static int crypto_aead_chacha20poly1305_decrypt_detached( byte[] m, byte[] nsec, byte[] c, long clen, byte[] mac, byte[] ad, long adlen, byte[] npub, byte[] k) { return libSodium().crypto_aead_chacha20poly1305_decrypt_detached(m, nsec, c, clen, mac, ad, adlen, npub, k); } static void crypto_aead_chacha20poly1305_keygen(byte[] k) { libSodium().crypto_aead_chacha20poly1305_keygen(k); } static long crypto_aead_xchacha20poly1305_ietf_keybytes() { return libSodium().crypto_aead_xchacha20poly1305_ietf_keybytes(); } static long crypto_aead_xchacha20poly1305_ietf_nsecbytes() { return libSodium().crypto_aead_xchacha20poly1305_ietf_nsecbytes(); } static long crypto_aead_xchacha20poly1305_ietf_npubbytes() { return libSodium().crypto_aead_xchacha20poly1305_ietf_npubbytes(); } static long crypto_aead_xchacha20poly1305_ietf_abytes() { return libSodium().crypto_aead_xchacha20poly1305_ietf_abytes(); } static long crypto_aead_xchacha20poly1305_ietf_messagebytes_max() { return libSodium().crypto_aead_xchacha20poly1305_ietf_messagebytes_max(); } static int crypto_aead_xchacha20poly1305_ietf_encrypt( byte[] c, LongLongByReference clen_p, byte[] m, long mlen, byte[] ad, long adlen, @Nullable byte[] nsec, Pointer npub, Pointer k) { return libSodium().crypto_aead_xchacha20poly1305_ietf_encrypt(c, clen_p, m, mlen, ad, adlen, nsec, npub, k); } static int crypto_aead_xchacha20poly1305_ietf_decrypt( byte[] m, LongLongByReference mlen_p, @Nullable byte[] nsec, byte[] c, long clen, byte[] ad, long adlen, Pointer npub, Pointer k) { return libSodium().crypto_aead_xchacha20poly1305_ietf_decrypt(m, mlen_p, nsec, c, clen, ad, adlen, npub, k); } static int crypto_aead_xchacha20poly1305_ietf_encrypt_detached( byte[] c, byte[] mac, LongLongByReference maclen_p, byte[] m, long mlen, byte[] ad, long adlen, @Nullable byte[] nsec, Pointer npub, Pointer k) { return libSodium() .crypto_aead_xchacha20poly1305_ietf_encrypt_detached(c, mac, maclen_p, m, mlen, ad, adlen, nsec, npub, k); } static int crypto_aead_xchacha20poly1305_ietf_decrypt_detached( byte[] m, @Nullable byte[] nsec, byte[] c, long clen, byte[] mac, byte[] ad, long adlen, Pointer npub, Pointer k) { return libSodium().crypto_aead_xchacha20poly1305_ietf_decrypt_detached(m, nsec, c, clen, mac, ad, adlen, npub, k); } static void crypto_aead_xchacha20poly1305_ietf_keygen(Pointer k) { libSodium().crypto_aead_xchacha20poly1305_ietf_keygen(k); } static long crypto_hash_sha512_statebytes() { return libSodium().crypto_hash_sha512_statebytes(); } static long crypto_hash_sha512_bytes() { return libSodium().crypto_hash_sha512_bytes(); } static int crypto_hash_sha512(byte[] out, byte[] in, long inlen) { return libSodium().crypto_hash_sha512(out, in, inlen); } static int crypto_hash_sha512_init(Pointer state) { return libSodium().crypto_hash_sha512_init(state); } static int crypto_hash_sha512_update(Pointer state, byte[] in, long inlen) { return libSodium().crypto_hash_sha512_update(state, in, inlen); } static int crypto_hash_sha512_final(Pointer state, byte[] out) { return libSodium().crypto_hash_sha512_final(state, out); } static long crypto_auth_hmacsha512_bytes() { return libSodium().crypto_auth_hmacsha512_bytes(); } static long crypto_auth_hmacsha512_keybytes() { return libSodium().crypto_auth_hmacsha512_keybytes(); } static int crypto_auth_hmacsha512(byte[] out, byte[] in, long inlen, byte[] k) { return libSodium().crypto_auth_hmacsha512(out, in, inlen, k); } static int crypto_auth_hmacsha512_verify(byte[] h, byte[] in, long inlen, byte[] k) { return libSodium().crypto_auth_hmacsha512_verify(h, in, inlen, k); } static long crypto_auth_hmacsha512_statebytes() { return libSodium().crypto_auth_hmacsha512_statebytes(); } static int crypto_auth_hmacsha512_init(Pointer state, byte[] key, long keylen) { return libSodium().crypto_auth_hmacsha512_init(state, key, keylen); } static int crypto_auth_hmacsha512_update(Pointer state, byte[] in, long inlen) { return libSodium().crypto_auth_hmacsha512_update(state, in, inlen); } static int crypto_auth_hmacsha512_final(Pointer state, byte[] out) { return libSodium().crypto_auth_hmacsha512_final(state, out); } static void crypto_auth_hmacsha512_keygen(byte[] k) { libSodium().crypto_auth_hmacsha512_keygen(k); } static long crypto_auth_hmacsha512256_bytes() { return libSodium().crypto_auth_hmacsha512256_bytes(); } static long crypto_auth_hmacsha512256_keybytes() { return libSodium().crypto_auth_hmacsha512256_keybytes(); } static int crypto_auth_hmacsha512256(byte[] out, byte[] in, long inlen, byte[] k) { return libSodium().crypto_auth_hmacsha512256(out, in, inlen, k); } static int crypto_auth_hmacsha512256_verify(byte[] h, byte[] in, long inlen, byte[] k) { return libSodium().crypto_auth_hmacsha512256_verify(h, in, inlen, k); } static long crypto_auth_hmacsha512256_statebytes() { return libSodium().crypto_auth_hmacsha512256_statebytes(); } static int crypto_auth_hmacsha512256_init(Pointer state, byte[] key, long keylen) { return libSodium().crypto_auth_hmacsha512256_init(state, key, keylen); } static int crypto_auth_hmacsha512256_update(Pointer state, byte[] in, long inlen) { return libSodium().crypto_auth_hmacsha512256_update(state, in, inlen); } static int crypto_auth_hmacsha512256_final(Pointer state, byte[] out) { return libSodium().crypto_auth_hmacsha512256_final(state, out); } static void crypto_auth_hmacsha512256_keygen(byte[] k) { libSodium().crypto_auth_hmacsha512256_keygen(k); } static long crypto_auth_bytes() { return libSodium().crypto_auth_bytes(); } static long crypto_auth_keybytes() { return libSodium().crypto_auth_keybytes(); } static String crypto_auth_primitive() { return libSodium().crypto_auth_primitive(); } static int crypto_auth(byte[] out, byte[] in, long inlen, Pointer k) { return libSodium().crypto_auth(out, in, inlen, k); } static int crypto_auth_verify(byte[] h, byte[] in, long inlen, Pointer k) { return libSodium().crypto_auth_verify(h, in, inlen, k); } static void crypto_auth_keygen(Pointer k) { libSodium().crypto_auth_keygen(k); } static long crypto_hash_sha256_statebytes() { return libSodium().crypto_hash_sha256_statebytes(); } static long crypto_hash_sha256_bytes() { return libSodium().crypto_hash_sha256_bytes(); } static int crypto_hash_sha256(byte[] out, byte[] in, long inlen) { return libSodium().crypto_hash_sha256(out, in, inlen); } static int crypto_hash_sha256_init(Pointer state) { return libSodium().crypto_hash_sha256_init(state); } static int crypto_hash_sha256_update(Pointer state, byte[] in, long inlen) { return libSodium().crypto_hash_sha256_update(state, in, inlen); } static int crypto_hash_sha256_final(Pointer state, byte[] out) { return libSodium().crypto_hash_sha256_final(state, out); } static long crypto_auth_hmacsha256_bytes() { return libSodium().crypto_auth_hmacsha256_bytes(); } static long crypto_auth_hmacsha256_keybytes() { return libSodium().crypto_auth_hmacsha256_keybytes(); } static int crypto_auth_hmacsha256(byte[] out, byte[] in, long inlen, byte[] k) { return libSodium().crypto_auth_hmacsha256(out, in, inlen, k); } static int crypto_auth_hmacsha256_verify(byte[] h, byte[] in, long inlen, byte[] k) { return libSodium().crypto_auth_hmacsha256_verify(h, in, inlen, k); } static long crypto_auth_hmacsha256_statebytes() { return libSodium().crypto_auth_hmacsha256_statebytes(); } static int crypto_auth_hmacsha256_init(Pointer state, byte[] key, long keylen) { return libSodium().crypto_auth_hmacsha256_init(state, key, keylen); } static int crypto_auth_hmacsha256_update(Pointer state, byte[] in, long inlen) { return libSodium().crypto_auth_hmacsha256_update(state, in, inlen); } static int crypto_auth_hmacsha256_final(Pointer state, byte[] out) { return libSodium().crypto_auth_hmacsha256_final(state, out); } static void crypto_auth_hmacsha256_keygen(byte[] k) { libSodium().crypto_auth_hmacsha256_keygen(k); } static long crypto_stream_xsalsa20_keybytes() { return libSodium().crypto_stream_xsalsa20_keybytes(); } static long crypto_stream_xsalsa20_noncebytes() { return libSodium().crypto_stream_xsalsa20_noncebytes(); } static long crypto_stream_xsalsa20_messagebytes_max() { return libSodium().crypto_stream_xsalsa20_messagebytes_max(); } static int crypto_stream_xsalsa20(byte[] c, long clen, byte[] n, byte[] k) { return libSodium().crypto_stream_xsalsa20(c, clen, n, k); } static int crypto_stream_xsalsa20_xor(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) { return libSodium().crypto_stream_xsalsa20_xor(c, m, mlen, n, k); } static int crypto_stream_xsalsa20_xor_ic(byte[] c, byte[] m, long mlen, byte[] n, long ic, byte[] k) { return libSodium().crypto_stream_xsalsa20_xor_ic(c, m, mlen, n, ic, k); } static void crypto_stream_xsalsa20_keygen(byte[] k) { libSodium().crypto_stream_xsalsa20_keygen(k); } static long crypto_box_curve25519xsalsa20poly1305_seedbytes() { return libSodium().crypto_box_curve25519xsalsa20poly1305_seedbytes(); } static long crypto_box_curve25519xsalsa20poly1305_publickeybytes() { return libSodium().crypto_box_curve25519xsalsa20poly1305_publickeybytes(); } static long crypto_box_curve25519xsalsa20poly1305_secretkeybytes() { return libSodium().crypto_box_curve25519xsalsa20poly1305_secretkeybytes(); } static long crypto_box_curve25519xsalsa20poly1305_beforenmbytes() { return libSodium().crypto_box_curve25519xsalsa20poly1305_beforenmbytes(); } static long crypto_box_curve25519xsalsa20poly1305_noncebytes() { return libSodium().crypto_box_curve25519xsalsa20poly1305_noncebytes(); } static long crypto_box_curve25519xsalsa20poly1305_macbytes() { return libSodium().crypto_box_curve25519xsalsa20poly1305_macbytes(); } static long crypto_box_curve25519xsalsa20poly1305_messagebytes_max() { return libSodium().crypto_box_curve25519xsalsa20poly1305_messagebytes_max(); } static int crypto_box_curve25519xsalsa20poly1305_seed_keypair(byte[] pk, byte[] sk, byte[] seed) { return libSodium().crypto_box_curve25519xsalsa20poly1305_seed_keypair(pk, sk, seed); } static int crypto_box_curve25519xsalsa20poly1305_keypair(byte[] pk, byte[] sk) { return libSodium().crypto_box_curve25519xsalsa20poly1305_keypair(pk, sk); } static int crypto_box_curve25519xsalsa20poly1305_beforenm(Pointer k, byte[] pk, byte[] sk) { return libSodium().crypto_box_curve25519xsalsa20poly1305_beforenm(k, pk, sk); } static long crypto_box_curve25519xsalsa20poly1305_boxzerobytes() { return libSodium().crypto_box_curve25519xsalsa20poly1305_boxzerobytes(); } static long crypto_box_curve25519xsalsa20poly1305_zerobytes() { return libSodium().crypto_box_curve25519xsalsa20poly1305_zerobytes(); } static int crypto_box_curve25519xsalsa20poly1305(byte[] c, byte[] m, long mlen, byte[] n, byte[] pk, byte[] sk) { return libSodium().crypto_box_curve25519xsalsa20poly1305(c, m, mlen, n, pk, sk); } static int crypto_box_curve25519xsalsa20poly1305_open(byte[] m, byte[] c, long clen, byte[] n, byte[] pk, byte[] sk) { return libSodium().crypto_box_curve25519xsalsa20poly1305_open(m, c, clen, n, pk, sk); } static int crypto_box_curve25519xsalsa20poly1305_afternm(byte[] c, byte[] m, long mlen, byte[] n, Pointer k) { return libSodium().crypto_box_curve25519xsalsa20poly1305_afternm(c, m, mlen, n, k); } static int crypto_box_curve25519xsalsa20poly1305_open_afternm(byte[] m, byte[] c, long clen, byte[] n, Pointer k) { return libSodium().crypto_box_curve25519xsalsa20poly1305_open_afternm(m, c, clen, n, k); } static long crypto_box_seedbytes() { return libSodium().crypto_box_seedbytes(); } static long crypto_box_publickeybytes() { return libSodium().crypto_box_publickeybytes(); } static long crypto_box_secretkeybytes() { return libSodium().crypto_box_secretkeybytes(); } static long crypto_box_noncebytes() { return libSodium().crypto_box_noncebytes(); } static long crypto_box_macbytes() { return libSodium().crypto_box_macbytes(); } static long crypto_box_messagebytes_max() { return libSodium().crypto_box_messagebytes_max(); } static String crypto_box_primitive() { return libSodium().crypto_box_primitive(); } static int crypto_box_seed_keypair(Pointer pk, Pointer sk, Pointer seed) { return libSodium().crypto_box_seed_keypair(pk, sk, seed); } static int crypto_box_keypair(Pointer pk, Pointer sk) { return libSodium().crypto_box_keypair(pk, sk); } static int crypto_box_easy(byte[] c, byte[] m, long mlen, Pointer n, Pointer pk, Pointer sk) { return libSodium().crypto_box_easy(c, m, mlen, n, pk, sk); } static int crypto_box_open_easy(byte[] m, byte[] c, long clen, Pointer n, Pointer pk, Pointer sk) { return libSodium().crypto_box_open_easy(m, c, clen, n, pk, sk); } static int crypto_box_detached(byte[] c, byte[] mac, byte[] m, long mlen, Pointer n, Pointer pk, Pointer sk) { return libSodium().crypto_box_detached(c, mac, m, mlen, n, pk, sk); } static int crypto_box_open_detached(byte[] m, byte[] c, byte[] mac, long clen, Pointer n, Pointer pk, Pointer sk) { return libSodium().crypto_box_open_detached(m, c, mac, clen, n, pk, sk); } static long crypto_box_beforenmbytes() { return libSodium().crypto_box_beforenmbytes(); } static int crypto_box_beforenm(Pointer k, Pointer pk, Pointer sk) { return libSodium().crypto_box_beforenm(k, pk, sk); } static int crypto_box_easy_afternm(byte[] c, byte[] m, long mlen, Pointer n, Pointer k) { return libSodium().crypto_box_easy_afternm(c, m, mlen, n, k); } static int crypto_box_open_easy_afternm(byte[] m, byte[] c, long clen, Pointer n, Pointer k) { return libSodium().crypto_box_open_easy_afternm(m, c, clen, n, k); } static int crypto_box_detached_afternm(byte[] c, byte[] mac, byte[] m, long mlen, Pointer n, Pointer k) { return libSodium().crypto_box_detached_afternm(c, mac, m, mlen, n, k); } static int crypto_box_open_detached_afternm(byte[] m, byte[] c, byte[] mac, long clen, Pointer n, Pointer k) { return libSodium().crypto_box_open_detached_afternm(m, c, mac, clen, n, k); } static long crypto_box_sealbytes() { return libSodium().crypto_box_sealbytes(); } static int crypto_box_seal(byte[] c, byte[] m, long mlen, Pointer pk) { return libSodium().crypto_box_seal(c, m, mlen, pk); } static int crypto_box_seal_open(byte[] m, byte[] c, long clen, Pointer pk, Pointer sk) { return libSodium().crypto_box_seal_open(m, c, clen, pk, sk); } static long crypto_box_zerobytes() { return libSodium().crypto_box_zerobytes(); } static long crypto_box_boxzerobytes() { return libSodium().crypto_box_boxzerobytes(); } static int crypto_box(byte[] c, byte[] m, long mlen, byte[] n, byte[] pk, byte[] sk) { return libSodium().crypto_box(c, m, mlen, n, pk, sk); } static int crypto_box_open(byte[] m, byte[] c, long clen, byte[] n, byte[] pk, byte[] sk) { return libSodium().crypto_box_open(m, c, clen, n, pk, sk); } static int crypto_box_afternm(byte[] c, byte[] m, long mlen, byte[] n, Pointer k) { return libSodium().crypto_box_afternm(c, m, mlen, n, k); } static int crypto_box_open_afternm(byte[] m, byte[] c, long clen, byte[] n, Pointer k) { return libSodium().crypto_box_open_afternm(m, c, clen, n, k); } static long crypto_core_hsalsa20_outputbytes() { return libSodium().crypto_core_hsalsa20_outputbytes(); } static long crypto_core_hsalsa20_inputbytes() { return libSodium().crypto_core_hsalsa20_inputbytes(); } static long crypto_core_hsalsa20_keybytes() { return libSodium().crypto_core_hsalsa20_keybytes(); } static long crypto_core_hsalsa20_constbytes() { return libSodium().crypto_core_hsalsa20_constbytes(); } static int crypto_core_hsalsa20(byte[] out, byte[] in, byte[] k, byte[] c) { return libSodium().crypto_core_hsalsa20(out, in, k, c); } static long crypto_core_hchacha20_outputbytes() { return libSodium().crypto_core_hchacha20_outputbytes(); } static long crypto_core_hchacha20_inputbytes() { return libSodium().crypto_core_hchacha20_inputbytes(); } static long crypto_core_hchacha20_keybytes() { return libSodium().crypto_core_hchacha20_keybytes(); } static long crypto_core_hchacha20_constbytes() { return libSodium().crypto_core_hchacha20_constbytes(); } static int crypto_core_hchacha20(byte[] out, byte[] in, byte[] k, byte[] c) { return libSodium().crypto_core_hchacha20(out, in, k, c); } static long crypto_core_salsa20_outputbytes() { return libSodium().crypto_core_salsa20_outputbytes(); } static long crypto_core_salsa20_inputbytes() { return libSodium().crypto_core_salsa20_inputbytes(); } static long crypto_core_salsa20_keybytes() { return libSodium().crypto_core_salsa20_keybytes(); } static long crypto_core_salsa20_constbytes() { return libSodium().crypto_core_salsa20_constbytes(); } static int crypto_core_salsa20(byte[] out, byte[] in, byte[] k, byte[] c) { return libSodium().crypto_core_salsa20(out, in, k, c); } static long crypto_core_salsa2012_outputbytes() { return libSodium().crypto_core_salsa2012_outputbytes(); } static long crypto_core_salsa2012_inputbytes() { return libSodium().crypto_core_salsa2012_inputbytes(); } static long crypto_core_salsa2012_keybytes() { return libSodium().crypto_core_salsa2012_keybytes(); } static long crypto_core_salsa2012_constbytes() { return libSodium().crypto_core_salsa2012_constbytes(); } static int crypto_core_salsa2012(byte[] out, byte[] in, byte[] k, byte[] c) { return libSodium().crypto_core_salsa2012(out, in, k, c); } static long crypto_core_salsa208_outputbytes() { return libSodium().crypto_core_salsa208_outputbytes(); } static long crypto_core_salsa208_inputbytes() { return libSodium().crypto_core_salsa208_inputbytes(); } static long crypto_core_salsa208_keybytes() { return libSodium().crypto_core_salsa208_keybytes(); } static long crypto_core_salsa208_constbytes() { return libSodium().crypto_core_salsa208_constbytes(); } static int crypto_core_salsa208(byte[] out, byte[] in, byte[] k, byte[] c) { return libSodium().crypto_core_salsa208(out, in, k, c); } static long crypto_generichash_blake2b_bytes_min() { return libSodium().crypto_generichash_blake2b_bytes_min(); } static long crypto_generichash_blake2b_bytes_max() { return libSodium().crypto_generichash_blake2b_bytes_max(); } static long crypto_generichash_blake2b_bytes() { return libSodium().crypto_generichash_blake2b_bytes(); } static long crypto_generichash_blake2b_keybytes_min() { return libSodium().crypto_generichash_blake2b_keybytes_min(); } static long crypto_generichash_blake2b_keybytes_max() { return libSodium().crypto_generichash_blake2b_keybytes_max(); } static long crypto_generichash_blake2b_keybytes() { return libSodium().crypto_generichash_blake2b_keybytes(); } static long crypto_generichash_blake2b_saltbytes() { return libSodium().crypto_generichash_blake2b_saltbytes(); } static long crypto_generichash_blake2b_personalbytes() { return libSodium().crypto_generichash_blake2b_personalbytes(); } static long crypto_generichash_blake2b_statebytes() { return libSodium().crypto_generichash_blake2b_statebytes(); } static int crypto_generichash_blake2b(byte[] out, long outlen, byte[] in, long inlen, byte[] key, long keylen) { return libSodium().crypto_generichash_blake2b(out, outlen, in, inlen, key, keylen); } static int crypto_generichash_blake2b_salt_personal( byte[] out, long outlen, byte[] in, long inlen, byte[] key, long keylen, byte[] salt, byte[] personal) { return libSodium().crypto_generichash_blake2b_salt_personal(out, outlen, in, inlen, key, keylen, salt, personal); } static int crypto_generichash_blake2b_init(Pointer state, byte[] key, long keylen, long outlen) { return libSodium().crypto_generichash_blake2b_init(state, key, keylen, outlen); } static int crypto_generichash_blake2b_init_salt_personal( Pointer state, byte[] key, long keylen, long outlen, byte[] salt, byte[] personal) { return libSodium().crypto_generichash_blake2b_init_salt_personal(state, key, keylen, outlen, salt, personal); } static int crypto_generichash_blake2b_update(Pointer state, byte[] in, long inlen) { return libSodium().crypto_generichash_blake2b_update(state, in, inlen); } static int crypto_generichash_blake2b_final(Pointer state, byte[] out, long outlen) { return libSodium().crypto_generichash_blake2b_final(state, out, outlen); } static void crypto_generichash_blake2b_keygen(byte[] k) { libSodium().crypto_generichash_blake2b_keygen(k); } static long crypto_generichash_bytes_min() { return libSodium().crypto_generichash_bytes_min(); } static long crypto_generichash_bytes_max() { return libSodium().crypto_generichash_bytes_max(); } static long crypto_generichash_bytes() { return libSodium().crypto_generichash_bytes(); } static long crypto_generichash_keybytes_min() { return libSodium().crypto_generichash_keybytes_min(); } static long crypto_generichash_keybytes_max() { return libSodium().crypto_generichash_keybytes_max(); } static long crypto_generichash_keybytes() { return libSodium().crypto_generichash_keybytes(); } static String crypto_generichash_primitive() { return libSodium().crypto_generichash_primitive(); } static long crypto_generichash_statebytes() { return libSodium().crypto_generichash_statebytes(); } static int crypto_generichash(byte[] out, long outlen, byte[] in, long inlen, byte[] key, long keylen) { return libSodium().crypto_generichash(out, outlen, in, inlen, key, keylen); } static int crypto_generichash_init(Pointer state, byte[] key, long keylen, long outlen) { return libSodium().crypto_generichash_init(state, key, keylen, outlen); } static int crypto_generichash_update(Pointer state, byte[] in, long inlen) { return libSodium().crypto_generichash_update(state, in, inlen); } static int crypto_generichash_final(Pointer state, byte[] out, long outlen) { return libSodium().crypto_generichash_final(state, out, outlen); } static void crypto_generichash_keygen(byte[] k) { libSodium().crypto_generichash_keygen(k); } static long crypto_hash_bytes() { return libSodium().crypto_hash_bytes(); } static int crypto_hash(byte[] out, byte[] in, long inlen) { return libSodium().crypto_hash(out, in, inlen); } static String crypto_hash_primitive() { return libSodium().crypto_hash_primitive(); } static long crypto_kdf_blake2b_bytes_min() { return libSodium().crypto_kdf_blake2b_bytes_min(); } static long crypto_kdf_blake2b_bytes_max() { return libSodium().crypto_kdf_blake2b_bytes_max(); } static long crypto_kdf_blake2b_contextbytes() { return libSodium().crypto_kdf_blake2b_contextbytes(); } static long crypto_kdf_blake2b_keybytes() { return libSodium().crypto_kdf_blake2b_keybytes(); } static int crypto_kdf_blake2b_derive_from_key( byte[] subkey, long subkey_len, long subkey_id, byte[] ctx, Pointer key) { return libSodium().crypto_kdf_blake2b_derive_from_key(subkey, subkey_len, subkey_id, ctx, key); } static long crypto_kdf_bytes_min() { return libSodium().crypto_kdf_bytes_min(); } static long crypto_kdf_bytes_max() { return libSodium().crypto_kdf_bytes_max(); } static long crypto_kdf_contextbytes() { return libSodium().crypto_kdf_contextbytes(); } static long crypto_kdf_keybytes() { return libSodium().crypto_kdf_keybytes(); } static String crypto_kdf_primitive() { return libSodium().crypto_kdf_primitive(); } static int crypto_kdf_derive_from_key(byte[] subkey, long subkey_len, long subkey_id, byte[] ctx, Pointer key) { return libSodium().crypto_kdf_derive_from_key(subkey, subkey_len, subkey_id, ctx, key); } static void crypto_kdf_keygen(Pointer k) { libSodium().crypto_kdf_keygen(k); } static long crypto_kx_publickeybytes() { return libSodium().crypto_kx_publickeybytes(); } static long crypto_kx_secretkeybytes() { return libSodium().crypto_kx_secretkeybytes(); } static long crypto_kx_seedbytes() { return libSodium().crypto_kx_seedbytes(); } static long crypto_kx_sessionkeybytes() { return libSodium().crypto_kx_sessionkeybytes(); } static String crypto_kx_primitive() { return libSodium().crypto_kx_primitive(); } static int crypto_kx_seed_keypair(Pointer pk, Pointer sk, Pointer seed) { return libSodium().crypto_kx_seed_keypair(pk, sk, seed); } static int crypto_kx_keypair(Pointer pk, Pointer sk) { return libSodium().crypto_kx_keypair(pk, sk); } static int crypto_kx_client_session_keys( Pointer rx, Pointer tx, Pointer client_pk, Pointer client_sk, Pointer server_pk) { return libSodium().crypto_kx_client_session_keys(rx, tx, client_pk, client_sk, server_pk); } static int crypto_kx_server_session_keys( Pointer rx, Pointer tx, Pointer server_pk, Pointer server_sk, Pointer client_pk) { return libSodium().crypto_kx_server_session_keys(rx, tx, server_pk, server_sk, client_pk); } static long crypto_onetimeauth_poly1305_statebytes() { return libSodium().crypto_onetimeauth_poly1305_statebytes(); } static long crypto_onetimeauth_poly1305_bytes() { return libSodium().crypto_onetimeauth_poly1305_bytes(); } static long crypto_onetimeauth_poly1305_keybytes() { return libSodium().crypto_onetimeauth_poly1305_keybytes(); } static int crypto_onetimeauth_poly1305(byte[] out, byte[] in, long inlen, byte[] k) { return libSodium().crypto_onetimeauth_poly1305(out, in, inlen, k); } static int crypto_onetimeauth_poly1305_verify(byte[] h, byte[] in, long inlen, byte[] k) { return libSodium().crypto_onetimeauth_poly1305_verify(h, in, inlen, k); } static int crypto_onetimeauth_poly1305_init(Pointer state, byte[] key) { return libSodium().crypto_onetimeauth_poly1305_init(state, key); } static int crypto_onetimeauth_poly1305_update(Pointer state, byte[] in, long inlen) { return libSodium().crypto_onetimeauth_poly1305_update(state, in, inlen); } static int crypto_onetimeauth_poly1305_final(Pointer state, byte[] out) { return libSodium().crypto_onetimeauth_poly1305_final(state, out); } static void crypto_onetimeauth_poly1305_keygen(byte[] k) { libSodium().crypto_onetimeauth_poly1305_keygen(k); } static long crypto_onetimeauth_statebytes() { return libSodium().crypto_onetimeauth_statebytes(); } static long crypto_onetimeauth_bytes() { return libSodium().crypto_onetimeauth_bytes(); } static long crypto_onetimeauth_keybytes() { return libSodium().crypto_onetimeauth_keybytes(); } static String crypto_onetimeauth_primitive() { return libSodium().crypto_onetimeauth_primitive(); } static int crypto_onetimeauth(byte[] out, byte[] in, long inlen, byte[] k) { return libSodium().crypto_onetimeauth(out, in, inlen, k); } static int crypto_onetimeauth_verify(byte[] h, byte[] in, long inlen, byte[] k) { return libSodium().crypto_onetimeauth_verify(h, in, inlen, k); } static int crypto_onetimeauth_init(Pointer state, byte[] key) { return libSodium().crypto_onetimeauth_init(state, key); } static int crypto_onetimeauth_update(Pointer state, byte[] in, long inlen) { return libSodium().crypto_onetimeauth_update(state, in, inlen); } static int crypto_onetimeauth_final(Pointer state, byte[] out) { return libSodium().crypto_onetimeauth_final(state, out); } static void crypto_onetimeauth_keygen(byte[] k) { libSodium().crypto_onetimeauth_keygen(k); } static int crypto_pwhash_argon2i_alg_argon2i13() { return libSodium().crypto_pwhash_argon2i_alg_argon2i13(); } static long crypto_pwhash_argon2i_bytes_min() { return libSodium().crypto_pwhash_argon2i_bytes_min(); } static long crypto_pwhash_argon2i_bytes_max() { return libSodium().crypto_pwhash_argon2i_bytes_max(); } static long crypto_pwhash_argon2i_passwd_min() { return libSodium().crypto_pwhash_argon2i_passwd_min(); } static long crypto_pwhash_argon2i_passwd_max() { return libSodium().crypto_pwhash_argon2i_passwd_max(); } static long crypto_pwhash_argon2i_saltbytes() { return libSodium().crypto_pwhash_argon2i_saltbytes(); } static long crypto_pwhash_argon2i_strbytes() { return libSodium().crypto_pwhash_argon2i_strbytes(); } static String crypto_pwhash_argon2i_strprefix() { return libSodium().crypto_pwhash_argon2i_strprefix(); } static long crypto_pwhash_argon2i_opslimit_min() { return libSodium().crypto_pwhash_argon2i_opslimit_min(); } static long crypto_pwhash_argon2i_opslimit_max() { return libSodium().crypto_pwhash_argon2i_opslimit_max(); } static long crypto_pwhash_argon2i_memlimit_min() { return libSodium().crypto_pwhash_argon2i_memlimit_min(); } static long crypto_pwhash_argon2i_memlimit_max() { return libSodium().crypto_pwhash_argon2i_memlimit_max(); } static long crypto_pwhash_argon2i_opslimit_interactive() { return libSodium().crypto_pwhash_argon2i_opslimit_interactive(); } static long crypto_pwhash_argon2i_memlimit_interactive() { return libSodium().crypto_pwhash_argon2i_memlimit_interactive(); } static long crypto_pwhash_argon2i_opslimit_moderate() { return libSodium().crypto_pwhash_argon2i_opslimit_moderate(); } static long crypto_pwhash_argon2i_memlimit_moderate() { return libSodium().crypto_pwhash_argon2i_memlimit_moderate(); } static long crypto_pwhash_argon2i_opslimit_sensitive() { return libSodium().crypto_pwhash_argon2i_opslimit_sensitive(); } static long crypto_pwhash_argon2i_memlimit_sensitive() { return libSodium().crypto_pwhash_argon2i_memlimit_sensitive(); } static int crypto_pwhash_argon2i( byte[] out, long outlen, byte[] passwd, long passwdlen, byte[] salt, long opslimit, long memlimit, int alg) { return libSodium().crypto_pwhash_argon2i(out, outlen, passwd, passwdlen, salt, opslimit, memlimit, alg); } static int crypto_pwhash_argon2i_str(byte[] out, byte[] passwd, long passwdlen, long opslimit, long memlimit) { return libSodium().crypto_pwhash_argon2i_str(out, passwd, passwdlen, opslimit, memlimit); } static int crypto_pwhash_argon2i_str_verify(byte[] str, byte[] passwd, long passwdlen) { return libSodium().crypto_pwhash_argon2i_str_verify(str, passwd, passwdlen); } static int crypto_pwhash_argon2i_str_needs_rehash(byte[] str, long opslimit, long memlimit) { return libSodium().crypto_pwhash_argon2i_str_needs_rehash(str, opslimit, memlimit); } static int crypto_pwhash_argon2id_alg_argon2id13() { return libSodium().crypto_pwhash_argon2id_alg_argon2id13(); } static long crypto_pwhash_argon2id_bytes_min() { return libSodium().crypto_pwhash_argon2id_bytes_min(); } static long crypto_pwhash_argon2id_bytes_max() { return libSodium().crypto_pwhash_argon2id_bytes_max(); } static long crypto_pwhash_argon2id_passwd_min() { return libSodium().crypto_pwhash_argon2id_passwd_min(); } static long crypto_pwhash_argon2id_passwd_max() { return libSodium().crypto_pwhash_argon2id_passwd_max(); } static long crypto_pwhash_argon2id_saltbytes() { return libSodium().crypto_pwhash_argon2id_saltbytes(); } static long crypto_pwhash_argon2id_strbytes() { return libSodium().crypto_pwhash_argon2id_strbytes(); } static String crypto_pwhash_argon2id_strprefix() { return libSodium().crypto_pwhash_argon2id_strprefix(); } static long crypto_pwhash_argon2id_opslimit_min() { return libSodium().crypto_pwhash_argon2id_opslimit_min(); } static long crypto_pwhash_argon2id_opslimit_max() { return libSodium().crypto_pwhash_argon2id_opslimit_max(); } static long crypto_pwhash_argon2id_memlimit_min() { return libSodium().crypto_pwhash_argon2id_memlimit_min(); } static long crypto_pwhash_argon2id_memlimit_max() { return libSodium().crypto_pwhash_argon2id_memlimit_max(); } static long crypto_pwhash_argon2id_opslimit_interactive() { return libSodium().crypto_pwhash_argon2id_opslimit_interactive(); } static long crypto_pwhash_argon2id_memlimit_interactive() { return libSodium().crypto_pwhash_argon2id_memlimit_interactive(); } static long crypto_pwhash_argon2id_opslimit_moderate() { return libSodium().crypto_pwhash_argon2id_opslimit_moderate(); } static long crypto_pwhash_argon2id_memlimit_moderate() { return libSodium().crypto_pwhash_argon2id_memlimit_moderate(); } static long crypto_pwhash_argon2id_opslimit_sensitive() { return libSodium().crypto_pwhash_argon2id_opslimit_sensitive(); } static long crypto_pwhash_argon2id_memlimit_sensitive() { return libSodium().crypto_pwhash_argon2id_memlimit_sensitive(); } static int crypto_pwhash_argon2id( byte[] out, long outlen, byte[] passwd, long passwdlen, byte[] salt, long opslimit, long memlimit, int alg) { return libSodium().crypto_pwhash_argon2id(out, outlen, passwd, passwdlen, salt, opslimit, memlimit, alg); } static int crypto_pwhash_argon2id_str(byte[] out, byte[] passwd, long passwdlen, long opslimit, long memlimit) { return libSodium().crypto_pwhash_argon2id_str(out, passwd, passwdlen, opslimit, memlimit); } static int crypto_pwhash_argon2id_str_verify(byte[] str, byte[] passwd, long passwdlen) { return libSodium().crypto_pwhash_argon2id_str_verify(str, passwd, passwdlen); } static int crypto_pwhash_argon2id_str_needs_rehash(byte[] str, long opslimit, long memlimit) { return libSodium().crypto_pwhash_argon2id_str_needs_rehash(str, opslimit, memlimit); } static int crypto_pwhash_alg_argon2i13() { return libSodium().crypto_pwhash_alg_argon2i13(); } static int crypto_pwhash_alg_argon2id13() { return libSodium().crypto_pwhash_alg_argon2id13(); } static int crypto_pwhash_alg_default() { return libSodium().crypto_pwhash_alg_default(); } static long crypto_pwhash_bytes_min() { return libSodium().crypto_pwhash_bytes_min(); } static long crypto_pwhash_bytes_max() { return libSodium().crypto_pwhash_bytes_max(); } static long crypto_pwhash_passwd_min() { return libSodium().crypto_pwhash_passwd_min(); } static long crypto_pwhash_passwd_max() { return libSodium().crypto_pwhash_passwd_max(); } static long crypto_pwhash_saltbytes() { return libSodium().crypto_pwhash_saltbytes(); } static long crypto_pwhash_strbytes() { return libSodium().crypto_pwhash_strbytes(); } static String crypto_pwhash_strprefix() { return libSodium().crypto_pwhash_strprefix(); } static long crypto_pwhash_opslimit_min() { return libSodium().crypto_pwhash_opslimit_min(); } static long crypto_pwhash_opslimit_max() { return libSodium().crypto_pwhash_opslimit_max(); } static long crypto_pwhash_memlimit_min() { return libSodium().crypto_pwhash_memlimit_min(); } static long crypto_pwhash_memlimit_max() { return libSodium().crypto_pwhash_memlimit_max(); } static long crypto_pwhash_opslimit_interactive() { return libSodium().crypto_pwhash_opslimit_interactive(); } static long crypto_pwhash_memlimit_interactive() { return libSodium().crypto_pwhash_memlimit_interactive(); } static long crypto_pwhash_opslimit_moderate() { return libSodium().crypto_pwhash_opslimit_moderate(); } static long crypto_pwhash_memlimit_moderate() { return libSodium().crypto_pwhash_memlimit_moderate(); } static long crypto_pwhash_opslimit_sensitive() { return libSodium().crypto_pwhash_opslimit_sensitive(); } static long crypto_pwhash_memlimit_sensitive() { return libSodium().crypto_pwhash_memlimit_sensitive(); } static int crypto_pwhash( byte[] out, long outlen, byte[] passwd, long passwdlen, Pointer salt, long opslimit, long memlimit, int alg) { return libSodium().crypto_pwhash(out, outlen, passwd, passwdlen, salt, opslimit, memlimit, alg); } static int crypto_pwhash_str(byte[] out, byte[] passwd, long passwdlen, long opslimit, long memlimit) { return libSodium().crypto_pwhash_str(out, passwd, passwdlen, opslimit, memlimit); } static int crypto_pwhash_str_alg(byte[] out, byte[] passwd, long passwdlen, long opslimit, long memlimit, int alg) { return libSodium().crypto_pwhash_str_alg(out, passwd, passwdlen, opslimit, memlimit, alg); } static int crypto_pwhash_str_verify(Pointer str, byte[] passwd, long passwdlen) { return libSodium().crypto_pwhash_str_verify(str, passwd, passwdlen); } static int crypto_pwhash_str_needs_rehash(Pointer str, long opslimit, long memlimit) { return libSodium().crypto_pwhash_str_needs_rehash(str, opslimit, memlimit); } static String crypto_pwhash_primitive() { return libSodium().crypto_pwhash_primitive(); } static long crypto_scalarmult_curve25519_bytes() { return libSodium().crypto_scalarmult_curve25519_bytes(); } static long crypto_scalarmult_curve25519_scalarbytes() { return libSodium().crypto_scalarmult_curve25519_scalarbytes(); } static int crypto_scalarmult_curve25519(byte[] q, byte[] n, byte[] p) { return libSodium().crypto_scalarmult_curve25519(q, n, p); } static int crypto_scalarmult_curve25519_base(byte[] q, byte[] n) { return libSodium().crypto_scalarmult_curve25519_base(q, n); } static long crypto_scalarmult_bytes() { return libSodium().crypto_scalarmult_bytes(); } static long crypto_scalarmult_scalarbytes() { return libSodium().crypto_scalarmult_scalarbytes(); } static String crypto_scalarmult_primitive() { return libSodium().crypto_scalarmult_primitive(); } static int crypto_scalarmult_base(Pointer q, Pointer n) { return libSodium().crypto_scalarmult_base(q, n); } static int crypto_scalarmult(byte[] q, byte[] n, byte[] p) { return libSodium().crypto_scalarmult(q, n, p); } static long crypto_secretbox_xsalsa20poly1305_keybytes() { return libSodium().crypto_secretbox_xsalsa20poly1305_keybytes(); } static long crypto_secretbox_xsalsa20poly1305_noncebytes() { return libSodium().crypto_secretbox_xsalsa20poly1305_noncebytes(); } static long crypto_secretbox_xsalsa20poly1305_macbytes() { return libSodium().crypto_secretbox_xsalsa20poly1305_macbytes(); } static long crypto_secretbox_xsalsa20poly1305_messagebytes_max() { return libSodium().crypto_secretbox_xsalsa20poly1305_messagebytes_max(); } static int crypto_secretbox_xsalsa20poly1305(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) { return libSodium().crypto_secretbox_xsalsa20poly1305(c, m, mlen, n, k); } static int crypto_secretbox_xsalsa20poly1305_open(byte[] m, byte[] c, long clen, byte[] n, byte[] k) { return libSodium().crypto_secretbox_xsalsa20poly1305_open(m, c, clen, n, k); } static void crypto_secretbox_xsalsa20poly1305_keygen(byte[] k) { libSodium().crypto_secretbox_xsalsa20poly1305_keygen(k); } static long crypto_secretbox_xsalsa20poly1305_boxzerobytes() { return libSodium().crypto_secretbox_xsalsa20poly1305_boxzerobytes(); } static long crypto_secretbox_xsalsa20poly1305_zerobytes() { return libSodium().crypto_secretbox_xsalsa20poly1305_zerobytes(); } static long crypto_secretbox_keybytes() { return libSodium().crypto_secretbox_keybytes(); } static long crypto_secretbox_noncebytes() { return libSodium().crypto_secretbox_noncebytes(); } static long crypto_secretbox_macbytes() { return libSodium().crypto_secretbox_macbytes(); } static String crypto_secretbox_primitive() { return libSodium().crypto_secretbox_primitive(); } static long crypto_secretbox_messagebytes_max() { return libSodium().crypto_secretbox_messagebytes_max(); } static int crypto_secretbox_easy(byte[] c, byte[] m, long mlen, Pointer n, Pointer k) { return libSodium().crypto_secretbox_easy(c, m, mlen, n, k); } static int crypto_secretbox_open_easy(byte[] m, byte[] c, long clen, Pointer n, Pointer k) { return libSodium().crypto_secretbox_open_easy(m, c, clen, n, k); } static int crypto_secretbox_detached(byte[] c, byte[] mac, byte[] m, long mlen, Pointer n, Pointer k) { return libSodium().crypto_secretbox_detached(c, mac, m, mlen, n, k); } static int crypto_secretbox_open_detached(byte[] m, byte[] c, byte[] mac, long clen, Pointer n, Pointer k) { return libSodium().crypto_secretbox_open_detached(m, c, mac, clen, n, k); } static void crypto_secretbox_keygen(Pointer k) { libSodium().crypto_secretbox_keygen(k); } static long crypto_secretbox_zerobytes() { return libSodium().crypto_secretbox_zerobytes(); } static long crypto_secretbox_boxzerobytes() { return libSodium().crypto_secretbox_boxzerobytes(); } static int crypto_secretbox(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) { return libSodium().crypto_secretbox(c, m, mlen, n, k); } static int crypto_secretbox_open(byte[] m, byte[] c, long clen, byte[] n, byte[] k) { return libSodium().crypto_secretbox_open(m, c, clen, n, k); } static long crypto_stream_chacha20_keybytes() { return libSodium().crypto_stream_chacha20_keybytes(); } static long crypto_stream_chacha20_noncebytes() { return libSodium().crypto_stream_chacha20_noncebytes(); } static long crypto_stream_chacha20_messagebytes_max() { return libSodium().crypto_stream_chacha20_messagebytes_max(); } static int crypto_stream_chacha20(byte[] c, long clen, byte[] n, byte[] k) { return libSodium().crypto_stream_chacha20(c, clen, n, k); } static int crypto_stream_chacha20_xor(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) { return libSodium().crypto_stream_chacha20_xor(c, m, mlen, n, k); } static int crypto_stream_chacha20_xor_ic(byte[] c, byte[] m, long mlen, byte[] n, long ic, byte[] k) { return libSodium().crypto_stream_chacha20_xor_ic(c, m, mlen, n, ic, k); } static void crypto_stream_chacha20_keygen(byte[] k) { libSodium().crypto_stream_chacha20_keygen(k); } static long crypto_stream_chacha20_ietf_keybytes() { return libSodium().crypto_stream_chacha20_ietf_keybytes(); } static long crypto_stream_chacha20_ietf_noncebytes() { return libSodium().crypto_stream_chacha20_ietf_noncebytes(); } static long crypto_stream_chacha20_ietf_messagebytes_max() { return libSodium().crypto_stream_chacha20_ietf_messagebytes_max(); } static int crypto_stream_chacha20_ietf(byte[] c, long clen, byte[] n, byte[] k) { return libSodium().crypto_stream_chacha20_ietf(c, clen, n, k); } static int crypto_stream_chacha20_ietf_xor(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) { return libSodium().crypto_stream_chacha20_ietf_xor(c, m, mlen, n, k); } static int crypto_stream_chacha20_ietf_xor_ic(byte[] c, byte[] m, long mlen, byte[] n, int ic, byte[] k) { return libSodium().crypto_stream_chacha20_ietf_xor_ic(c, m, mlen, n, ic, k); } static void crypto_stream_chacha20_ietf_keygen(byte[] k) { libSodium().crypto_stream_chacha20_ietf_keygen(k); } static long crypto_secretstream_xchacha20poly1305_abytes() { return libSodium().crypto_secretstream_xchacha20poly1305_abytes(); } static long crypto_secretstream_xchacha20poly1305_headerbytes() { return libSodium().crypto_secretstream_xchacha20poly1305_headerbytes(); } static long crypto_secretstream_xchacha20poly1305_keybytes() { return libSodium().crypto_secretstream_xchacha20poly1305_keybytes(); } static long crypto_secretstream_xchacha20poly1305_messagebytes_max() { return libSodium().crypto_secretstream_xchacha20poly1305_messagebytes_max(); } static char crypto_secretstream_xchacha20poly1305_tag_message() { return libSodium().crypto_secretstream_xchacha20poly1305_tag_message(); } static char crypto_secretstream_xchacha20poly1305_tag_push() { return libSodium().crypto_secretstream_xchacha20poly1305_tag_push(); } static char crypto_secretstream_xchacha20poly1305_tag_rekey() { return libSodium().crypto_secretstream_xchacha20poly1305_tag_rekey(); } static char crypto_secretstream_xchacha20poly1305_tag_final() { return libSodium().crypto_secretstream_xchacha20poly1305_tag_final(); } static long crypto_secretstream_xchacha20poly1305_statebytes() { return libSodium().crypto_secretstream_xchacha20poly1305_statebytes(); } static void crypto_secretstream_xchacha20poly1305_keygen(Pointer k) { libSodium().crypto_secretstream_xchacha20poly1305_keygen(k); } static int crypto_secretstream_xchacha20poly1305_init_push(Pointer state, byte[] header, Pointer k) { return libSodium().crypto_secretstream_xchacha20poly1305_init_push(state, header, k); } static int crypto_secretstream_xchacha20poly1305_push( Pointer state, byte[] c, @Nullable LongLongByReference clen_p, byte[] m, long mlen, @Nullable byte[] ad, long adlen, byte tag) { return libSodium().crypto_secretstream_xchacha20poly1305_push(state, c, clen_p, m, mlen, ad, adlen, tag); } static int crypto_secretstream_xchacha20poly1305_init_pull(Pointer state, byte[] header, Pointer k) { return libSodium().crypto_secretstream_xchacha20poly1305_init_pull(state, header, k); } static int crypto_secretstream_xchacha20poly1305_pull( Pointer state, byte[] m, @Nullable LongLongByReference mlen_p, ByteByReference tag_p, byte[] c, long clen, @Nullable byte[] ad, long adlen) { return libSodium().crypto_secretstream_xchacha20poly1305_pull(state, m, mlen_p, tag_p, c, clen, ad, adlen); } static void crypto_secretstream_xchacha20poly1305_rekey(Pointer state) { libSodium().crypto_secretstream_xchacha20poly1305_rekey(state); } static long crypto_shorthash_siphash24_bytes() { return libSodium().crypto_shorthash_siphash24_bytes(); } static long crypto_shorthash_siphash24_keybytes() { return libSodium().crypto_shorthash_siphash24_keybytes(); } static int crypto_shorthash_siphash24(byte[] out, byte[] in, long inlen, byte[] k) { return libSodium().crypto_shorthash_siphash24(out, in, inlen, k); } static long crypto_shorthash_siphashx24_bytes() { return libSodium().crypto_shorthash_siphashx24_bytes(); } static long crypto_shorthash_siphashx24_keybytes() { return libSodium().crypto_shorthash_siphashx24_keybytes(); } static int crypto_shorthash_siphashx24(byte[] out, byte[] in, long inlen, byte[] k) { return libSodium().crypto_shorthash_siphashx24(out, in, inlen, k); } static long crypto_shorthash_bytes() { return libSodium().crypto_shorthash_bytes(); } static long crypto_shorthash_keybytes() { return libSodium().crypto_shorthash_keybytes(); } static String crypto_shorthash_primitive() { return libSodium().crypto_shorthash_primitive(); } static int crypto_shorthash(byte[] out, byte[] in, long inlen, byte[] k) { return libSodium().crypto_shorthash(out, in, inlen, k); } static void crypto_shorthash_keygen(byte[] k) { libSodium().crypto_shorthash_keygen(k); } static long crypto_sign_ed25519ph_statebytes() { return libSodium().crypto_sign_ed25519ph_statebytes(); } static long crypto_sign_ed25519_bytes() { return libSodium().crypto_sign_ed25519_bytes(); } static long crypto_sign_ed25519_seedbytes() { return libSodium().crypto_sign_ed25519_seedbytes(); } static long crypto_sign_ed25519_publickeybytes() { return libSodium().crypto_sign_ed25519_publickeybytes(); } static long crypto_sign_ed25519_secretkeybytes() { return libSodium().crypto_sign_ed25519_secretkeybytes(); } static long crypto_sign_ed25519_messagebytes_max() { return libSodium().crypto_sign_ed25519_messagebytes_max(); } static int crypto_sign_ed25519(byte[] sm, LongLongByReference smlen_p, byte[] m, long mlen, byte[] sk) { return libSodium().crypto_sign_ed25519(sm, smlen_p, m, mlen, sk); } static int crypto_sign_ed25519_open(byte[] m, LongLongByReference mlen_p, byte[] sm, long smlen, byte[] pk) { return libSodium().crypto_sign_ed25519_open(m, mlen_p, sm, smlen, pk); } static int crypto_sign_ed25519_detached(byte[] sig, LongLongByReference siglen_p, byte[] m, long mlen, byte[] sk) { return libSodium().crypto_sign_ed25519_detached(sig, siglen_p, m, mlen, sk); } static int crypto_sign_ed25519_verify_detached(byte[] sig, byte[] m, long mlen, byte[] pk) { return libSodium().crypto_sign_ed25519_verify_detached(sig, m, mlen, pk); } static int crypto_sign_ed25519_keypair(byte[] pk, byte[] sk) { return libSodium().crypto_sign_ed25519_keypair(pk, sk); } static int crypto_sign_ed25519_seed_keypair(byte[] pk, byte[] sk, byte[] seed) { return libSodium().crypto_sign_ed25519_seed_keypair(pk, sk, seed); } static int crypto_sign_ed25519_pk_to_curve25519(byte[] curve25519_pk, byte[] ed25519_pk) { return libSodium().crypto_sign_ed25519_pk_to_curve25519(curve25519_pk, ed25519_pk); } static int crypto_sign_ed25519_sk_to_curve25519(byte[] curve25519_sk, byte[] ed25519_sk) { return libSodium().crypto_sign_ed25519_sk_to_curve25519(curve25519_sk, ed25519_sk); } static int crypto_sign_ed25519_sk_to_seed(byte[] seed, byte[] sk) { return libSodium().crypto_sign_ed25519_sk_to_seed(seed, sk); } static int crypto_sign_ed25519_sk_to_pk(byte[] pk, byte[] sk) { return libSodium().crypto_sign_ed25519_sk_to_pk(pk, sk); } static int crypto_sign_ed25519ph_init(Pointer state) { return libSodium().crypto_sign_ed25519ph_init(state); } static int crypto_sign_ed25519ph_update(Pointer state, byte[] m, long mlen) { return libSodium().crypto_sign_ed25519ph_update(state, m, mlen); } static int crypto_sign_ed25519ph_final_create(Pointer state, byte[] sig, LongLongByReference siglen_p, byte[] sk) { return libSodium().crypto_sign_ed25519ph_final_create(state, sig, siglen_p, sk); } static int crypto_sign_ed25519ph_final_verify(Pointer state, byte[] sig, byte[] pk) { return libSodium().crypto_sign_ed25519ph_final_verify(state, sig, pk); } static long crypto_sign_statebytes() { return libSodium().crypto_sign_statebytes(); } static long crypto_sign_bytes() { return libSodium().crypto_sign_bytes(); } static long crypto_sign_seedbytes() { return libSodium().crypto_sign_seedbytes(); } static long crypto_sign_publickeybytes() { return libSodium().crypto_sign_publickeybytes(); } static long crypto_sign_secretkeybytes() { return libSodium().crypto_sign_secretkeybytes(); } static long crypto_sign_messagebytes_max() { return libSodium().crypto_sign_messagebytes_max(); } static String crypto_sign_primitive() { return libSodium().crypto_sign_primitive(); } static int crypto_sign_seed_keypair(byte[] pk, byte[] sk, byte[] seed) { return libSodium().crypto_sign_seed_keypair(pk, sk, seed); } static int crypto_sign_keypair(byte[] pk, byte[] sk) { return libSodium().crypto_sign_keypair(pk, sk); } static int crypto_sign(byte[] sm, LongLongByReference smlen_p, byte[] m, long mlen, byte[] sk) { return libSodium().crypto_sign(sm, smlen_p, m, mlen, sk); } static int crypto_sign_open(byte[] m, LongLongByReference mlen_p, byte[] sm, long smlen, byte[] pk) { return libSodium().crypto_sign_open(m, mlen_p, sm, smlen, pk); } static int crypto_sign_detached(byte[] sig, LongLongByReference siglen_p, byte[] m, long mlen, byte[] sk) { return libSodium().crypto_sign_detached(sig, siglen_p, m, mlen, sk); } static int crypto_sign_verify_detached(byte[] sig, byte[] m, long mlen, byte[] pk) { return libSodium().crypto_sign_verify_detached(sig, m, mlen, pk); } static int crypto_sign_init(Pointer state) { return libSodium().crypto_sign_init(state); } static int crypto_sign_update(Pointer state, byte[] m, long mlen) { return libSodium().crypto_sign_update(state, m, mlen); } static int crypto_sign_final_create(Pointer state, byte[] sig, LongLongByReference siglen_p, byte[] sk) { return libSodium().crypto_sign_final_create(state, sig, siglen_p, sk); } static int crypto_sign_final_verify(Pointer state, byte[] sig, byte[] pk) { return libSodium().crypto_sign_final_verify(state, sig, pk); } static long crypto_stream_keybytes() { return libSodium().crypto_stream_keybytes(); } static long crypto_stream_noncebytes() { return libSodium().crypto_stream_noncebytes(); } static long crypto_stream_messagebytes_max() { return libSodium().crypto_stream_messagebytes_max(); } static String crypto_stream_primitive() { return libSodium().crypto_stream_primitive(); } static int crypto_stream(byte[] c, long clen, byte[] n, byte[] k) { return libSodium().crypto_stream(c, clen, n, k); } static int crypto_stream_xor(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) { return libSodium().crypto_stream_xor(c, m, mlen, n, k); } static void crypto_stream_keygen(byte[] k) { libSodium().crypto_stream_keygen(k); } static long crypto_stream_salsa20_keybytes() { return libSodium().crypto_stream_salsa20_keybytes(); } static long crypto_stream_salsa20_noncebytes() { return libSodium().crypto_stream_salsa20_noncebytes(); } static long crypto_stream_salsa20_messagebytes_max() { return libSodium().crypto_stream_salsa20_messagebytes_max(); } static int crypto_stream_salsa20(byte[] c, long clen, byte[] n, byte[] k) { return libSodium().crypto_stream_salsa20(c, clen, n, k); } static int crypto_stream_salsa20_xor(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) { return libSodium().crypto_stream_salsa20_xor(c, m, mlen, n, k); } static int crypto_stream_salsa20_xor_ic(byte[] c, byte[] m, long mlen, byte[] n, long ic, byte[] k) { return libSodium().crypto_stream_salsa20_xor_ic(c, m, mlen, n, ic, k); } static void crypto_stream_salsa20_keygen(byte[] k) { libSodium().crypto_stream_salsa20_keygen(k); } static long crypto_verify_16_bytes() { return libSodium().crypto_verify_16_bytes(); } static int crypto_verify_16(byte[] x, byte[] y) { return libSodium().crypto_verify_16(x, y); } static long crypto_verify_32_bytes() { return libSodium().crypto_verify_32_bytes(); } static int crypto_verify_32(byte[] x, byte[] y) { return libSodium().crypto_verify_32(x, y); } static long crypto_verify_64_bytes() { return libSodium().crypto_verify_64_bytes(); } static int crypto_verify_64(byte[] x, byte[] y) { return libSodium().crypto_verify_64(x, y); } static String implementation_name() { return libSodium().implementation_name(); } static int random() { return libSodium().random(); } static void stir() { libSodium().stir(); } static int uniform(int upper_bound) { return libSodium().uniform(upper_bound); } static void buf(byte[] buf, long size) { libSodium().buf(buf, size); } static int close() { return libSodium().close(); } static long randombytes_seedbytes() { return libSodium().randombytes_seedbytes(); } static void randombytes_buf(Pointer buf, long size) { libSodium().randombytes_buf(buf, size); } static void randombytes_buf_deterministic(byte[] buf, long size, byte[] seed) { libSodium().randombytes_buf_deterministic(buf, size, seed); } static int randombytes_random() { return libSodium().randombytes_random(); } static int randombytes_uniform(int upper_bound) { return libSodium().randombytes_uniform(upper_bound); } static void randombytes_stir() { libSodium().randombytes_stir(); } static int randombytes_close() { return libSodium().randombytes_close(); } static int randombytes_set_implementation(Pointer impl) { return libSodium().randombytes_set_implementation(impl); } static String randombytes_implementation_name() { return libSodium().randombytes_implementation_name(); } static void randombytes(byte[] buf, long buf_len) { libSodium().randombytes(buf, buf_len); } static int sodium_runtime_has_neon() { return libSodium().sodium_runtime_has_neon(); } static int sodium_runtime_has_sse2() { return libSodium().sodium_runtime_has_sse2(); } static int sodium_runtime_has_sse3() { return libSodium().sodium_runtime_has_sse3(); } static int sodium_runtime_has_ssse3() { return libSodium().sodium_runtime_has_ssse3(); } static int sodium_runtime_has_sse41() { return libSodium().sodium_runtime_has_sse41(); } static int sodium_runtime_has_avx() { return libSodium().sodium_runtime_has_avx(); } static int sodium_runtime_has_avx2() { return libSodium().sodium_runtime_has_avx2(); } static int sodium_runtime_has_avx512f() { return libSodium().sodium_runtime_has_avx512f(); } static int sodium_runtime_has_pclmul() { return libSodium().sodium_runtime_has_pclmul(); } static int sodium_runtime_has_aesni() { return libSodium().sodium_runtime_has_aesni(); } static int sodium_runtime_has_rdrand() { return libSodium().sodium_runtime_has_rdrand(); } static void sodium_memzero(Pointer pnt, long len) { libSodium().sodium_memzero(pnt, len); } // static void sodium_stackzero(long len) { // libSodium().sodium_stackzero(len); // } static int sodium_memcmp(Pointer b1_, Pointer b2_, long len) { return libSodium().sodium_memcmp(b1_, b2_, len); } static int sodium_compare(Pointer b1_, Pointer b2_, long len) { return libSodium().sodium_compare(b1_, b2_, len); } static int sodium_is_zero(Pointer n, long nlen) { return libSodium().sodium_is_zero(n, nlen); } static void sodium_increment(Pointer n, long nlen) { libSodium().sodium_increment(n, nlen); } static void sodium_add(Pointer a, Pointer b, long len) { libSodium().sodium_add(a, b, len); } // FIXME: not available due to issue with LibSodium#sodium_bin2hex // static byte[] sodium_bin2hex(byte[] hex, long hex_maxlen, byte[] bin, long bin_len) { // return libSodium().sodium_bin2hex(hex, hex_maxlen, bin, bin_len); // } static int sodium_hex2bin( byte[] bin, long bin_maxlen, byte[] hex, long hex_len, byte[] ignore, LongLongByReference bin_len, Pointer hex_end) { return libSodium().sodium_hex2bin(bin, bin_maxlen, hex, hex_len, ignore, bin_len, hex_end); } static long sodium_base64_encoded_len(long bin_len, int variant) { return libSodium().sodium_base64_encoded_len(bin_len, variant); } // FIXME: not available due to issue with LibSodium#sodium_bin2base64 // static byte[] sodium_bin2base64(byte[] b64, long b64_maxlen, byte[] bin, long bin_len, int variant) { // return libSodium().sodium_bin2base64(b64, b64_maxlen, bin, bin_len, variant); // } static int sodium_base642bin( byte[] bin, long bin_maxlen, byte[] b64, long b64_len, byte[] ignore, LongLongByReference bin_len, Pointer b64_end, int variant) { return libSodium().sodium_base642bin(bin, bin_maxlen, b64, b64_len, ignore, bin_len, b64_end, variant); } static int sodium_mlock(Pointer addr, long len) { return libSodium().sodium_mlock(addr, len); } static int sodium_munlock(Pointer addr, long len) { return libSodium().sodium_munlock(addr, len); } static Pointer sodium_malloc(long size) { return libSodium().sodium_malloc(size); } static Pointer sodium_allocarray(long count, long size) { return libSodium().sodium_allocarray(count, size); } static void sodium_free(Pointer ptr) { libSodium().sodium_free(ptr); } static int sodium_mprotect_noaccess(Pointer ptr) { return libSodium().sodium_mprotect_noaccess(ptr); } static int sodium_mprotect_readonly(Pointer ptr) { return libSodium().sodium_mprotect_readonly(ptr); } static int sodium_mprotect_readwrite(Pointer ptr) { return libSodium().sodium_mprotect_readwrite(ptr); } static int sodium_pad( LongLongByReference padded_buflen_p, byte[] buf, long unpadded_buflen, long blocksize, long max_buflen) { return libSodium().sodium_pad(padded_buflen_p, buf, unpadded_buflen, blocksize, max_buflen); } static int sodium_unpad(LongLongByReference unpadded_buflen_p, byte[] buf, long padded_buflen, long blocksize) { return libSodium().sodium_unpad(unpadded_buflen_p, buf, padded_buflen, blocksize); } static long crypto_stream_xchacha20_keybytes() { return libSodium().crypto_stream_xchacha20_keybytes(); } static long crypto_stream_xchacha20_noncebytes() { return libSodium().crypto_stream_xchacha20_noncebytes(); } static long crypto_stream_xchacha20_messagebytes_max() { return libSodium().crypto_stream_xchacha20_messagebytes_max(); } static int crypto_stream_xchacha20(byte[] c, long clen, byte[] n, byte[] k) { return libSodium().crypto_stream_xchacha20(c, clen, n, k); } static int crypto_stream_xchacha20_xor(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) { return libSodium().crypto_stream_xchacha20_xor(c, m, mlen, n, k); } static int crypto_stream_xchacha20_xor_ic(byte[] c, byte[] m, long mlen, byte[] n, long ic, byte[] k) { return libSodium().crypto_stream_xchacha20_xor_ic(c, m, mlen, n, ic, k); } static void crypto_stream_xchacha20_keygen(byte[] k) { libSodium().crypto_stream_xchacha20_keygen(k); } static long crypto_box_curve25519xchacha20poly1305_seedbytes() { return libSodium().crypto_box_curve25519xchacha20poly1305_seedbytes(); } static long crypto_box_curve25519xchacha20poly1305_publickeybytes() { return libSodium().crypto_box_curve25519xchacha20poly1305_publickeybytes(); } static long crypto_box_curve25519xchacha20poly1305_secretkeybytes() { return libSodium().crypto_box_curve25519xchacha20poly1305_secretkeybytes(); } static long crypto_box_curve25519xchacha20poly1305_beforenmbytes() { return libSodium().crypto_box_curve25519xchacha20poly1305_beforenmbytes(); } static long crypto_box_curve25519xchacha20poly1305_noncebytes() { return libSodium().crypto_box_curve25519xchacha20poly1305_noncebytes(); } static long crypto_box_curve25519xchacha20poly1305_macbytes() { return libSodium().crypto_box_curve25519xchacha20poly1305_macbytes(); } static long crypto_box_curve25519xchacha20poly1305_messagebytes_max() { return libSodium().crypto_box_curve25519xchacha20poly1305_messagebytes_max(); } static int crypto_box_curve25519xchacha20poly1305_seed_keypair(byte[] pk, byte[] sk, byte[] seed) { return libSodium().crypto_box_curve25519xchacha20poly1305_seed_keypair(pk, sk, seed); } static int crypto_box_curve25519xchacha20poly1305_keypair(byte[] pk, byte[] sk) { return libSodium().crypto_box_curve25519xchacha20poly1305_keypair(pk, sk); } static int crypto_box_curve25519xchacha20poly1305_easy( byte[] c, byte[] m, long mlen, byte[] n, byte[] pk, byte[] sk) { return libSodium().crypto_box_curve25519xchacha20poly1305_easy(c, m, mlen, n, pk, sk); } static int crypto_box_curve25519xchacha20poly1305_open_easy( byte[] m, byte[] c, long clen, byte[] n, byte[] pk, byte[] sk) { return libSodium().crypto_box_curve25519xchacha20poly1305_open_easy(m, c, clen, n, pk, sk); } static int crypto_box_curve25519xchacha20poly1305_detached( byte[] c, byte[] mac, byte[] m, long mlen, byte[] n, byte[] pk, byte[] sk) { return libSodium().crypto_box_curve25519xchacha20poly1305_detached(c, mac, m, mlen, n, pk, sk); } static int crypto_box_curve25519xchacha20poly1305_open_detached( byte[] m, byte[] c, byte[] mac, long clen, byte[] n, byte[] pk, byte[] sk) { return libSodium().crypto_box_curve25519xchacha20poly1305_open_detached(m, c, mac, clen, n, pk, sk); } static int crypto_box_curve25519xchacha20poly1305_beforenm(Pointer k, byte[] pk, byte[] sk) { return libSodium().crypto_box_curve25519xchacha20poly1305_beforenm(k, pk, sk); } static int crypto_box_curve25519xchacha20poly1305_easy_afternm(byte[] c, byte[] m, long mlen, byte[] n, Pointer k) { return libSodium().crypto_box_curve25519xchacha20poly1305_easy_afternm(c, m, mlen, n, k); } static int crypto_box_curve25519xchacha20poly1305_open_easy_afternm( byte[] m, byte[] c, long clen, byte[] n, Pointer k) { return libSodium().crypto_box_curve25519xchacha20poly1305_open_easy_afternm(m, c, clen, n, k); } static int crypto_box_curve25519xchacha20poly1305_detached_afternm( byte[] c, byte[] mac, byte[] m, long mlen, byte[] n, Pointer k) { return libSodium().crypto_box_curve25519xchacha20poly1305_detached_afternm(c, mac, m, mlen, n, k); } static int crypto_box_curve25519xchacha20poly1305_open_detached_afternm( byte[] m, byte[] c, byte[] mac, long clen, byte[] n, Pointer k) { return libSodium().crypto_box_curve25519xchacha20poly1305_open_detached_afternm(m, c, mac, clen, n, k); } static long crypto_box_curve25519xchacha20poly1305_sealbytes() { return libSodium().crypto_box_curve25519xchacha20poly1305_sealbytes(); } static int crypto_box_curve25519xchacha20poly1305_seal(byte[] c, byte[] m, long mlen, byte[] pk) { return libSodium().crypto_box_curve25519xchacha20poly1305_seal(c, m, mlen, pk); } static int crypto_box_curve25519xchacha20poly1305_seal_open(byte[] m, byte[] c, long clen, byte[] pk, byte[] sk) { return libSodium().crypto_box_curve25519xchacha20poly1305_seal_open(m, c, clen, pk, sk); } static long crypto_core_ed25519_bytes() { return libSodium().crypto_core_ed25519_bytes(); } static long crypto_core_ed25519_uniformbytes() { return libSodium().crypto_core_ed25519_uniformbytes(); } static int crypto_core_ed25519_is_valid_point(byte[] p) { return libSodium().crypto_core_ed25519_is_valid_point(p); } static int crypto_core_ed25519_add(byte[] r, byte[] p, byte[] q) { return libSodium().crypto_core_ed25519_add(r, p, q); } static int crypto_core_ed25519_sub(byte[] r, byte[] p, byte[] q) { return libSodium().crypto_core_ed25519_sub(r, p, q); } static int crypto_core_ed25519_from_uniform(byte[] p, byte[] r) { return libSodium().crypto_core_ed25519_from_uniform(p, r); } static long crypto_scalarmult_ed25519_bytes() { return libSodium().crypto_scalarmult_ed25519_bytes(); } static long crypto_scalarmult_ed25519_scalarbytes() { return libSodium().crypto_scalarmult_ed25519_scalarbytes(); } static int crypto_scalarmult_ed25519(byte[] q, byte[] n, byte[] p) { return libSodium().crypto_scalarmult_ed25519(q, n, p); } static int crypto_scalarmult_ed25519_base(byte[] q, byte[] n) { return libSodium().crypto_scalarmult_ed25519_base(q, n); } static long crypto_secretbox_xchacha20poly1305_keybytes() { return libSodium().crypto_secretbox_xchacha20poly1305_keybytes(); } static long crypto_secretbox_xchacha20poly1305_noncebytes() { return libSodium().crypto_secretbox_xchacha20poly1305_noncebytes(); } static long crypto_secretbox_xchacha20poly1305_macbytes() { return libSodium().crypto_secretbox_xchacha20poly1305_macbytes(); } static long crypto_secretbox_xchacha20poly1305_messagebytes_max() { return libSodium().crypto_secretbox_xchacha20poly1305_messagebytes_max(); } static int crypto_secretbox_xchacha20poly1305_easy(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) { return libSodium().crypto_secretbox_xchacha20poly1305_easy(c, m, mlen, n, k); } static int crypto_secretbox_xchacha20poly1305_open_easy(byte[] m, byte[] c, long clen, byte[] n, byte[] k) { return libSodium().crypto_secretbox_xchacha20poly1305_open_easy(m, c, clen, n, k); } static int crypto_secretbox_xchacha20poly1305_detached( byte[] c, byte[] mac, byte[] m, long mlen, byte[] n, byte[] k) { return libSodium().crypto_secretbox_xchacha20poly1305_detached(c, mac, m, mlen, n, k); } static int crypto_secretbox_xchacha20poly1305_open_detached( byte[] m, byte[] c, byte[] mac, long clen, byte[] n, byte[] k) { return libSodium().crypto_secretbox_xchacha20poly1305_open_detached(m, c, mac, clen, n, k); } static long crypto_pwhash_scryptsalsa208sha256_bytes_min() { return libSodium().crypto_pwhash_scryptsalsa208sha256_bytes_min(); } static long crypto_pwhash_scryptsalsa208sha256_bytes_max() { return libSodium().crypto_pwhash_scryptsalsa208sha256_bytes_max(); } static long crypto_pwhash_scryptsalsa208sha256_passwd_min() { return libSodium().crypto_pwhash_scryptsalsa208sha256_passwd_min(); } static long crypto_pwhash_scryptsalsa208sha256_passwd_max() { return libSodium().crypto_pwhash_scryptsalsa208sha256_passwd_max(); } static long crypto_pwhash_scryptsalsa208sha256_saltbytes() { return libSodium().crypto_pwhash_scryptsalsa208sha256_saltbytes(); } static long crypto_pwhash_scryptsalsa208sha256_strbytes() { return libSodium().crypto_pwhash_scryptsalsa208sha256_strbytes(); } static String crypto_pwhash_scryptsalsa208sha256_strprefix() { return libSodium().crypto_pwhash_scryptsalsa208sha256_strprefix(); } static long crypto_pwhash_scryptsalsa208sha256_opslimit_min() { return libSodium().crypto_pwhash_scryptsalsa208sha256_opslimit_min(); } static long crypto_pwhash_scryptsalsa208sha256_opslimit_max() { return libSodium().crypto_pwhash_scryptsalsa208sha256_opslimit_max(); } static long crypto_pwhash_scryptsalsa208sha256_memlimit_min() { return libSodium().crypto_pwhash_scryptsalsa208sha256_memlimit_min(); } static long crypto_pwhash_scryptsalsa208sha256_memlimit_max() { return libSodium().crypto_pwhash_scryptsalsa208sha256_memlimit_max(); } static long crypto_pwhash_scryptsalsa208sha256_opslimit_interactive() { return libSodium().crypto_pwhash_scryptsalsa208sha256_opslimit_interactive(); } static long crypto_pwhash_scryptsalsa208sha256_memlimit_interactive() { return libSodium().crypto_pwhash_scryptsalsa208sha256_memlimit_interactive(); } static long crypto_pwhash_scryptsalsa208sha256_opslimit_sensitive() { return libSodium().crypto_pwhash_scryptsalsa208sha256_opslimit_sensitive(); } static long crypto_pwhash_scryptsalsa208sha256_memlimit_sensitive() { return libSodium().crypto_pwhash_scryptsalsa208sha256_memlimit_sensitive(); } static int crypto_pwhash_scryptsalsa208sha256( byte[] out, long outlen, byte[] passwd, long passwdlen, byte[] salt, long opslimit, long memlimit) { return libSodium().crypto_pwhash_scryptsalsa208sha256(out, outlen, passwd, passwdlen, salt, opslimit, memlimit); } static int crypto_pwhash_scryptsalsa208sha256_str( byte[] out, byte[] passwd, long passwdlen, long opslimit, long memlimit) { return libSodium().crypto_pwhash_scryptsalsa208sha256_str(out, passwd, passwdlen, opslimit, memlimit); } static int crypto_pwhash_scryptsalsa208sha256_str_verify(byte[] str, byte[] passwd, long passwdlen) { return libSodium().crypto_pwhash_scryptsalsa208sha256_str_verify(str, passwd, passwdlen); } static int crypto_pwhash_scryptsalsa208sha256_ll( byte[] passwd, long passwdlen, byte[] salt, long saltlen, long N, int r, int p, byte[] buf, long buflen) { return libSodium().crypto_pwhash_scryptsalsa208sha256_ll(passwd, passwdlen, salt, saltlen, N, r, p, buf, buflen); } static int crypto_pwhash_scryptsalsa208sha256_str_needs_rehash(byte[] str, long opslimit, long memlimit) { return libSodium().crypto_pwhash_scryptsalsa208sha256_str_needs_rehash(str, opslimit, memlimit); } static long crypto_stream_salsa2012_keybytes() { return libSodium().crypto_stream_salsa2012_keybytes(); } static long crypto_stream_salsa2012_noncebytes() { return libSodium().crypto_stream_salsa2012_noncebytes(); } static long crypto_stream_salsa2012_messagebytes_max() { return libSodium().crypto_stream_salsa2012_messagebytes_max(); } static int crypto_stream_salsa2012(byte[] c, long clen, byte[] n, byte[] k) { return libSodium().crypto_stream_salsa2012(c, clen, n, k); } static int crypto_stream_salsa2012_xor(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) { return libSodium().crypto_stream_salsa2012_xor(c, m, mlen, n, k); } static void crypto_stream_salsa2012_keygen(byte[] k) { libSodium().crypto_stream_salsa2012_keygen(k); } static long crypto_stream_salsa208_keybytes() { return libSodium().crypto_stream_salsa208_keybytes(); } static long crypto_stream_salsa208_noncebytes() { return libSodium().crypto_stream_salsa208_noncebytes(); } static long crypto_stream_salsa208_messagebytes_max() { return libSodium().crypto_stream_salsa208_messagebytes_max(); } static int crypto_stream_salsa208(byte[] c, long clen, byte[] n, byte[] k) { return libSodium().crypto_stream_salsa208(c, clen, n, k); } static int crypto_stream_salsa208_xor(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) { return libSodium().crypto_stream_salsa208_xor(c, m, mlen, n, k); } static void crypto_stream_salsa208_keygen(byte[] k) { libSodium().crypto_stream_salsa208_keygen(k); } } cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/SodiumException.java000066400000000000000000000016131341750772100307410ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; /** * An exception that is thrown when an error occurs using the native sodium library. */ public final class SodiumException extends RuntimeException { /** * @param message The exception message. */ public SodiumException(String message) { super(message); } } cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/SodiumVersion.java000066400000000000000000000027461341750772100304400ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; /** * Details of a sodium native library version. */ public final class SodiumVersion implements Comparable { private final int major; private final int minor; private final String name; SodiumVersion(int major, int minor, String name) { this.major = major; this.minor = minor; this.name = name; } /** * The major version number. * * @return The major version number. */ public int major() { return major; } /** * The minor version number. * * @return The minor version number. */ public int minor() { return minor; } @Override public String toString() { return name; } @Override public int compareTo(SodiumVersion other) { if (this.major == other.major) { return Integer.compare(this.minor, other.minor); } return Integer.compare(this.major, other.major); } } cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/XChaCha20Poly1305.java000066400000000000000000000722201341750772100304020ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import net.consensys.cava.bytes.Bytes; import javax.annotation.Nullable; import javax.security.auth.Destroyable; import jnr.ffi.Pointer; import jnr.ffi.byref.ByteByReference; import jnr.ffi.byref.LongLongByReference; // Documentation copied under the ISC License, from // https://github.com/jedisct1/libsodium-doc/blob/424b7480562c2e063bc8c52c452ef891621c8480/secret-key_cryptography/xchacha20-poly1305_construction.md /** * Authenticated Encryption with Additional Data using XChaCha20-Poly1305. * *

* The XChaCha20-Poly1305 construction can safely encrypt a practically unlimited number of messages with the same key, * without any practical limit to the size of a message (up to ~ 2^64 bytes). * *

* As an alternative to counters, its large nonce size (192-bit) allows random nonces to be safely used. * *

* For this reason, and if interoperability with other libraries is not a concern, this is the recommended AEAD * construction. * *

* This class depends 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'. */ public final class XChaCha20Poly1305 { private XChaCha20Poly1305() {} private static final byte[] EMPTY_BYTES = new byte[0]; /** * Check if Sodium and the XChaCha20Poly1305 algorithm is available. * *

* XChaCha20Poly1305 is supported in sodium native library version >= 10.0.12. * * @return {@code true} if Sodium and the XChaCha20Poly1305 algorithm is available. */ public static boolean isAvailable() { try { return Sodium.supportsVersion(Sodium.VERSION_10_0_12); } catch (UnsatisfiedLinkError e) { return false; } } private static void assertAvailable() { if (!isAvailable()) { throw new UnsupportedOperationException( "Sodium XChaCha20Poly1305 is not available (requires sodium native library >= 10.0.12)"); } } /** * Check if Sodium and the XChaCha20Poly1305 secret stream algorithm is available. * *

* XChaCha20Poly1305 secret stream is supported in sodium native library version >= 10.0.14. * * @return {@code true} if Sodium and the XChaCha20Poly1305 secret stream algorithm is available. */ public static boolean isSecretStreamAvailable() { try { return Sodium.supportsVersion(Sodium.VERSION_10_0_14); } catch (UnsatisfiedLinkError e) { return false; } } private static void assertSecretStreamAvailable() { if (!isSecretStreamAvailable()) { throw new UnsupportedOperationException( "Sodium XChaCha20Poly1305 secret stream is not available (requires sodium native library >= 10.0.14)"); } } /** * A XChaCha20-Poly1305 key. */ public static final class Key implements Destroyable { @Nullable private Pointer ptr; private final int length; private Key(Pointer ptr, int length) { this.ptr = ptr; this.length = length; } @Override protected void finalize() { destroy(); } @Override public void destroy() { if (ptr != null) { Pointer p = ptr; ptr = null; Sodium.sodium_free(p); } } @Override public boolean isDestroyed() { return ptr == null; } /** * Create a {@link Key} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the key. * @return A key, based on the supplied bytes. */ public static Key fromBytes(Bytes bytes) { return fromBytes(bytes.toArrayUnsafe()); } /** * Create a {@link Key} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the key. * @return A key, based on the supplied bytes. * @throws UnsupportedOperationException If XChaCha20Poly1305 support is not available. */ public static Key fromBytes(byte[] bytes) { assertAvailable(); if (bytes.length != Sodium.crypto_aead_xchacha20poly1305_ietf_keybytes()) { throw new IllegalArgumentException( "key must be " + Sodium.crypto_aead_xchacha20poly1305_ietf_keybytes() + " bytes, got " + bytes.length); } return Sodium.dup(bytes, Key::new); } /** * Obtain the length of the key in bytes (32). * * @return The length of the key in bytes (32). * @throws UnsupportedOperationException If XChaCha20Poly1305 support is not available. */ public static int length() { assertAvailable(); long keybytes = Sodium.crypto_aead_xchacha20poly1305_ietf_keybytes(); if (keybytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_aead_xchacha20poly1305_ietf_keybytes: " + keybytes + " is too large"); } return (int) keybytes; } /** * Generate a new key using a random generator. * * @return A randomly generated key. * @throws UnsupportedOperationException If XChaCha20Poly1305 support is not available. */ public static Key random() { assertAvailable(); int length = length(); Pointer ptr = Sodium.malloc(length); try { // When support for 10.0.11 is dropped, use this instead //Sodium.crypto_aead_xchacha20poly1305_ietf_keygen(ptr); Sodium.randombytes_buf(ptr, length); return new Key(ptr, length); } catch (Throwable e) { Sodium.sodium_free(ptr); throw e; } } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof Key)) { return false; } checkState(ptr != null, "Key has been destroyed"); Key other = (Key) obj; return other.ptr != null && Sodium.sodium_memcmp(this.ptr, other.ptr, length) == 0; } @Override public int hashCode() { checkState(ptr != null, "Key has been destroyed"); return Sodium.hashCode(ptr, length); } /** * @return The bytes of this key. */ public Bytes bytes() { return Bytes.wrap(bytesArray()); } /** * @return The bytes of this key. */ public byte[] bytesArray() { checkState(ptr != null, "Key has been destroyed"); return Sodium.reify(ptr, length); } } /** * A XChaCha20-Poly1305 nonce. */ public static final class Nonce { private final Pointer ptr; private final int length; private Nonce(Pointer ptr, int length) { this.ptr = ptr; this.length = length; } @Override protected void finalize() { Sodium.sodium_free(ptr); } /** * Create a {@link Nonce} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the nonce. * @return A nonce, based on these bytes. */ public static Nonce fromBytes(Bytes bytes) { return fromBytes(bytes.toArrayUnsafe()); } /** * Create a {@link Nonce} from an array of bytes. * *

* The byte array must be of length {@link #length()}. * * @param bytes The bytes for the nonce. * @return A nonce, based on these bytes. * @throws UnsupportedOperationException If XChaCha20Poly1305 support is not available. */ public static Nonce fromBytes(byte[] bytes) { assertAvailable(); if (bytes.length != Sodium.crypto_aead_xchacha20poly1305_ietf_npubbytes()) { throw new IllegalArgumentException( "nonce must be " + Sodium.crypto_aead_xchacha20poly1305_ietf_npubbytes() + " bytes, got " + bytes.length); } return Sodium.dup(bytes, Nonce::new); } /** * Obtain the length of the nonce in bytes (24). * * @return The length of the nonce in bytes (24). * @throws UnsupportedOperationException If XChaCha20Poly1305 support is not available. */ public static int length() { assertAvailable(); long npubbytes = Sodium.crypto_aead_xchacha20poly1305_ietf_npubbytes(); if (npubbytes > Integer.MAX_VALUE) { throw new SodiumException("crypto_aead_xchacha20poly1305_ietf_npubbytes: " + npubbytes + " is too large"); } return (int) npubbytes; } /** * Generate a new {@link Nonce} using a random generator. * * @return A randomly generated nonce. * @throws UnsupportedOperationException If XChaCha20Poly1305 support is not available. */ public static Nonce random() { assertAvailable(); return Sodium.randomBytes(length(), Nonce::new); } /** * Increment this nonce. * *

* Note that this is not synchronized. If multiple threads are creating encrypted messages and incrementing this * nonce, then external synchronization is required to ensure no two encrypt operations use the same nonce. * * @return A new {@link Nonce}. */ public Nonce increment() { return Sodium.dupAndIncrement(ptr, length, Nonce::new); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof Nonce)) { return false; } Nonce other = (Nonce) obj; return Sodium.sodium_memcmp(this.ptr, other.ptr, length) == 0; } @Override public int hashCode() { return Sodium.hashCode(ptr, length); } /** * @return The bytes of this nonce. */ public Bytes bytes() { return Bytes.wrap(bytesArray()); } /** * @return The bytes of this nonce. */ public byte[] bytesArray() { return Sodium.reify(ptr, length); } } /** * Encrypt a message for a given key. * * @param message The message to encrypt. * @param key The key to encrypt for. * @param nonce A unique nonce. * @return The encrypted data. */ public static Bytes encrypt(Bytes message, Key key, Nonce nonce) { return Bytes.wrap(encrypt(message.toArrayUnsafe(), key, nonce)); } /** * Encrypt a message for a given key. * * @param message The message to encrypt. * @param key The key to encrypt for. * @param nonce A unique nonce. * @return The encrypted data. */ public static byte[] encrypt(byte[] message, Key key, Nonce nonce) { return encrypt(message, EMPTY_BYTES, key, nonce); } /** * Encrypt a message for a given key. * * @param message The message to encrypt. * @param data Extra non-confidential data that will be included with the encrypted payload. * @param key The key to encrypt for. * @param nonce A unique nonce. * @return The encrypted data. */ public static Bytes encrypt(Bytes message, Bytes data, Key key, Nonce nonce) { return Bytes.wrap(encrypt(message.toArrayUnsafe(), data.toArrayUnsafe(), key, nonce)); } /** * Encrypt a message for a given key. * * @param message The message to encrypt. * @param data Extra non-confidential data that will be included with the encrypted payload. * @param key The key to encrypt for. * @param nonce A unique nonce. * @return The encrypted data. * @throws UnsupportedOperationException If XChaCha20Poly1305 support is not available. */ public static byte[] encrypt(byte[] message, byte[] data, Key key, Nonce nonce) { assertAvailable(); checkArgument(key.ptr != null, "Key has been destroyed"); byte[] cipherText = new byte[maxCypherTextLength(message)]; LongLongByReference cipherTextLen = new LongLongByReference(); int rc = Sodium.crypto_aead_xchacha20poly1305_ietf_encrypt( cipherText, cipherTextLen, message, message.length, data, data.length, null, nonce.ptr, key.ptr); if (rc != 0) { throw new SodiumException("crypto_aead_xchacha20poly1305_ietf_encrypt: failed with result " + rc); } return maybeSliceResult(cipherText, cipherTextLen, "crypto_aead_xchacha20poly1305_ietf_encrypt"); } private static int maxCypherTextLength(byte[] message) { long abytes = Sodium.crypto_aead_xchacha20poly1305_ietf_abytes(); if (abytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_aead_xchacha20poly1305_ietf_abytes: " + abytes + " is too large"); } return (int) abytes + message.length; } /** * Encrypt a message for a given key, generating a detached message authentication code. * * @param message The message to encrypt. * @param key The key to encrypt for. * @param nonce A unique nonce. * @return The encrypted data. */ public static DetachedEncryptionResult encryptDetached(Bytes message, Key key, Nonce nonce) { return encryptDetached(message.toArrayUnsafe(), key, nonce); } /** * Encrypt a message for a given key, generating a detached message authentication code. * * @param message The message to encrypt. * @param key The key to encrypt for. * @param nonce A unique nonce. * @return The encrypted data. */ public static DetachedEncryptionResult encryptDetached(byte[] message, Key key, Nonce nonce) { return encryptDetached(message, EMPTY_BYTES, key, nonce); } /** * Encrypt a message for a given key, generating a detached message authentication code. * * @param message The message to encrypt. * @param data Extra non-confidential data that will be included with the encrypted payload. * @param key The key to encrypt for. * @param nonce A unique nonce. * @return The encrypted data. */ public static DetachedEncryptionResult encryptDetached(Bytes message, Bytes data, Key key, Nonce nonce) { return encryptDetached(message.toArrayUnsafe(), data.toArrayUnsafe(), key, nonce); } /** * Encrypt a message for a given key, generating a detached message authentication code. * * @param message The message to encrypt. * @param data Extra non-confidential data that will be included with the encrypted payload. * @param key The key to encrypt for. * @param nonce A unique nonce. * @return The encrypted data. * @throws UnsupportedOperationException If XChaCha20Poly1305 support is not available. */ public static DetachedEncryptionResult encryptDetached(byte[] message, byte[] data, Key key, Nonce nonce) { assertAvailable(); checkArgument(key.ptr != null, "Key has been destroyed"); byte[] cipherText = new byte[message.length]; long abytes = Sodium.crypto_aead_xchacha20poly1305_ietf_abytes(); if (abytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_aead_xchacha20poly1305_ietf_abytes: " + abytes + " is too large"); } byte[] mac = new byte[(int) abytes]; LongLongByReference macLen = new LongLongByReference(); int rc = Sodium.crypto_aead_xchacha20poly1305_ietf_encrypt_detached( cipherText, mac, macLen, message, message.length, data, data.length, null, nonce.ptr, key.ptr); if (rc != 0) { throw new SodiumException("crypto_aead_xchacha20poly1305_ietf_encrypt_detached: failed with result " + rc); } return new DefaultDetachedEncryptionResult( cipherText, maybeSliceResult(mac, macLen, "crypto_aead_xchacha20poly1305_ietf_encrypt_detached")); } private static final byte TAG_FINAL = (0x01 | 0x02); private static final class SSEncrypt implements SecretEncryptionStream { private final int abytes; private final byte[] header; @Nullable private Pointer state; private boolean complete = false; private SSEncrypt(Key key) { checkArgument(key.ptr != null, "Key has been destroyed"); long abytes = Sodium.crypto_secretstream_xchacha20poly1305_abytes(); if (abytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_aead_xchacha20poly1305_ietf_abytes: " + abytes + " is too large"); } this.abytes = (int) abytes; long headerbytes = Sodium.crypto_secretstream_xchacha20poly1305_headerbytes(); if (headerbytes > Integer.MAX_VALUE) { throw new IllegalStateException( "crypto_secretstream_xchacha20poly1305_headerbytes: " + abytes + " is too large"); } this.header = new byte[(int) headerbytes]; Pointer state = Sodium.malloc(Sodium.crypto_secretstream_xchacha20poly1305_statebytes()); try { int rc = Sodium.crypto_secretstream_xchacha20poly1305_init_push(state, header, key.ptr); if (rc != 0) { throw new SodiumException("crypto_secretstream_xchacha20poly1305_init_push: failed with result " + rc); } } catch (Throwable e) { Sodium.sodium_free(state); throw e; } this.state = state; } @Override protected void finalize() { destroy(); } @Override public void destroy() { if (state != null) { Pointer p = state; state = null; Sodium.sodium_free(p); } } @Override public boolean isDestroyed() { return state == null; } @Override public byte[] headerArray() { return header; } @Override public byte[] push(byte[] clearText, boolean isFinal) { checkState(!complete, "stream already completed"); checkState(state != null, "stream has been destroyed"); byte[] cipherText = new byte[abytes + clearText.length]; byte tag = isFinal ? TAG_FINAL : 0; int rc = Sodium.crypto_secretstream_xchacha20poly1305_push( state, cipherText, null, clearText, clearText.length, null, 0, tag); if (rc != 0) { throw new SodiumException("crypto_secretstream_xchacha20poly1305_push: failed with result " + rc); } if (isFinal) { complete = true; // destroy state before finalization, as it will not be re-used destroy(); } return cipherText; } } /** * Open an encryption stream. * * @param key The key to encrypt for. * @return The input stream. * @throws UnsupportedOperationException If XChaCha20Poly1305 secret stream support is not available. */ public static SecretEncryptionStream openEncryptionStream(Key key) { assertSecretStreamAvailable(); return new SSEncrypt(key); } /** * Decrypt a message using a given key. * * @param cipherText The cipher text to decrypt. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decrypt(Bytes cipherText, Key key, Nonce nonce) { byte[] bytes = decrypt(cipherText.toArrayUnsafe(), key, nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a given key. * * @param cipherText The cipher text to decrypt. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decrypt(byte[] cipherText, Key key, Nonce nonce) { return decrypt(cipherText, EMPTY_BYTES, key, nonce); } /** * Decrypt a message using a given key. * * @param cipherText The cipher text to decrypt. * @param data Extra non-confidential data that is included within the encrypted payload. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decrypt(Bytes cipherText, Bytes data, Key key, Nonce nonce) { byte[] bytes = decrypt(cipherText.toArrayUnsafe(), data.toArrayUnsafe(), key, nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a given key. * * @param cipherText The cipher text to decrypt. * @param data Extra non-confidential data that is included within the encrypted payload. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. * @throws UnsupportedOperationException If XChaCha20Poly1305 support is not available. */ @Nullable public static byte[] decrypt(byte[] cipherText, byte[] data, Key key, Nonce nonce) { assertAvailable(); checkArgument(key.ptr != null, "Key has been destroyed"); byte[] clearText = new byte[maxClearTextLength(cipherText)]; LongLongByReference clearTextLen = new LongLongByReference(); int rc = Sodium.crypto_aead_xchacha20poly1305_ietf_decrypt( clearText, clearTextLen, null, cipherText, cipherText.length, data, data.length, nonce.ptr, key.ptr); if (rc == -1) { return null; } if (rc != 0) { throw new SodiumException("crypto_aead_xchacha20poly1305_ietf_decrypt: failed with result " + rc); } return maybeSliceResult(clearText, clearTextLen, "crypto_aead_xchacha20poly1305_ietf_decrypt"); } private static int maxClearTextLength(byte[] cipherText) { long abytes = Sodium.crypto_aead_xchacha20poly1305_ietf_abytes(); if (abytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_aead_xchacha20poly1305_ietf_abytes: " + abytes + " is too large"); } if (abytes > cipherText.length) { throw new IllegalArgumentException("cipherText is too short"); } return cipherText.length - ((int) abytes); } /** * Decrypt a message using a given key and a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ public static Bytes decryptDetached(Bytes cipherText, Bytes mac, Key key, Nonce nonce) { byte[] bytes = decryptDetached(cipherText.toArrayUnsafe(), mac.toArrayUnsafe(), key, nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a given key and a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static byte[] decryptDetached(byte[] cipherText, byte[] mac, Key key, Nonce nonce) { return decryptDetached(cipherText, mac, EMPTY_BYTES, key, nonce); } /** * Decrypt a message using a given key and a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param data Extra non-confidential data that is included within the encrypted payload. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. */ @Nullable public static Bytes decryptDetached(Bytes cipherText, Bytes mac, Bytes data, Key key, Nonce nonce) { byte[] bytes = decryptDetached(cipherText.toArrayUnsafe(), mac.toArrayUnsafe(), data.toArrayUnsafe(), key, nonce); return (bytes != null) ? Bytes.wrap(bytes) : null; } /** * Decrypt a message using a given key and a detached message authentication code. * * @param cipherText The cipher text to decrypt. * @param mac The message authentication code. * @param data Extra non-confidential data that is included within the encrypted payload. * @param key The key to use for decryption. * @param nonce The nonce that was used for encryption. * @return The decrypted data, or {@code null} if verification failed. * @throws UnsupportedOperationException If XChaCha20Poly1305 support is not available. */ @Nullable public static byte[] decryptDetached(byte[] cipherText, byte[] mac, byte[] data, Key key, Nonce nonce) { assertAvailable(); checkArgument(key.ptr != null, "Key has been destroyed"); long abytes = Sodium.crypto_aead_xchacha20poly1305_ietf_abytes(); if (abytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_aead_xchacha20poly1305_ietf_abytes: " + abytes + " is too large"); } if (mac.length != abytes) { throw new IllegalArgumentException("mac must be " + abytes + " bytes, got " + mac.length); } byte[] clearText = new byte[cipherText.length]; int rc = Sodium.crypto_aead_xchacha20poly1305_ietf_decrypt_detached( clearText, null, cipherText, cipherText.length, mac, data, data.length, nonce.ptr, key.ptr); if (rc == -1) { return null; } if (rc != 0) { throw new SodiumException("crypto_aead_xchacha20poly1305_ietf_decrypt_detached: failed with result " + rc); } return clearText; } private static final class SSDecrypt implements SecretDecryptionStream { private final int abytes; @Nullable private Pointer state; private boolean complete = false; private SSDecrypt(Key key, byte[] header) { checkArgument(key.ptr != null, "Key has been destroyed"); if (header.length != Sodium.crypto_secretstream_xchacha20poly1305_headerbytes()) { throw new IllegalArgumentException( "header must be " + Sodium.crypto_secretstream_xchacha20poly1305_headerbytes() + " bytes, got " + header.length); } long abytes = Sodium.crypto_secretstream_xchacha20poly1305_abytes(); if (abytes > Integer.MAX_VALUE) { throw new IllegalStateException("crypto_aead_xchacha20poly1305_ietf_abytes: " + abytes + " is too large"); } this.abytes = (int) abytes; Pointer state = Sodium.malloc(Sodium.crypto_secretstream_xchacha20poly1305_statebytes()); try { int rc = Sodium.crypto_secretstream_xchacha20poly1305_init_pull(state, header, key.ptr); if (rc != 0) { throw new SodiumException("crypto_secretstream_xchacha20poly1305_init_push: failed with result " + rc); } } catch (Throwable e) { Sodium.sodium_free(state); throw e; } this.state = state; } @Override protected void finalize() { destroy(); } @Override public void destroy() { if (state != null) { Pointer p = state; state = null; Sodium.sodium_free(p); } } @Override public boolean isDestroyed() { return state == null; } @Override public byte[] pull(byte[] cipherText) { checkState(!complete, "stream already completed"); checkState(state != null, "stream has been destroyed"); if (abytes > cipherText.length) { throw new IllegalArgumentException("cipherText is too short"); } byte[] clearText = new byte[cipherText.length - abytes]; ByteByReference tag = new ByteByReference(); int rc = Sodium.crypto_secretstream_xchacha20poly1305_pull( state, clearText, null, tag, cipherText, cipherText.length, null, 0); if (rc != 0) { throw new SodiumException("crypto_secretstream_xchacha20poly1305_push: failed with result " + rc); } if (tag.byteValue() == TAG_FINAL) { complete = true; // destroy state before finalization, as it will not be re-used destroy(); } return clearText; } @Override public boolean isComplete() { return complete; } } /** * Open an decryption stream. * * @param key The key to use for decryption. * @param header The header for the stream. * @return The input stream. * @throws UnsupportedOperationException If XChaCha20Poly1305 secret stream support is not available. */ public static SecretDecryptionStream openDecryptionStream(Key key, byte[] header) { assertSecretStreamAvailable(); return new SSDecrypt(key, header); } private static byte[] maybeSliceResult(byte[] bytes, LongLongByReference actualLength, String methodName) { if (actualLength.longValue() == bytes.length) { return bytes; } if (actualLength.longValue() > Integer.MAX_VALUE) { throw new SodiumException(methodName + ": result of length " + actualLength.longValue() + " is too large"); } byte[] result = new byte[actualLength.intValue()]; System.arraycopy(bytes, 0, result, 0, result.length); return result; } } cava-0.6.0/crypto/src/main/java/net/consensys/cava/crypto/sodium/package-info.java000066400000000000000000000016371341750772100301540ustar00rootroot00000000000000/** * 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 it can be loaded explicitly using * {@link net.consensys.cava.crypto.sodium.Sodium#searchLibrary(java.nio.file.Path...)} or * {@link net.consensys.cava.crypto.sodium.Sodium#loadLibrary(java.nio.file.Path)}. * *

* 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'. */ @ParametersAreNonnullByDefault package net.consensys.cava.crypto.sodium; import javax.annotation.ParametersAreNonnullByDefault; cava-0.6.0/crypto/src/test/000077500000000000000000000000001341750772100155245ustar00rootroot00000000000000cava-0.6.0/crypto/src/test/java/000077500000000000000000000000001341750772100164455ustar00rootroot00000000000000cava-0.6.0/crypto/src/test/java/net/000077500000000000000000000000001341750772100172335ustar00rootroot00000000000000cava-0.6.0/crypto/src/test/java/net/consensys/000077500000000000000000000000001341750772100212575ustar00rootroot00000000000000cava-0.6.0/crypto/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100221715ustar00rootroot00000000000000cava-0.6.0/crypto/src/test/java/net/consensys/cava/crypto/000077500000000000000000000000001341750772100235115ustar00rootroot00000000000000cava-0.6.0/crypto/src/test/java/net/consensys/cava/crypto/HashTest.java000066400000000000000000000116241341750772100261030ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.*; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.junit.BouncyCastleExtension; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(BouncyCastleExtension.class) class HashTest { @Test void sha2_256() { String horseSha2 = "fd62862b6dc213bee77c2badd6311528253c6cb3107e03c16051aa15584eca1c"; String cowSha2 = "beb134754910a4b4790c69ab17d3975221f4c534b70c8d6e82b30c165e8c0c09"; Bytes resultHorse = Hash.sha2_256(Bytes.wrap("horse".getBytes(UTF_8))); assertEquals(Bytes.fromHexString(horseSha2), resultHorse); byte[] resultHorse2 = Hash.sha2_256("horse".getBytes(UTF_8)); assertArrayEquals(Bytes.fromHexString(horseSha2).toArray(), resultHorse2); Bytes resultCow = Hash.sha2_256(Bytes.wrap("cow".getBytes(UTF_8))); assertEquals(Bytes.fromHexString(cowSha2), resultCow); byte[] resultCow2 = Hash.sha2_256("cow".getBytes(UTF_8)); assertArrayEquals(Bytes.fromHexString(cowSha2).toArray(), resultCow2); } @Test void sha2_512_256() { String horseSha2 = "6d64886cd066b81cf2dcf16ae70e97017d35f2f4ab73c5c5810aaa9ab573dab3"; String cowSha2 = "7d26bad15e2f266cb4cbe9b1913978cb8a8bd08d92ee157b6be87c92dfce2d3e"; Bytes resultHorse = Hash.sha2_512_256(Bytes.wrap("horse".getBytes(UTF_8))); assertEquals(Bytes.fromHexString(horseSha2), resultHorse); byte[] resultHorse2 = Hash.sha2_512_256("horse".getBytes(UTF_8)); assertArrayEquals(Bytes.fromHexString(horseSha2).toArray(), resultHorse2); Bytes resultCow = Hash.sha2_512_256(Bytes.wrap("cow".getBytes(UTF_8))); assertEquals(Bytes.fromHexString(cowSha2), resultCow); byte[] resultCow2 = Hash.sha2_512_256("cow".getBytes(UTF_8)); assertArrayEquals(Bytes.fromHexString(cowSha2).toArray(), resultCow2); } @Test void keccak256() { String horseKeccak256 = "c87f65ff3f271bf5dc8643484f66b200109caffe4bf98c4cb393dc35740b28c0"; String cowKeccak256 = "c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"; Bytes resultHorse = Hash.keccak256(Bytes.wrap("horse".getBytes(UTF_8))); assertEquals(Bytes.fromHexString(horseKeccak256), resultHorse); byte[] resultHorse2 = Hash.keccak256("horse".getBytes(UTF_8)); assertArrayEquals(Bytes.fromHexString(horseKeccak256).toArray(), resultHorse2); Bytes resultCow = Hash.keccak256(Bytes.wrap("cow".getBytes(UTF_8))); assertEquals(Bytes.fromHexString(cowKeccak256), resultCow); byte[] resultCow2 = Hash.keccak256("cow".getBytes(UTF_8)); assertArrayEquals(Bytes.fromHexString(cowKeccak256).toArray(), resultCow2); } @Test void sha3_256() { String horseSha3 = "d8137088d21c7c0d69107cd51d1c32440a57aa5c59f73ed7310522ea491000ac"; String cowSha3 = "fba26f1556b8c7b473d01e3eae218318f752e808407794fc0b6490988a33a82d"; Bytes resultHorse = Hash.sha3_256(Bytes.wrap("horse".getBytes(UTF_8))); assertEquals(Bytes.fromHexString(horseSha3), resultHorse); byte[] resultHorse2 = Hash.sha3_256("horse".getBytes(UTF_8)); assertArrayEquals(Bytes.fromHexString(horseSha3).toArray(), resultHorse2); Bytes resultCow = Hash.sha3_256(Bytes.wrap("cow".getBytes(UTF_8))); assertEquals(Bytes.fromHexString(cowSha3), resultCow); byte[] resultCow2 = Hash.sha3_256("cow".getBytes(UTF_8)); assertArrayEquals(Bytes.fromHexString(cowSha3).toArray(), resultCow2); } @Test void sha3_512() { String horseSha3 = "d78700def5dd85a9f5a1f8cce8614889e696d4dc82b17189e4974acc050659b49494f03cd0bfbb13a32132b4b4af5e16efd8b0643a5453c87e8e6dfb086b3568"; String cowSha3 = "14accdcf3380cd31674aa5edcd2a53f1b1dad3922eb335e89399321e17a8be5ea315b5346a4c45f6a2595b8e2e24bb345daeb97c7ddd2e970b9e53c9ae439f23"; Bytes resultHorse = Hash.sha3_512(Bytes.wrap("horse".getBytes(UTF_8))); assertEquals(Bytes.fromHexString(horseSha3), resultHorse); byte[] resultHorse2 = Hash.sha3_512("horse".getBytes(UTF_8)); assertArrayEquals(Bytes.fromHexString(horseSha3).toArray(), resultHorse2); Bytes resultCow = Hash.sha3_512(Bytes.wrap("cow".getBytes(UTF_8))); assertEquals(Bytes.fromHexString(cowSha3), resultCow); byte[] resultCow2 = Hash.sha3_512("cow".getBytes(UTF_8)); assertArrayEquals(Bytes.fromHexString(cowSha3).toArray(), resultCow2); } } cava-0.6.0/crypto/src/test/java/net/consensys/cava/crypto/SECP256K1Test.java000066400000000000000000000332451341750772100264060ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto; import static java.nio.charset.StandardCharsets.UTF_8; import static net.consensys.cava.bytes.Bytes.fromHexString; import static org.junit.jupiter.api.Assertions.*; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import net.consensys.cava.crypto.SECP256K1.KeyPair; import net.consensys.cava.crypto.SECP256K1.PublicKey; import net.consensys.cava.crypto.SECP256K1.SecretKey; import net.consensys.cava.crypto.SECP256K1.Signature; import net.consensys.cava.junit.BouncyCastleExtension; import net.consensys.cava.junit.TempDirectory; import net.consensys.cava.junit.TempDirectoryExtension; import java.math.BigInteger; import java.nio.file.Files; import java.nio.file.Path; import java.util.Random; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) @ExtendWith(BouncyCastleExtension.class) class SECP256K1Test { @Test void testCreatePrivateKey_NullEncoding() { assertThrows(NullPointerException.class, () -> SecretKey.fromBytes(null)); } @Test void testPrivateKeyEquals() { SecretKey secretKey1 = SecretKey.fromInteger(BigInteger.TEN); SecretKey secretKey2 = SecretKey.fromInteger(BigInteger.TEN); assertEquals(secretKey1, secretKey2); } @Test void testPrivateHashCode() { SecretKey secretKey = SecretKey.fromInteger(BigInteger.TEN); assertNotEquals(0, secretKey.hashCode()); } @Test void testCreatePublicKey_NullEncoding() { assertThrows(NullPointerException.class, () -> SECP256K1.PublicKey.fromBytes(null)); } @Test void testCreatePublicKey_EncodingTooShort() { assertThrows(IllegalArgumentException.class, () -> SECP256K1.PublicKey.fromBytes(Bytes.wrap(new byte[63]))); } @Test void testCreatePublicKey_EncodingTooLong() { assertThrows(IllegalArgumentException.class, () -> SECP256K1.PublicKey.fromBytes(Bytes.wrap(new byte[65]))); } @Test void testPublicKeyEquals() { SECP256K1.PublicKey publicKey1 = SECP256K1.PublicKey.fromBytes( fromHexString( "a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7")); SECP256K1.PublicKey publicKey2 = SECP256K1.PublicKey.fromBytes( fromHexString( "a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7")); assertEquals(publicKey1, publicKey2); } @Test void testPublicHashCode() { SECP256K1.PublicKey publicKey = SECP256K1.PublicKey.fromBytes( fromHexString( "a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7")); assertNotEquals(0, publicKey.hashCode()); } @Test void testCreateKeyPair_PublicKeyNull() { assertThrows( NullPointerException.class, () -> SECP256K1.KeyPair.create(null, SECP256K1.PublicKey.fromBytes(Bytes.wrap(new byte[64])))); } @Test void testCreateKeyPair_PrivateKeyNull() { assertThrows( NullPointerException.class, () -> SECP256K1.KeyPair.create(SecretKey.fromBytes(Bytes32.wrap(new byte[32])), null)); } @Test void testKeyPairGeneration() { SECP256K1.KeyPair keyPair = SECP256K1.KeyPair.random(); assertNotNull(keyPair); assertNotNull(keyPair.secretKey()); assertNotNull(keyPair.publicKey()); } @Test void testKeyPairEquals() { SecretKey secretKey1 = SecretKey.fromInteger(BigInteger.TEN); SecretKey secretKey2 = SecretKey.fromInteger(BigInteger.TEN); SECP256K1.PublicKey publicKey1 = SECP256K1.PublicKey.fromBytes( fromHexString( "a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7")); SECP256K1.PublicKey publicKey2 = SECP256K1.PublicKey.fromBytes( fromHexString( "a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7")); SECP256K1.KeyPair keyPair1 = SECP256K1.KeyPair.create(secretKey1, publicKey1); SECP256K1.KeyPair keyPair2 = SECP256K1.KeyPair.create(secretKey2, publicKey2); assertEquals(keyPair1, keyPair2); } @Test void testKeyPairHashCode() { SECP256K1.KeyPair keyPair = SECP256K1.KeyPair.random(); assertNotEquals(0, keyPair.hashCode()); } @Test void testKeyPairGeneration_PublicKeyRecovery() { SECP256K1.KeyPair keyPair = SECP256K1.KeyPair.random(); assertEquals(keyPair.publicKey(), SECP256K1.PublicKey.fromSecretKey(keyPair.secretKey())); } @Test void testPublicKeyRecovery() { SecretKey secretKey = SecretKey.fromInteger(BigInteger.TEN); SECP256K1.PublicKey expectedPublicKey = SECP256K1.PublicKey.fromBytes( fromHexString( "a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7")); SECP256K1.PublicKey publicKey = SECP256K1.PublicKey.fromSecretKey(secretKey); assertEquals(expectedPublicKey, publicKey); } @Test void testCreateSignature() { SECP256K1.Signature signature = new SECP256K1.Signature((byte) 0, BigInteger.ONE, BigInteger.TEN); assertEquals(BigInteger.ONE, signature.r()); assertEquals(BigInteger.TEN, signature.s()); assertEquals((byte) 0, signature.v()); } @Test void testEncodeSignature() { SECP256K1.Signature signature = new SECP256K1.Signature((byte) 0, BigInteger.ONE, BigInteger.TEN); assertEquals( "0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000A00", signature.bytes().toString()); } @Test void testCreateSignatureFromEncoding() { SECP256K1.Signature signature = SECP256K1.Signature.fromBytes( fromHexString( "0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000A01")); assertEquals(BigInteger.ONE, signature.r()); assertEquals(BigInteger.TEN, signature.s()); assertEquals((byte) 1, signature.v()); } @Test void testCreateSignatureWithNullR() { assertThrows(NullPointerException.class, () -> SECP256K1.Signature.create((byte) 1, null, BigInteger.ONE)); } @Test void testCreateSignatureWithNullS() { assertThrows(NullPointerException.class, () -> SECP256K1.Signature.create((byte) 1, BigInteger.ONE, null)); } @Test void testCreateSignatureWithZeroR() { Exception throwable = assertThrows( IllegalArgumentException.class, () -> SECP256K1.Signature.create((byte) 1, BigInteger.ZERO, BigInteger.ONE)); assertEquals( "Invalid r-value, should be >= 1 and < " + SECP256K1.Parameters.CURVE.getN() + ", got 0", throwable.getMessage()); } @Test void testCreateSignatureWithZeroS() { Exception throwable = assertThrows( IllegalArgumentException.class, () -> SECP256K1.Signature.create((byte) 1, BigInteger.ONE, BigInteger.ZERO)); assertEquals( "Invalid s-value, should be >= 1 and < " + SECP256K1.Parameters.CURVE.getN() + ", got 0", throwable.getMessage()); } @Test void testCreateSignatureWithRHigherThanCurve() { BigInteger curveN = SECP256K1.Parameters.CURVE.getN(); Exception throwable = assertThrows( IllegalArgumentException.class, () -> SECP256K1.Signature.create((byte) 1, curveN.add(BigInteger.ONE), BigInteger.ONE)); assertEquals( "Invalid r-value, should be >= 1 and < " + curveN + ", got " + curveN.add(BigInteger.ONE), throwable.getMessage()); } @Test void testCreateSignatureWithSHigherThanCurve() { BigInteger curveN = SECP256K1.Parameters.CURVE.getN(); Exception throwable = assertThrows( IllegalArgumentException.class, () -> SECP256K1.Signature.create((byte) 1, BigInteger.ONE, curveN.add(BigInteger.ONE))); assertEquals( "Invalid s-value, should be >= 1 and < " + curveN + ", got " + curveN.add(BigInteger.ONE), throwable.getMessage()); } @Test void testRecoverPublicKeyFromSignature() { SecretKey secretKey = SecretKey.fromInteger(new BigInteger("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4", 16)); SECP256K1.KeyPair keyPair = SECP256K1.KeyPair.fromSecretKey(secretKey); long seed = new Random().nextLong(); Random random = new Random(seed); for (int i = 0; i < 100; ++i) { try { byte[] data = new byte[20]; random.nextBytes(data); SECP256K1.Signature signature = SECP256K1.sign(data, keyPair); PublicKey recoveredPublicKey = SECP256K1.PublicKey.recoverFromSignature(data, signature); assertNotNull(recoveredPublicKey); assertEquals(keyPair.publicKey().toString(), recoveredPublicKey.toString()); assertTrue(SECP256K1.verify(data, signature, recoveredPublicKey)); } catch (AssertionError e) { System.err.println("Random seed: " + seed); throw e; } } Bytes32 hash = Bytes32.fromHexString("ACB1C19AC0832320815B5E886C6B73AD7D6177853D44B026F2A7A9E11BB899FC"); SECP256K1.Signature signature = SECP256K1.Signature.create( (byte) 1, new BigInteger("62380806879052346173879701944100777919767605075819957043497305774369260714318"), new BigInteger("38020116821208196490118623452490256423459205241616519723877133146103446128360")); assertNull(SECP256K1.PublicKey.recoverFromHashAndSignature(hash, signature)); } @Test void testCannotRecoverPublicKeyFromSignature() { SECP256K1.Signature signature = new Signature((byte) 0, SECP256K1.Parameters.CURVE_ORDER.subtract(BigInteger.ONE), BigInteger.valueOf(10)); assertNull(SECP256K1.PublicKey.recoverFromSignature(Bytes.of("Random data".getBytes(UTF_8)), signature)); } @Test void testSignatureGeneration() { SecretKey secretKey = SecretKey.fromInteger(new BigInteger("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4", 16)); SECP256K1.KeyPair keyPair = SECP256K1.KeyPair.fromSecretKey(secretKey); Bytes data = Bytes.wrap("This is an example of a signed message.".getBytes(UTF_8)); SECP256K1.Signature expectedSignature = new SECP256K1.Signature( (byte) 1, new BigInteger("d2ce488f4da29e68f22cb05cac1b19b75df170a12b4ad1bdd4531b8e9115c6fb", 16), new BigInteger("75c1fe50a95e8ccffcbb5482a1e42fbbdd6324131dfe75c3b3b7f9a7c721eccb", 16)); SECP256K1.Signature actualSignature = SECP256K1.sign(data, keyPair); assertEquals(expectedSignature, actualSignature); } @Test void testSignatureVerification() { SecretKey secretKey = SecretKey.fromInteger(new BigInteger("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4", 16)); SECP256K1.KeyPair keyPair = SECP256K1.KeyPair.fromSecretKey(secretKey); Bytes data = Bytes.wrap("This is an example of a signed message.".getBytes(UTF_8)); SECP256K1.Signature signature = SECP256K1.sign(data, keyPair); assertTrue(SECP256K1.verify(data, signature, keyPair.publicKey())); } @Test void testFileContainsValidPrivateKey(@TempDirectory Path tempDir) throws Exception { Path tempFile = tempDir.resolve("tempId"); Files.write(tempFile, "000000000000000000000000000000000000000000000000000000000000000A".getBytes(UTF_8)); SecretKey secretKey = SecretKey.load(tempFile); assertEquals(fromHexString("000000000000000000000000000000000000000000000000000000000000000A"), secretKey.bytes()); } @Test void testReadWritePrivateKeyString(@TempDirectory Path tempDir) throws Exception { SecretKey secretKey = SecretKey.fromInteger(BigInteger.TEN); SECP256K1.KeyPair keyPair1 = SECP256K1.KeyPair.fromSecretKey(secretKey); Path tempFile = tempDir.resolve("tempId"); keyPair1.store(tempFile); SECP256K1.KeyPair keyPair2 = SECP256K1.KeyPair.load(tempFile); assertEquals(keyPair1, keyPair2); } @Test void testInvalidFileThrowsInvalidKeyPairException(@TempDirectory Path tempDir) throws Exception { Path tempFile = tempDir.resolve("tempId"); Files.write(tempFile, "not valid".getBytes(UTF_8)); assertThrows(InvalidSEC256K1SecretKeyStoreException.class, () -> SecretKey.load(tempFile)); } @Test void testInvalidMultiLineFileThrowsInvalidIdException(@TempDirectory Path tempDir) throws Exception { Path tempFile = tempDir.resolve("tempId"); Files.write(tempFile, "not\n\nvalid".getBytes(UTF_8)); assertThrows(InvalidSEC256K1SecretKeyStoreException.class, () -> SecretKey.load(tempFile)); } @Test void testEncodedBytes() { KeyPair kp = SECP256K1.KeyPair.random(); Signature sig = SECP256K1.sign(Bytes.of(1, 2, 3), kp); assertEquals(65, sig.bytes().size()); assertTrue(sig.bytes().get(64) <= 3 && sig.bytes().get(64) >= 0); } @Test void testSharedSecretBytes() { KeyPair kp = SECP256K1.KeyPair.random(); KeyPair otherKP = SECP256K1.KeyPair.random(); Bytes32 sharedSecret = SECP256K1.calculateKeyAgreement(kp.secretKey(), otherKP.publicKey()); Bytes32 otherSharedSecret = SECP256K1.calculateKeyAgreement(otherKP.secretKey(), kp.publicKey()); assertEquals(sharedSecret, otherSharedSecret); } } cava-0.6.0/crypto/src/test/java/net/consensys/cava/crypto/sodium/000077500000000000000000000000001341750772100250115ustar00rootroot00000000000000cava-0.6.0/crypto/src/test/java/net/consensys/cava/crypto/sodium/AES256GCMTest.java000066400000000000000000000073451341750772100277210ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class AES256GCMTest { private static AES256GCM.Nonce nonce; @BeforeAll static void checkAvailable() { assumeTrue(Sodium.isAvailable(), "Sodium native library is not available"); assumeTrue(AES256GCM.isAvailable(), "AES256-GSM support is not available"); nonce = AES256GCM.Nonce.random(); } @BeforeEach void incrementNonce() { nonce = nonce.increment(); } @Test void checkCombinedEncryptDecrypt() { AES256GCM.Key key = AES256GCM.Key.random(); byte[] message = "This is a test message".getBytes(UTF_8); byte[] data = "123456".getBytes(UTF_8); byte[] cipherText = AES256GCM.encrypt(message, data, key, nonce); byte[] clearText = AES256GCM.decrypt(cipherText, data, key, nonce); assertNotNull(clearText); assertArrayEquals(message, clearText); assertNull(AES256GCM.decrypt(cipherText, data, key, nonce.increment())); } @Test void checkCombinedPrecomputedEncryptDecrypt() { try (AES256GCM precomputed = AES256GCM.forKey(AES256GCM.Key.random())) { byte[] message = "This is a test message".getBytes(UTF_8); byte[] data = "123456".getBytes(UTF_8); byte[] cipherText = precomputed.encrypt(message, data, nonce); byte[] clearText = precomputed.decrypt(cipherText, data, nonce); assertNotNull(clearText); assertArrayEquals(message, clearText); assertNull(precomputed.decrypt(cipherText, data, nonce.increment())); } } @Test void checkDetachedEncryptDecrypt() { AES256GCM.Key key = AES256GCM.Key.random(); byte[] message = "This is a test message".getBytes(UTF_8); byte[] data = "123456".getBytes(UTF_8); DetachedEncryptionResult result = AES256GCM.encryptDetached(message, data, key, nonce); byte[] clearText = AES256GCM.decryptDetached(result.cipherTextArray(), result.macArray(), data, key, nonce); assertNotNull(clearText); assertArrayEquals(message, clearText); clearText = AES256GCM.decryptDetached(result.cipherTextArray(), result.macArray(), data, key, nonce.increment()); assertNull(clearText); } @Test void checkDetachedPrecomputedEncryptDecrypt() { try (AES256GCM precomputed = AES256GCM.forKey(AES256GCM.Key.random())) { byte[] message = "This is a test message".getBytes(UTF_8); byte[] data = "123456".getBytes(UTF_8); DetachedEncryptionResult result = precomputed.encryptDetached(message, data, nonce); byte[] clearText = precomputed.decryptDetached(result.cipherTextArray(), result.macArray(), data, nonce); assertNotNull(clearText); assertArrayEquals(message, clearText); clearText = precomputed.decryptDetached(result.cipherTextArray(), result.macArray(), data, nonce.increment()); assertNull(clearText); } } } cava-0.6.0/crypto/src/test/java/net/consensys/cava/crypto/sodium/AuthTest.java000066400000000000000000000027711341750772100274240ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; class AuthTest { @BeforeAll static void checkAvailable() { assumeTrue(Sodium.isAvailable(), "Sodium native library is not available"); } @Test void checkAuthenticateAndVerify() { Auth.Key key = Auth.Key.random(); byte[] input = "An input to authenticate".getBytes(UTF_8); byte[] tag = Auth.auth(input, key); assertTrue(Auth.verify(tag, input, key)); assertFalse(Auth.verify(new byte[tag.length], input, key)); assertFalse(Auth.verify(tag, "An invalid input".getBytes(UTF_8), key)); assertFalse(Auth.verify(tag, input, Auth.Key.random())); } } cava-0.6.0/crypto/src/test/java/net/consensys/cava/crypto/sodium/BoxTest.java000066400000000000000000000136221341750772100272500ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class BoxTest { private static Box.Seed seed; private static Box.Nonce nonce; @BeforeAll static void setup() { assumeTrue(Sodium.isAvailable(), "Sodium native library is not available"); nonce = Box.Nonce.random(); // @formatter:off seed = Box.Seed.fromBytes(new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f }); // @formatter:on } @BeforeEach void incrementNonce() { nonce = nonce.increment(); } @Test void checkCombinedEncryptDecrypt() { Box.KeyPair aliceKeyPair = Box.KeyPair.random(); Box.KeyPair bobKeyPair = Box.KeyPair.fromSeed(seed); byte[] message = "This is a test message".getBytes(UTF_8); byte[] cipherText = Box.encrypt(message, aliceKeyPair.publicKey(), bobKeyPair.secretKey(), nonce); byte[] clearText = Box.decrypt(cipherText, bobKeyPair.publicKey(), aliceKeyPair.secretKey(), nonce); assertNotNull(clearText); assertArrayEquals(message, clearText); clearText = Box.decrypt(cipherText, bobKeyPair.publicKey(), aliceKeyPair.secretKey(), nonce.increment()); assertNull(clearText); Box.KeyPair otherKeyPair = Box.KeyPair.random(); clearText = Box.decrypt(cipherText, otherKeyPair.publicKey(), bobKeyPair.secretKey(), nonce); assertNull(clearText); } @Test void checkCombinedPrecomputedEncryptDecrypt() { Box.KeyPair aliceKeyPair = Box.KeyPair.random(); Box.KeyPair bobKeyPair = Box.KeyPair.random(); byte[] message = "This is a test message".getBytes(UTF_8); byte[] cipherText; try (Box precomputed = Box.forKeys(aliceKeyPair.publicKey(), bobKeyPair.secretKey())) { cipherText = precomputed.encrypt(message, nonce); } byte[] clearText = Box.decrypt(cipherText, bobKeyPair.publicKey(), aliceKeyPair.secretKey(), nonce); assertNotNull(clearText); assertArrayEquals(message, clearText); try (Box precomputed = Box.forKeys(bobKeyPair.publicKey(), aliceKeyPair.secretKey())) { clearText = precomputed.decrypt(cipherText, nonce); assertNotNull(clearText); assertArrayEquals(message, clearText); assertNull(precomputed.decrypt(cipherText, nonce.increment())); } Box.KeyPair otherKeyPair = Box.KeyPair.random(); try (Box precomputed = Box.forKeys(otherKeyPair.publicKey(), bobKeyPair.secretKey())) { assertNull(precomputed.decrypt(cipherText, nonce)); } } @Test void checkDetachedEncryptDecrypt() { Box.KeyPair aliceKeyPair = Box.KeyPair.random(); Box.KeyPair bobKeyPair = Box.KeyPair.random(); byte[] message = "This is a test message".getBytes(UTF_8); DetachedEncryptionResult result = Box.encryptDetached(message, aliceKeyPair.publicKey(), bobKeyPair.secretKey(), nonce); byte[] clearText = Box.decryptDetached( result.cipherTextArray(), result.macArray(), bobKeyPair.publicKey(), aliceKeyPair.secretKey(), nonce); assertNotNull(clearText); assertArrayEquals(message, clearText); clearText = Box.decryptDetached( result.cipherTextArray(), result.macArray(), bobKeyPair.publicKey(), aliceKeyPair.secretKey(), nonce.increment()); assertNull(clearText); Box.KeyPair otherKeyPair = Box.KeyPair.random(); clearText = Box.decryptDetached( result.cipherTextArray(), result.macArray(), otherKeyPair.publicKey(), bobKeyPair.secretKey(), nonce); assertNull(clearText); } @Test void checkDetachedPrecomputedEncryptDecrypt() { Box.KeyPair aliceKeyPair = Box.KeyPair.random(); Box.KeyPair bobKeyPair = Box.KeyPair.random(); byte[] message = "This is a test message".getBytes(UTF_8); DetachedEncryptionResult result; try (Box precomputed = Box.forKeys(aliceKeyPair.publicKey(), bobKeyPair.secretKey())) { result = precomputed.encryptDetached(message, nonce); } byte[] clearText = Box.decryptDetached( result.cipherTextArray(), result.macArray(), bobKeyPair.publicKey(), aliceKeyPair.secretKey(), nonce); assertNotNull(clearText); assertArrayEquals(message, clearText); try (Box precomputed = Box.forKeys(bobKeyPair.publicKey(), aliceKeyPair.secretKey())) { clearText = precomputed.decryptDetached(result.cipherTextArray(), result.macArray(), nonce); assertNotNull(clearText); assertArrayEquals(message, clearText); assertNull(precomputed.decryptDetached(result.cipherTextArray(), result.macArray(), nonce.increment())); } Box.KeyPair otherKeyPair = Box.KeyPair.random(); try (Box precomputed = Box.forKeys(otherKeyPair.publicKey(), bobKeyPair.secretKey())) { assertNull(precomputed.decryptDetached(result.cipherTextArray(), result.macArray(), nonce)); } } } cava-0.6.0/crypto/src/test/java/net/consensys/cava/crypto/sodium/KeyDerivationTest.java000066400000000000000000000032021341750772100312660ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assumptions.assumeTrue; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.crypto.sodium.KeyDerivation.MasterKey; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; class KeyDerivationTest { @BeforeAll static void checkAvailable() { assumeTrue(Sodium.isAvailable(), "Sodium native library is not available"); assumeTrue(KeyDerivation.isAvailable(), "KeyDerivation support is not available (requires >= 10.0.12"); } @Test void differentIdsShouldGenerateDifferentKeys() { MasterKey masterKey = MasterKey.random(); Bytes subKey1 = masterKey.deriveKey(40, 1, "abcdefg"); assertEquals(subKey1, masterKey.deriveKey(40, 1, "abcdefg")); assertNotEquals(subKey1, masterKey.deriveKey(40, 2, "abcdefg")); assertNotEquals(subKey1, masterKey.deriveKey(40, 1, new byte[KeyDerivation.contextLength()])); } } cava-0.6.0/crypto/src/test/java/net/consensys/cava/crypto/sodium/PasswordHashTest.java000066400000000000000000000076071341750772100311340ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.crypto.sodium.PasswordHash.Algorithm; import net.consensys.cava.crypto.sodium.PasswordHash.Salt; import net.consensys.cava.crypto.sodium.PasswordHash.VerificationResult; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; class PasswordHashTest { @BeforeAll static void checkAvailable() { assumeTrue(Sodium.isAvailable(), "Sodium native library is not available"); } @Test void shouldGenerateSameKeyForSameParameters() { String password = "A very insecure password"; Salt salt = Salt.random(); Bytes hash = PasswordHash.hash( password, 20, salt, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), Algorithm.recommended()); assertEquals(20, hash.size()); Bytes generated = PasswordHash.hash( password, 20, salt, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), Algorithm.recommended()); assertEquals(hash, generated); generated = PasswordHash.hash( password, 20, Salt.random(), PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), Algorithm.recommended()); assertNotEquals(hash, generated); generated = PasswordHash.hash( password, 20, salt, PasswordHash.moderateOpsLimit(), PasswordHash.interactiveMemLimit(), Algorithm.recommended()); assertNotEquals(hash, generated); } @Test void shouldThrowForLowOpsLimitWithArgon2i() { assertThrows(IllegalArgumentException.class, () -> { PasswordHash.hash( "A very insecure password", 20, Salt.random(), 1, PasswordHash.moderateMemLimit(), Algorithm.argon2i13()); }); } @Test void checkHashAndVerify() { String password = "A very insecure password"; String hash = PasswordHash.hashInteractive(password); assertTrue(PasswordHash.verify(hash, password)); VerificationResult result = PasswordHash.checkHashForInteractive(hash, password); assertEquals(VerificationResult.PASSED, result); assertTrue(result.passed()); assertFalse(PasswordHash.verify(hash, "Bad password")); result = PasswordHash.checkHashForInteractive(hash, "Bad password"); assertEquals(VerificationResult.FAILED, result); assertFalse(result.passed()); } @Test void checkHashAndVerifyNeedingRehash() { assumeTrue(Sodium.supportsVersion(Sodium.VERSION_10_0_14), "Requires sodium native library >= 10.0.14"); String password = "A very insecure password"; String hash = PasswordHash.hashInteractive(password); assertTrue(PasswordHash.needsRehash(hash)); VerificationResult result = PasswordHash.checkHash(hash, password); assertEquals(VerificationResult.NEEDS_REHASH, result); assertTrue(result.passed()); } } cava-0.6.0/crypto/src/test/java/net/consensys/cava/crypto/sodium/SecretBoxTest.java000066400000000000000000000152431341750772100304170ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; class SecretBoxTest { @BeforeAll static void checkAvailable() { assumeTrue(Sodium.isAvailable(), "Sodium native library is not available"); } @Test void checkCombinedEncryptDecrypt() { SecretBox.Key key = SecretBox.Key.random(); SecretBox.Nonce nonce = SecretBox.Nonce.random().increment(); byte[] message = "This is a test message".getBytes(UTF_8); byte[] cipherText = SecretBox.encrypt(message, key, nonce); byte[] clearText = SecretBox.decrypt(cipherText, key, nonce); assertNotNull(clearText); assertArrayEquals(message, clearText); assertNull(SecretBox.decrypt(cipherText, key, nonce.increment())); SecretBox.Key otherKey = SecretBox.Key.random(); assertNull(SecretBox.decrypt(cipherText, otherKey, nonce)); } @Test void checkCombinedEncryptDecryptEmptyMessage() { SecretBox.Key key = SecretBox.Key.random(); SecretBox.Nonce nonce = SecretBox.Nonce.random().increment(); byte[] cipherText = SecretBox.encrypt(new byte[0], key, nonce); byte[] clearText = SecretBox.decrypt(cipherText, key, nonce); assertNotNull(clearText); assertEquals(0, clearText.length); } @Test void checkDetachedEncryptDecrypt() { SecretBox.Key key = SecretBox.Key.random(); SecretBox.Nonce nonce = SecretBox.Nonce.random().increment(); byte[] message = "This is a test message".getBytes(UTF_8); DetachedEncryptionResult result = SecretBox.encryptDetached(message, key, nonce); byte[] clearText = SecretBox.decryptDetached(result.cipherTextArray(), result.macArray(), key, nonce); assertNotNull(clearText); assertArrayEquals(message, clearText); assertNull(SecretBox.decryptDetached(result.cipherTextArray(), result.macArray(), key, nonce.increment())); SecretBox.Key otherKey = SecretBox.Key.random(); assertNull(SecretBox.decryptDetached(result.cipherTextArray(), result.macArray(), otherKey, nonce)); } @Test void checkDetachedEncryptDecryptEmptyMessage() { SecretBox.Key key = SecretBox.Key.random(); SecretBox.Nonce nonce = SecretBox.Nonce.random().increment(); DetachedEncryptionResult result = SecretBox.encryptDetached(new byte[0], key, nonce); byte[] clearText = SecretBox.decryptDetached(result.cipherTextArray(), result.macArray(), key, nonce); assertNotNull(clearText); assertEquals(0, clearText.length); } @Test void checkCombinedEncryptDecryptWithPassword() { String password = "a random password"; byte[] message = "This is a test message".getBytes(UTF_8); byte[] cipherText = SecretBox.encrypt( message, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended()); byte[] clearText = SecretBox.decrypt( cipherText, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended()); assertNotNull(clearText); assertArrayEquals(message, clearText); String otherPassword = "a different password"; assertNull( SecretBox.decrypt( cipherText, otherPassword, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended())); } @Test void checkCombinedEncryptDecryptEmptyMessageWithPassword() { String password = "a random password"; byte[] cipherText = SecretBox.encrypt( new byte[0], password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended()); byte[] clearText = SecretBox.decrypt( cipherText, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended()); assertNotNull(clearText); assertEquals(0, clearText.length); } @Test void checkDetachedEncryptDecryptWithPassword() { String password = "a random password"; byte[] message = "This is a test message".getBytes(UTF_8); DetachedEncryptionResult result = SecretBox.encryptDetached( message, password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended()); byte[] clearText = SecretBox.decryptDetached( result.cipherTextArray(), result.macArray(), password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended()); assertNotNull(clearText); assertArrayEquals(message, clearText); String otherPassword = "a different password"; assertNull( SecretBox.decryptDetached( result.cipherTextArray(), result.macArray(), otherPassword, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended())); } @Test void checkDetachedEncryptDecryptEmptyMessageWithPassword() { String password = "a random password"; DetachedEncryptionResult result = SecretBox.encryptDetached( new byte[0], password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended()); byte[] clearText = SecretBox.decryptDetached( result.cipherTextArray(), result.macArray(), password, PasswordHash.interactiveOpsLimit(), PasswordHash.interactiveMemLimit(), PasswordHash.Algorithm.recommended()); assertNotNull(clearText); assertEquals(0, clearText.length); } } cava-0.6.0/crypto/src/test/java/net/consensys/cava/crypto/sodium/SodiumTest.java000066400000000000000000000062111341750772100277540ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assumptions.assumeTrue; import jnr.ffi.Pointer; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; class SodiumTest { @BeforeAll static void checkSodium() { assumeTrue(Sodium.isAvailable(), "Sodium native library is not available"); } @Test void checkBasicConstants() { assertEquals(12, Sodium.crypto_aead_aes256gcm_npubbytes()); assertEquals(64, Sodium.crypto_auth_hmacsha512_bytes()); assertEquals(32, Sodium.crypto_generichash_bytes()); } @Test void checkCryptoHashSha512MultiPart() { byte[] message = "This is a test message".getBytes(UTF_8); byte[] hash = new byte[(int) Sodium.crypto_hash_sha512_bytes()]; int rc = Sodium.crypto_hash_sha512(hash, message, message.length); assertEquals(0, rc); Pointer state = Sodium.sodium_malloc(Sodium.crypto_hash_sha512_statebytes()); try { rc = Sodium.crypto_hash_sha512_init(state); assertEquals(0, rc); byte[] message1 = "This is ".getBytes(UTF_8); Sodium.crypto_hash_sha512_update(state, message1, message1.length); byte[] message2 = "a test message".getBytes(UTF_8); Sodium.crypto_hash_sha512_update(state, message2, message2.length); byte[] hash2 = new byte[(int) Sodium.crypto_hash_sha512_bytes()]; Sodium.crypto_hash_sha512_final(state, hash2); assertArrayEquals(hash, hash2); } finally { Sodium.sodium_free(state); } } @Test void checkCryptoHashSha256MultiPart() { byte[] message = "This is a test message".getBytes(UTF_8); byte[] hash = new byte[(int) Sodium.crypto_hash_sha256_bytes()]; int rc = Sodium.crypto_hash_sha256(hash, message, message.length); assertEquals(0, rc); Pointer state = Sodium.sodium_malloc(Sodium.crypto_hash_sha256_statebytes()); try { rc = Sodium.crypto_hash_sha256_init(state); assertEquals(0, rc); byte[] message1 = "This is ".getBytes(UTF_8); Sodium.crypto_hash_sha256_update(state, message1, message1.length); byte[] message2 = "a test message".getBytes(UTF_8); Sodium.crypto_hash_sha256_update(state, message2, message2.length); byte[] hash2 = new byte[(int) Sodium.crypto_hash_sha256_bytes()]; Sodium.crypto_hash_sha256_final(state, hash2); assertArrayEquals(hash, hash2); } finally { Sodium.sodium_free(state); } } } cava-0.6.0/crypto/src/test/java/net/consensys/cava/crypto/sodium/XChaCha20Poly1305Test.java000066400000000000000000000072641341750772100313030ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.crypto.sodium; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; class XChaCha20Poly1305Test { @BeforeAll static void checkAvailable() { assumeTrue(Sodium.isAvailable(), "Sodium native library is not available"); assumeTrue(XChaCha20Poly1305.isAvailable(), "XChaCha20Poly1305 support is not available (requires >= 10.0.12"); } @Test void checkCombinedEncryptDecrypt() { XChaCha20Poly1305.Key key = XChaCha20Poly1305.Key.random(); XChaCha20Poly1305.Nonce nonce = XChaCha20Poly1305.Nonce.random().increment(); byte[] message = "This is a test message".getBytes(UTF_8); byte[] data = "123456".getBytes(UTF_8); byte[] cipherText = XChaCha20Poly1305.encrypt(message, data, key, nonce); byte[] clearText = XChaCha20Poly1305.decrypt(cipherText, data, key, nonce); assertNotNull(clearText); assertArrayEquals(message, clearText); assertNull(XChaCha20Poly1305.decrypt(cipherText, data, key, nonce.increment())); } @Test void checkDetachedEncryptDecrypt() { XChaCha20Poly1305.Key key = XChaCha20Poly1305.Key.random(); XChaCha20Poly1305.Nonce nonce = XChaCha20Poly1305.Nonce.random().increment(); byte[] message = "This is a test message".getBytes(UTF_8); byte[] data = "123456".getBytes(UTF_8); DetachedEncryptionResult result = XChaCha20Poly1305.encryptDetached(message, data, key, nonce); byte[] clearText = XChaCha20Poly1305.decryptDetached(result.cipherTextArray(), result.macArray(), data, key, nonce); assertNotNull(clearText); assertArrayEquals(message, clearText); clearText = XChaCha20Poly1305.decryptDetached(result.cipherTextArray(), result.macArray(), data, key, nonce.increment()); assertNull(clearText); } @Test void checkStreamEncryptDecrypt() { assumeTrue(XChaCha20Poly1305.isSecretStreamAvailable()); XChaCha20Poly1305.Key key = XChaCha20Poly1305.Key.random(); byte[] message1 = "This is the first message".getBytes(UTF_8); byte[] message2 = "This is the second message".getBytes(UTF_8); byte[] message3 = "This is the third message".getBytes(UTF_8); SecretEncryptionStream ses = XChaCha20Poly1305.openEncryptionStream(key); byte[] header = ses.headerArray(); byte[] cipher1 = ses.push(message1); byte[] cipher2 = ses.push(message2); byte[] cipher3 = ses.pushLast(message3); SecretDecryptionStream sds = XChaCha20Poly1305.openDecryptionStream(key, header); assertArrayEquals(message1, sds.pull(cipher1)); assertFalse(sds.isComplete()); assertArrayEquals(message2, sds.pull(cipher2)); assertFalse(sds.isComplete()); assertArrayEquals(message3, sds.pull(cipher3)); assertTrue(sds.isComplete()); } } cava-0.6.0/dependency-versions.gradle000066400000000000000000000036361341750772100176120ustar00rootroot00000000000000dependencyManagement { dependencies { dependency('com.github.jnr:jnr-ffi:2.1.9') dependency('com.github.kstyrc:embedded-redis:0.6') dependency('com.google.code.findbugs:jsr305:3.0.2') dependency('com.google.errorprone:error_prone_core:2.3.2') dependency('com.google.errorprone:javac:9+181-r4173-1') dependency('com.google.guava:guava:27.0.1-jre') dependency('com.h2database:h2:1.4.197') dependency('com.jolbox:bonecp:0.8.0.RELEASE') dependency('com.squareup.okhttp3:okhttp:3.12.0') dependency('com.winterbe:expekt:0.5.0') dependency('io.lettuce:lettuce-core:5.1.3.RELEASE') dependency('io.vertx:vertx-core:3.6.2') dependencySet(group: 'org.antlr', version: '4.7.1') { entry 'antlr4' entry 'antlr4-runtime' } dependency('org.assertj:assertj-core:3.11.1') dependencySet(group: 'org.bouncycastle', version: '1.60') { entry 'bcpkix-jdk15on' entry 'bcprov-jdk15on' } dependency('org.fusesource.leveldbjni:leveldbjni-all:1.8') dependencySet(group: 'org.junit.jupiter', version: '5.3.2') { entry 'junit-jupiter-api' entry 'junit-jupiter-engine' entry 'junit-jupiter-params' } dependency('org.jetbrains:annotations:16.0.3') dependencySet(group: 'org.jetbrains.kotlin', version: '1.3.11') { entry 'kotlin-reflect' entry 'kotlin-stdlib' entry 'kotlin-stdlib-jdk8' } dependencySet(group: 'org.jetbrains.kotlinx', version: '1.0.1') { entry 'kotlinx-coroutines-core' entry 'kotlinx-coroutines-guava' entry 'kotlinx-coroutines-jdk8' } dependencySet(group: 'org.jetbrains.spek', version: '1.1.5') { entry 'spek-api' entry 'spek-junit-platform-engine' } dependencySet(group: 'org.logl', version: '0.3.1') { entry 'logl-api' entry 'logl-logl' } dependency('org.mapdb:mapdb:3.0.7') dependency('org.xerial.snappy:snappy-java:1.1.7.2') } } cava-0.6.0/devp2p/000077500000000000000000000000001341750772100136365ustar00rootroot00000000000000cava-0.6.0/devp2p/build.gradle000066400000000000000000000012671341750772100161230ustar00rootroot00000000000000description = 'Ethereum ÐΞVp2p implementation.' dependencies { compile project(':bytes') compile project(':concurrent') compile project(':concurrent-coroutines') compile project(':crypto') compile project(':kademlia') compile project(':net-coroutines') compile project(':rlp') compile 'io.vertx:vertx-core' compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core' compile 'org.logl:logl-api' testCompile project(':junit') testCompile 'org.bouncycastle:bcprov-jdk15on' testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testCompile 'org.logl:logl-logl' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/devp2p/src/000077500000000000000000000000001341750772100144255ustar00rootroot00000000000000cava-0.6.0/devp2p/src/main/000077500000000000000000000000001341750772100153515ustar00rootroot00000000000000cava-0.6.0/devp2p/src/main/kotlin/000077500000000000000000000000001341750772100166515ustar00rootroot00000000000000cava-0.6.0/devp2p/src/main/kotlin/net/000077500000000000000000000000001341750772100174375ustar00rootroot00000000000000cava-0.6.0/devp2p/src/main/kotlin/net/consensys/000077500000000000000000000000001341750772100214635ustar00rootroot00000000000000cava-0.6.0/devp2p/src/main/kotlin/net/consensys/cava/000077500000000000000000000000001341750772100223755ustar00rootroot00000000000000cava-0.6.0/devp2p/src/main/kotlin/net/consensys/cava/devp2p/000077500000000000000000000000001341750772100235755ustar00rootroot00000000000000cava-0.6.0/devp2p/src/main/kotlin/net/consensys/cava/devp2p/AtomicLongProperty.kt000066400000000000000000000022411341750772100277350ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.devp2p import java.util.concurrent.atomic.AtomicLong import kotlin.reflect.KProperty // Extension methods that allow an AtomicLong to be treated as a Long property internal operator fun AtomicLong.getValue(thisRef: Any?, property: KProperty<*>): Long = this.get() internal operator fun AtomicLong.setValue(thisRef: Any?, property: KProperty<*>, value: Long) { this.set(value) } internal operator fun AtomicLong.inc(): AtomicLong { this.incrementAndGet() return this } internal operator fun AtomicLong.dec(): AtomicLong { this.decrementAndGet() return this } cava-0.6.0/devp2p/src/main/kotlin/net/consensys/cava/devp2p/DiscoveryService.kt000066400000000000000000001012131341750772100274230ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.devp2p import com.google.common.cache.Cache import com.google.common.cache.CacheBuilder import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.ObsoleteCoroutinesApi import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ClosedReceiveChannelException import kotlinx.coroutines.channels.ClosedSendChannelException import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.channels.consumeEach import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.yield import net.consensys.cava.bytes.Bytes32 import net.consensys.cava.concurrent.AsyncCompletion import net.consensys.cava.concurrent.AsyncResult import net.consensys.cava.concurrent.coroutines.CoroutineLatch import net.consensys.cava.concurrent.coroutines.asyncCompletion import net.consensys.cava.concurrent.coroutines.asyncResult import net.consensys.cava.crypto.SECP256K1 import net.consensys.cava.kademlia.orderedInsert import net.consensys.cava.kademlia.xorDistCmp import net.consensys.cava.net.coroutines.CommonCoroutineGroup import net.consensys.cava.net.coroutines.CoroutineChannelGroup import net.consensys.cava.net.coroutines.CoroutineDatagramChannel import org.logl.LogMessage.patternFormat import org.logl.LoggerProvider import java.io.IOException import java.net.InetAddress import java.net.InetSocketAddress import java.net.SocketAddress import java.net.URI import java.nio.ByteBuffer import java.nio.channels.ClosedChannelException import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicLong import kotlin.coroutines.CoroutineContext internal const val PACKET_EXPIRATION_PERIOD_MS: Long = (20 * 1000) // 20 seconds internal const val PACKET_EXPIRATION_CHECK_GRACE_MS: Long = (5 * 1000) // 5 seconds internal const val PEER_VERIFICATION_TIMEOUT_MS: Long = (22 * 1000) // 22 seconds (packet expiration + a little) internal const val PEER_VERIFICATION_RETRY_DELAY_MS: Long = (5 * 60 * 1000) // 5 minutes internal const val BOOTSTRAP_PEER_VERIFICATION_TIMEOUT_MS: Long = (2 * 60 * 1000) // 2 minutes internal const val REFRESH_INTERVAL_MS: Long = (60 * 1000) // 1 minute internal const val PING_RETRIES: Int = 20 internal const val RESEND_DELAY_MS: Long = 1000 // 1 second internal const val RESEND_DELAY_INCREASE_MS: Long = 500 // 500 milliseconds internal const val RESEND_MAX_DELAY_MS: Long = (30 * 1000) // 30 seconds internal const val ENDPOINT_PROOF_LONGEVITY_MS: Long = (12 * 60 * 60 * 1000) // 12 hours internal const val FIND_NODES_CACHE_EXPIRY: Long = (3 * 60 * 1000) // 3 minutes internal const val FIND_NODES_QUERY_GAP_MS: Long = (30 * 1000) // 30 seconds internal const val LOOKUP_RESPONSE_TIMEOUT_MS: Long = 500 // 500 milliseconds /** * An Ethereum ÐΞVp2p discovery service. * * @author Chris Leishman - https://cleishm.github.io/ */ interface DiscoveryService { companion object { internal val CURRENT_TIME_SUPPLIER: () -> Long = { System.currentTimeMillis() } internal val DEFAULT_BUFFER_ALLOCATOR = { ByteBuffer.allocate(Packet.MAX_SIZE) } /** * Start the discovery service. * * @param keyPair the local node's keypair * @param port the port to listen on (defaults to `0`, which will cause a random free port to be chosen) * @param host the host name or IP address of the interface to bind to (defaults to `null`, which will cause the * service to listen on all interfaces * @param bootstrapURIs the URIs for bootstrap nodes * @param peerRepository a [PeerRepository] for obtaining [Peer] instances * @param advertiseAddress the IP address to advertise to peers, or `null` if the address of the first bound * interface should be used. * @param advertiseUdpPort the UDP port to advertise to peers, or `null` if the bound port should to be used. * @param advertiseTcpPort the TCP port to advertise to peers, or `null` if it should be the same as the UDP port. * @param routingTable a [PeerRoutingTable] which handles the ÐΞVp2p routing table * @param packetFilter a filter for incoming packets * @param loggerProvider a provider for a logger * @param channelGroup the [CoroutineChannelGroup] for network channels created by this service * @param bufferAllocator a [ByteBuffer] allocator, which must return buffers of size 1280 bytes or larger * @param timeSupplier a function supplying the current time, in milliseconds since the epoch */ @JvmOverloads fun open( keyPair: SECP256K1.KeyPair, port: Int = 0, host: String? = null, bootstrapURIs: List = emptyList(), peerRepository: PeerRepository = EphemeralPeerRepository(), advertiseAddress: InetAddress? = null, advertiseUdpPort: Int? = null, advertiseTcpPort: Int? = null, routingTable: PeerRoutingTable = DevP2PPeerRoutingTable(keyPair.publicKey()), packetFilter: ((SECP256K1.PublicKey, InetSocketAddress) -> Boolean)? = null, loggerProvider: LoggerProvider = LoggerProvider.nullProvider(), channelGroup: CoroutineChannelGroup = CommonCoroutineGroup, bufferAllocator: () -> ByteBuffer = DEFAULT_BUFFER_ALLOCATOR, timeSupplier: () -> Long = CURRENT_TIME_SUPPLIER ): DiscoveryService { val bindAddress = if (host == null) InetSocketAddress(port) else InetSocketAddress(host, port) return open( keyPair, bindAddress, bootstrapURIs, peerRepository, advertiseAddress, advertiseUdpPort, advertiseTcpPort, routingTable, packetFilter, loggerProvider, channelGroup, bufferAllocator, timeSupplier ) } /** * Start the discovery service. * * @param keyPair the local node's keypair * @param bindAddress the address to listen on * @param bootstrapURIs the URIs for bootstrap nodes * @param peerRepository a [PeerRepository] for obtaining [Peer] instances * @param advertiseAddress the IP address to advertise for incoming packets * @param advertiseUdpPort the UDP port to advertise to peers, or `null` if the bound port should to be used. * @param advertiseTcpPort the TCP port to advertise to peers, or `null` if it should be the same as the UDP port. * @param routingTable a [PeerRoutingTable] which handles the ÐΞVp2p routing table * @param packetFilter a filter for incoming packets * @param loggerProvider a provider for a logger * @param channelGroup the [CoroutineChannelGroup] for network channels created by this service * @param bufferAllocator a [ByteBuffer] allocator, which must return buffers of size 1280 bytes or larger * @param timeSupplier a function supplying the current time, in milliseconds since the epoch */ @JvmOverloads fun open( keyPair: SECP256K1.KeyPair, bindAddress: InetSocketAddress, bootstrapURIs: List = emptyList(), peerRepository: PeerRepository = EphemeralPeerRepository(), advertiseAddress: InetAddress? = null, advertiseUdpPort: Int? = null, advertiseTcpPort: Int? = null, routingTable: PeerRoutingTable = DevP2PPeerRoutingTable(keyPair.publicKey()), packetFilter: ((SECP256K1.PublicKey, InetSocketAddress) -> Boolean)? = null, loggerProvider: LoggerProvider = LoggerProvider.nullProvider(), channelGroup: CoroutineChannelGroup = CommonCoroutineGroup, bufferAllocator: () -> ByteBuffer = DEFAULT_BUFFER_ALLOCATOR, timeSupplier: () -> Long = CURRENT_TIME_SUPPLIER ): DiscoveryService { return CoroutineDiscoveryService( keyPair, bindAddress, bootstrapURIs, advertiseAddress, advertiseUdpPort, advertiseTcpPort, peerRepository, routingTable, packetFilter, loggerProvider, channelGroup, bufferAllocator, timeSupplier ) } } /** * `true` if the service has been shutdown */ val isShutdown: Boolean /** * `true` if the service has terminated */ val isTerminated: Boolean /** * the UDP port that the service is listening on */ val localPort: Int /** * the node id for this node (i.e. it's public key) */ val nodeId: SECP256K1.PublicKey /** * Suspend until the bootstrap peers have been reached, or failed. * * @return the number of bootstrap peers successfully added */ suspend fun awaitBootstrap(): Int /** * Attempt to find a specific peer, or peers close to it. * * @param target the node-id to search for * @return a list of 16 peers, ordered by their distance to the target node-id. */ suspend fun lookup(target: SECP256K1.PublicKey): List /** * Attempt to find a specific peer, or peers close to it asynchronously. * * @param target the node-id to search for * @return a future of a list of 16 peers, ordered by their distance to the target node-id. */ fun lookupAsync(target: SECP256K1.PublicKey): AsyncResult> /** * Request shutdown of this service. The service will terminate at a later time (see [DiscoveryService.awaitTermination]). */ fun shutdown() /** * Suspend until this service has terminated. */ suspend fun awaitTermination() /** * Provide a completion that will complete when the service has terminated. * * @return A completion that will complete when the service has terminated. */ fun awaitTerminationAsync(): AsyncCompletion /** * Shutdown this service immediately. */ fun shutdownNow() val invalidPackets: Long val selfPackets: Long val expiredPackets: Long val filteredPackets: Long val unvalidatedPeerPackets: Long val unexpectedPongs: Long val unexpectedNeighbors: Long } internal class CoroutineDiscoveryService( private val keyPair: SECP256K1.KeyPair, bindAddress: InetSocketAddress, bootstrapURIs: List = emptyList(), advertiseAddress: InetAddress? = null, advertiseUdpPort: Int? = null, advertiseTcpPort: Int? = null, private val peerRepository: PeerRepository = EphemeralPeerRepository(), private val routingTable: PeerRoutingTable = DevP2PPeerRoutingTable(keyPair.publicKey()), private val packetFilter: ((SECP256K1.PublicKey, InetSocketAddress) -> Boolean)? = null, loggerProvider: LoggerProvider = LoggerProvider.nullProvider(), channelGroup: CoroutineChannelGroup = CommonCoroutineGroup, private val bufferAllocator: () -> ByteBuffer = DiscoveryService.DEFAULT_BUFFER_ALLOCATOR, private val timeSupplier: () -> Long = DiscoveryService.CURRENT_TIME_SUPPLIER, private val channel: CoroutineDatagramChannel = CoroutineDatagramChannel.open(channelGroup) ) : DiscoveryService, CoroutineScope { private val logger = loggerProvider.getLogger(DiscoveryService::class.java) private val serviceDescriptor = "ÐΞVp2p discovery " + System.identityHashCode(this) private val selfEndpoint: Endpoint private val job = Job() // override the default exception handler, which dumps to stderr override val coroutineContext: CoroutineContext get() = job + Dispatchers.Default + CoroutineExceptionHandler { _, _ -> } private val activityLatch = CoroutineLatch(1) private val bootstrapperCount: Deferred private val refreshLoop: Job override val isShutdown: Boolean get() = !channel.isOpen override val isTerminated: Boolean get() = activityLatch.isOpen override val localPort: Int get() = channel.localPort override val nodeId: SECP256K1.PublicKey get() = keyPair.publicKey() private val verifyingEndpoints: Cache = CacheBuilder.newBuilder().expireAfterAccess(PEER_VERIFICATION_RETRY_DELAY_MS, TimeUnit.MILLISECONDS).build() private val awaitingPongs = ConcurrentHashMap() private val findNodeStates: Cache = CacheBuilder.newBuilder().expireAfterAccess(FIND_NODES_CACHE_EXPIRY, TimeUnit.MILLISECONDS) .removalListener { it.value.close() } .build() override var invalidPackets: Long by AtomicLong(0) override var selfPackets: Long by AtomicLong(0) override var expiredPackets: Long by AtomicLong(0) override var filteredPackets: Long by AtomicLong(0) override var unvalidatedPeerPackets: Long by AtomicLong(0) override var unexpectedPongs: Long by AtomicLong(0) override var unexpectedNeighbors: Long by AtomicLong(0) init { channel.bind(bindAddress) selfEndpoint = Endpoint( advertiseAddress ?: channel.getAdvertisableAddress()!!, advertiseUdpPort ?: channel.localPort, advertiseTcpPort ) val bootstrapping = bootstrapURIs.map { uri -> activityLatch.countUp() async { try { bootstrapFrom(uri) } finally { activityLatch.countDown() } } } bootstrapperCount = async { bootstrapping.awaitAll().sumBy { success -> if (success) 1 else 0 } } refreshLoop = launch { activityLatch.countUp() try { while (true) { delay(REFRESH_INTERVAL_MS) refresh() } } finally { activityLatch.countDown() } } logger.info("{}: started, listening on {}", serviceDescriptor, channel.localAddress) launch { try { receivePackets() } finally { for (pending in awaitingPongs.values) { pending.complete(null) } awaitingPongs.clear() verifyingEndpoints.invalidateAll() verifyingEndpoints.cleanUp() findNodeStates.invalidateAll() findNodeStates.cleanUp() activityLatch.countDown() } logger.info("{}: terminated", serviceDescriptor) } } private suspend fun bootstrapFrom(uri: URI): Boolean { val (bootstrapNodeId, endpoint) = parseEnodeUri(uri) val peer = peerRepository.get(bootstrapNodeId) val now = timeSupplier() peer.updateEndpoint(endpoint, now) try { val result = withTimeout(BOOTSTRAP_PEER_VERIFICATION_TIMEOUT_MS) { endpointVerification(endpoint, peer).verifyWithRetries() } ?: return false if (result.peer != peer) { logger.warn( "{}: ignoring bootstrap peer {} - responding node used a different node-id", serviceDescriptor, uri ) return false } logger.info("{}: verified bootstrap peer {}", serviceDescriptor, uri) addToRoutingTable(peer) findNodes(peer, nodeId) logger.info("{}: completed bootstrapping from {}", serviceDescriptor, uri) return true } catch (_: TimeoutCancellationException) { logger.warn("{}: timeout verifying bootstrap node {}", serviceDescriptor, uri) return false } } private suspend fun receivePackets() { while (channel.isOpen) { val datagram = bufferAllocator() val address = try { channel.receive(datagram) } catch (e: ClosedChannelException) { break } datagram.flip() // do quick sanity checks and discard bad packets before launching a co-routine if (datagram.limit() < Packet.MIN_SIZE) { logger.debug("{}: ignoring under-sized packet with source {}", serviceDescriptor, address) ++invalidPackets continue } if (datagram.limit() > Packet.MAX_SIZE) { logger.debug("{}: ignoring over-sized packet with source {}", serviceDescriptor, address) ++invalidPackets continue } activityLatch.countUp() val arrivalTime = timeSupplier() val job = launch { try { receivePacket(datagram, address, arrivalTime) } catch (e: Throwable) { logger.error(patternFormat("{}: unexpected error during packet handling", serviceDescriptor), e) } } job.invokeOnCompletion { activityLatch.countDown() } yield() } } override suspend fun awaitBootstrap(): Int = bootstrapperCount.await() override fun shutdown() { if (channel.isOpen) { logger.info("{}: shutdown", serviceDescriptor) } channel.close() refreshLoop.cancel() } override suspend fun awaitTermination() { activityLatch.await() } override fun awaitTerminationAsync(): AsyncCompletion = asyncCompletion { awaitTermination() } override fun shutdownNow() { job.cancel() } @UseExperimental(ObsoleteCoroutinesApi::class) override suspend fun lookup(target: SECP256K1.PublicKey): List { val targetId = target.bytesArray() val results = neighbors(target).toMutableList() // maybe add ourselves to the set val selfPeer = peerRepository.get(nodeId) results.orderedInsert(selfPeer) { a, _ -> targetId.xorDistCmp(a.nodeId.bytesArray(), nodeId.bytesArray()) } results.removeAt(results.lastIndex) val queried = mutableSetOf(selfPeer) while (true) { val toQuery = results.filterNot { p -> queried.contains(p) }.take(3).toList() if (toQuery.isEmpty()) { return results } val nodes = Channel(capacity = Channel.UNLIMITED) toQuery.forEach { p -> findNodes(p, target, nodes) } while (true) { // stop if no more responses are received after the given time val node = withTimeoutOrNull(LOOKUP_RESPONSE_TIMEOUT_MS) { nodes.receiveOrNull() } ?: break val peer = peerRepository.get(node.nodeId) if (!results.contains(peer)) { results.orderedInsert(peer) { a, _ -> targetId.xorDistCmp(a.nodeId.bytesArray(), node.nodeId.bytesArray()) } results.removeAt(results.lastIndex) } } queried.addAll(toQuery) nodes.close() } } override fun lookupAsync(target: SECP256K1.PublicKey) = asyncResult { lookup(target) } private suspend fun refresh() { logger.debug("{}: table refresh triggered", serviceDescriptor) // TODO: instead of a random target, choose a target to optimally fill the peer table lookup(SECP256K1.KeyPair.random().publicKey()) } private suspend fun receivePacket(datagram: ByteBuffer, address: SocketAddress, arrivalTime: Long) { if (address !is InetSocketAddress) { throw IOException("Datagram received from non-inet socket address: " + address.javaClass) } val packet: Packet try { packet = Packet.decodeFrom(datagram) } catch (e: DecodingException) { logger.debug("{}: ignoring invalid packet from {}", serviceDescriptor, address) ++invalidPackets return } if (packet.nodeId == nodeId) { logger.debug("{}: ignoring packet from self", serviceDescriptor) ++selfPackets return } if (packet.isExpired(arrivalTime - PACKET_EXPIRATION_CHECK_GRACE_MS)) { logger.debug("{}: ignoring expired packet", serviceDescriptor) ++expiredPackets return } if (packetFilter?.invoke(packet.nodeId, address) == false) { logger.debug("{}: packet rejected by filter", serviceDescriptor) ++filteredPackets return } when (packet) { is PingPacket -> handlePing(packet, address, arrivalTime) is PongPacket -> handlePong(packet, address, arrivalTime) is FindNodePacket -> handleFindNode(packet, address, arrivalTime) is NeighborsPacket -> handleNeighbors(packet, address) }.let {} // guarantees "when" matching is exhaustive } private suspend fun handlePing(packet: PingPacket, from: InetSocketAddress, arrivalTime: Long) { // COMPATIBILITY: The ping packet should contain the canonical endpoint for the peer, yet it is often observed to // be incorrect (using private-subnet addresses, wildcard addresses, etc). So instead, respond to the source // address of the packet itself. val fromEndpoint = Endpoint(from, packet.from.tcpPort) val peer = peerRepository.get(packet.nodeId) // update the endpoint if the peer does not have one that's been proven var currentEndpoint = peer.updateEndpoint(fromEndpoint, arrivalTime, arrivalTime - ENDPOINT_PROOF_LONGEVITY_MS) if (currentEndpoint.tcpPort != packet.from.tcpPort) { currentEndpoint = peer.updateEndpoint( Endpoint(currentEndpoint.address, currentEndpoint.udpPort, packet.from.tcpPort), arrivalTime ) } val pong = PongPacket.create(keyPair, timeSupplier(), currentEndpoint, packet.hash) sendPacket(from, pong) // https://github.com/ethereum/devp2p/blob/master/discv4.md#ping-packet-0x01 also suggests sending a ping // packet if the peer is unknown, however sending two packets in response to a single incoming would allow a // traffic amplification attack } private suspend fun handlePong(packet: PongPacket, from: InetSocketAddress, arrivalTime: Long) { val pending = awaitingPongs.remove(packet.pingHash) ?: run { logger.debug("{}: received unexpected or late pong from {}", serviceDescriptor, from) ++unexpectedPongs return } val sender = pending.peer // COMPATIBILITY: If the node-id's don't match, the pong should probably be rejected. However, if a different // peer is listening at the same address, it will respond to the ping with its node-id. Instead of rejecting, // accept the pong and update the new peer record with the proven endpoint, preferring to keep its current // tcpPort and otherwise keeping the tcpPort of the original peer. val peer = if (sender.nodeId == packet.nodeId) sender else peerRepository.get(packet.nodeId) val endpoint = if (peer.verifyEndpoint(pending.endpoint, arrivalTime)) { pending.endpoint } else { val endpoint = peer.endpoint?.tcpPort?.let { port -> Endpoint(pending.endpoint.address, pending.endpoint.udpPort, port) } ?: pending.endpoint peer.updateEndpoint(endpoint, arrivalTime) peer.verifyEndpoint(endpoint, arrivalTime) endpoint } if (sender.nodeId == packet.nodeId) { logger.debug("{}: verified peer endpoint {} (node-id: {})", serviceDescriptor, endpoint.address, peer.nodeId) } else { logger.debug( "{}: verified peer endpoint {} (node-id: {} - changed from {})", serviceDescriptor, endpoint.address, peer.nodeId, sender.nodeId ) } pending.complete(VerificationResult(peer, endpoint)) } private suspend fun handleFindNode(packet: FindNodePacket, from: InetSocketAddress, arrivalTime: Long) { // if the peer has not been validated, delay sending neighbors until it is val peer = peerRepository.get(packet.nodeId) val (_, endpoint) = ensurePeerIsValid(peer, from, arrivalTime) ?: run { logger.debug("{}: received findNode from {} which cannot be validated", serviceDescriptor, from) ++unvalidatedPeerPackets return } logger.debug("{}: received findNode from {} for target-id {}", serviceDescriptor, from, packet.target) val nodes = neighbors(packet.target).map { p -> p.toNode() } val address = endpoint.udpSocketAddress NeighborsPacket.createRequired(keyPair, timeSupplier(), nodes).forEach { p -> logger.debug("{}: sending {} neighbors to {}", serviceDescriptor, p.nodes.size, address) sendPacket(address, p) } } private fun handleNeighbors(packet: NeighborsPacket, from: InetSocketAddress) { findNodeStates.getIfPresent(packet.nodeId)?.let { state -> for (node in packet.nodes) { launch { logger.debug("{}: received neighbour {} from {}", serviceDescriptor, node.endpoint.address, from) val neighbor = peerRepository.get(node.nodeId) val now = timeSupplier() neighbor.updateEndpoint(node.endpoint, now, now - ENDPOINT_PROOF_LONGEVITY_MS) withTimeoutOrNull(PEER_VERIFICATION_TIMEOUT_MS) { endpointVerification(node.endpoint, neighbor).verify(now) }?.let { result -> logger.debug( "{}: adding {} to the routing table (node-id: {})", serviceDescriptor, result.endpoint.address, result.peer.nodeId ) addToRoutingTable(result.peer) } } } state.receive(packet.nodes) } ?: run { logger.debug("{}: received unexpected or late neighbors packet from {}", serviceDescriptor, from) ++unexpectedNeighbors } } private fun addToRoutingTable(peer: Peer) { routingTable.add(peer)?.let { contested -> launch { contested.endpoint?.let { endpoint -> withTimeoutOrNull(PEER_VERIFICATION_TIMEOUT_MS) { endpointVerification(endpoint, contested).verify() } } ?: routingTable.evict(contested) } } } private suspend fun ensurePeerIsValid( peer: Peer, address: InetSocketAddress, arrivalTime: Long ): VerificationResult? { val now = timeSupplier() peer.getEndpoint(now - ENDPOINT_PROOF_LONGEVITY_MS)?.let { endpoint -> // already valid return VerificationResult(peer, endpoint) } val endpoint = peer.updateEndpoint( Endpoint(address, peer.endpoint?.tcpPort), arrivalTime, now - ENDPOINT_PROOF_LONGEVITY_MS ) return withTimeoutOrNull(PEER_VERIFICATION_TIMEOUT_MS) { endpointVerification(endpoint, peer).verify(now) } } private fun endpointVerification(endpoint: Endpoint, peer: Peer) = verifyingEndpoints.get(endpoint.udpSocketAddress) { EndpointVerification(endpoint, peer) } // a representation of the state and current action for verifying and endpoint, // to avoid concurrent attempts to verify the same endpoint private inner class EndpointVerification(val endpoint: Endpoint, val peer: Peer) { private val deferred = CompletableDeferred() @Volatile private var active: Job? = null private var nextPingMs: Long = 0 private var retryDelay: Long = 0 suspend fun verify(now: Long = timeSupplier()): VerificationResult? { if (!deferred.isCompleted) { // if not already actively pinging and enough time has passed since the last ping, send a single ping synchronized(this) { if (active?.isCompleted != false && now >= nextPingMs) { nextPingMs = now + RESEND_DELAY_MS launch { sendPing(now) } } } } return deferred.await() } suspend fun verifyWithRetries(): VerificationResult? { if (!deferred.isCompleted) { // if not already actively pinging, start pinging with retries synchronized(this) { if (active?.isCompleted != false) { active = launch { repeat(PING_RETRIES) { delay(nextPingMs - timeSupplier()) nextPingMs = timeSupplier() + RESEND_DELAY_MS retryDelay += RESEND_DELAY_INCREASE_MS if (retryDelay > RESEND_MAX_DELAY_MS) { retryDelay = RESEND_MAX_DELAY_MS } sendPing() } } } } } return deferred.await() } private suspend fun sendPing(now: Long = timeSupplier()) { val pingPacket = PingPacket.create(keyPair, now, selfEndpoint, endpoint) // create local references to be captured in the closure, rather than the whole packet instance val hash = pingPacket.hash val timeout = pingPacket.expiration - now // very unlikely that there is another ping packet created with the same hash yet a different EndpointVerification // instance, but if there is then the first will be waiting on a deferred that never completes and will // eventually time out if (awaitingPongs.put(hash, this) != this) { launch { delay(timeout) awaitingPongs.remove(hash) } sendPacket(endpoint.udpSocketAddress, pingPacket) } } fun complete(result: VerificationResult?): Boolean { active?.cancel() return deferred.complete(result) } } private data class VerificationResult( /** The peer that responded to the verification request. */ val peer: Peer, /** * The endpoint that was verified. * * This will typically be the same as peer.endpoint, but may not be due to concurrent updates. */ val endpoint: Endpoint ) @UseExperimental(ObsoleteCoroutinesApi::class) private suspend fun findNodes(peer: Peer, target: SECP256K1.PublicKey) { // consume all received nodes (and discard), thus suspending until completed Channel(capacity = Channel.CONFLATED).also { findNodes(peer, target, it) }.consumeEach { } } private suspend fun findNodes(peer: Peer, target: SECP256K1.PublicKey, channel: SendChannel) { if (peer.nodeId == nodeId) { // for queries to self, respond directly neighbors(target).map { p -> channel.send(p.toNode()) } return } findNodeStates.get(peer.nodeId) { FindNodeState(peer) }.findNodes(target, channel) } private fun neighbors(target: SECP256K1.PublicKey) = routingTable.nearest(target, DEVP2P_BUCKET_SIZE) private data class FindNodeRequest(val target: SECP256K1.PublicKey, val results: SendChannel) private inner class FindNodeState(val peer: Peer) { // the protocol doesn't have a correlation mechanism between findNode requests and the associated responses, // so requests have to be queued and a delay between them used to try and determine when no more responses // will be sent private val targets = Channel(capacity = Channel.UNLIMITED) private val job: Job @Volatile private var results: SendChannel? = null @Volatile private var lastReceive: Long = 0 init { job = launch { sendLoop() } } @UseExperimental(ExperimentalCoroutinesApi::class) private suspend fun sendLoop() { try { while (true) { val request = targets.receive() if (request.results.isClosedForSend) { continue } val endpoint = peer.endpoint if (endpoint == null) { request.results.close() continue } var now = timeSupplier() results = request.results lastReceive = now val findNodePacket = FindNodePacket.create(keyPair, now, request.target) sendPacket(endpoint.udpSocketAddress, findNodePacket) logger.debug("{}: sent findNode to {} for {}", serviceDescriptor, endpoint.udpSocketAddress, request.target) // wait for results, only moving onto the next when packets stop arriving for a reasonable period do { delay(lastReceive - now + FIND_NODES_QUERY_GAP_MS) now = timeSupplier() } while (now - lastReceive < FIND_NODES_QUERY_GAP_MS) results?.close() results = null // issue a "get" on the state cache, to indicate that this state is still in use val state = findNodeStates.getIfPresent(peer.nodeId) if (state != this) { logger.warn("{}: findNode state for {} has been replaced") close() } } } catch (_: ClosedReceiveChannelException) { // ignore } catch (_: CancellationException) { // ignore } catch (_: ClosedChannelException) { // ignore } catch (e: Exception) { logger.error( patternFormat("{}: Error while sending FindNode requests for peer {}", serviceDescriptor, peer.nodeId), e ) } } suspend fun findNodes(target: SECP256K1.PublicKey, channel: SendChannel) { targets.send(FindNodeRequest(target, channel)) } fun receive(nodes: List) { results?.let { channel -> lastReceive = timeSupplier() try { nodes.forEach { node -> channel.offer(node) } } catch (_: ClosedSendChannelException) { results = null } } } fun close() { job.cancel() targets.close() results?.close() while (true) { val request = targets.poll() ?: break request.results.close() } } } private suspend fun sendPacket(address: InetSocketAddress, packet: Packet) { val buffer = bufferAllocator() packet.encodeTo(buffer) buffer.flip() channel.send(buffer, address) } } cava-0.6.0/devp2p/src/main/kotlin/net/consensys/cava/devp2p/Endpoint.kt000066400000000000000000000101441341750772100257150ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.devp2p import net.consensys.cava.rlp.RLPException import net.consensys.cava.rlp.RLPReader import net.consensys.cava.rlp.RLPWriter import java.net.InetAddress import java.net.InetSocketAddress import java.net.UnknownHostException private fun parseInetAddress(address: String): InetAddress { require(Character.digit(address[0], 16) != -1 || address[0] == ':') { "address should be a literal IP address, got $address" } return InetAddress.getByName(address) } /** * An Ethereum node endpoint. * * @constructor Create a new endpoint. * @param address the InetAddress * @param udpPort the UDP port for the endpoint * @param tcpPort the TCP port for the endpoint or `null` if no TCP port is known * @throws IllegalArgumentException if either port is out of range */ data class Endpoint( val address: InetAddress, val udpPort: Int = DEFAULT_PORT, val tcpPort: Int? = null ) { /** * Create a new endpoint. * * @param address the IP string literal * @param udpPort the UDP port for the endpoint * @param tcpPort the TCP port for the endpoint or `null` if no TCP port is known * @throws IllegalArgumentException if the address isn't an IP address, or either port is out of range */ constructor(address: String, udpPort: Int = DEFAULT_PORT, tcpPort: Int? = null) : this(parseInetAddress(address), udpPort, tcpPort) /** * Create a new endpoint. * * @param address an InetSocketAddress, containing the IP address the UDP port */ constructor(address: InetSocketAddress, tcpPort: Int? = null) : this(address.address, address.port, tcpPort) companion object { /** * The default port used by Ethereum DevP2P. */ const val DEFAULT_PORT = 30303 /** * Create an Endpoint by reading fields from the RLP input stream. * * If the fields are wrapped into an RLP list, use `reader.readList` to unwrap before calling this method. * * @param reader the RLP input stream from which to read * @return the decoded endpoint * @throws RLPException if the RLP source does not decode to a valid endpoint */ fun readFrom(reader: RLPReader): Endpoint { val addr: InetAddress try { addr = InetAddress.getByAddress(reader.readValue().toArrayUnsafe()) } catch (e: UnknownHostException) { throw RLPException(e) } val udpPort = reader.readInt() // Some implementations seem to send packets that either do not have the TCP port field, or to have an // RLP NULL value for it. var tcpPort: Int? = null if (!reader.isComplete) { tcpPort = reader.readInt() if (tcpPort == 0) { tcpPort = null } } return Endpoint(addr.hostAddress, udpPort, tcpPort) } } init { require(udpPort in 1..65535) { "udpPort should be between 1 and 65535, got $udpPort" } require(tcpPort == null || tcpPort in 1..65535) { "tcpPort should be between 1 and 65535, got $tcpPort" } } val udpSocketAddress: InetSocketAddress = InetSocketAddress(address, udpPort) val tcpSocketAddress: InetSocketAddress? = if (tcpPort != null) InetSocketAddress(address, tcpPort) else null /** * Write this endpoint to an RLP output. * * @param writer the RLP writer */ internal fun writeTo(writer: RLPWriter) { writer.writeByteArray(address.address) writer.writeInt(udpPort) writer.writeInt(tcpPort ?: 0) } // rough over-estimate, assuming maximum size encoding for the port numbers internal fun rlpSize(): Int = 1 + address.address.size + 2 * (1 + 2) } cava-0.6.0/devp2p/src/main/kotlin/net/consensys/cava/devp2p/EnodeUri.kt000066400000000000000000000040151341750772100256470ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.devp2p import net.consensys.cava.bytes.Bytes import net.consensys.cava.crypto.SECP256K1 import java.net.URI import java.util.regex.Pattern private val DISCPORT_QUERY_STRING_REGEX = Pattern.compile(".*discport=([^&]+).*") /** * The components of an enode URI. */ data class EnodeUriComponents(val nodeId: SECP256K1.PublicKey, val endpoint: Endpoint) /** * Parse an enode URI. * * @return the node id and the endpoint * @throws IllegalArgumentException if the uri is not a valid enode URI */ fun parseEnodeUri(uri: URI): EnodeUriComponents { require("enode" == uri.scheme) { "URI must be an enode:// uri" } require(uri.userInfo != null) { "URI must have a node id" } val nodeId = SECP256K1.PublicKey.fromBytes(Bytes.fromHexString(uri.userInfo)) var tcpPort = Endpoint.DEFAULT_PORT if (uri.port >= 0) { tcpPort = uri.port } // If TCP and UDP ports differ, expect a query param 'discport' with the UDP port. // See https://github.com/ethereum/wiki/wiki/enode-url-format var udpPort = tcpPort val query = uri.query if (query != null) { val matcher = DISCPORT_QUERY_STRING_REGEX.matcher(query) if (matcher.matches()) { try { udpPort = Integer.parseInt(matcher.group(1)) } catch (e: NumberFormatException) { throw IllegalArgumentException("Invalid discport query parameter") } } } return EnodeUriComponents( nodeId, Endpoint(uri.host, udpPort, tcpPort) ) } cava-0.6.0/devp2p/src/main/kotlin/net/consensys/cava/devp2p/Node.kt000066400000000000000000000025611341750772100250260ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.devp2p import net.consensys.cava.crypto.SECP256K1 import net.consensys.cava.rlp.RLPReader import net.consensys.cava.rlp.RLPWriter internal data class Node( val endpoint: Endpoint, val nodeId: SECP256K1.PublicKey ) { companion object { fun readFrom(reader: RLPReader): Node { val endpoint = Endpoint.readFrom(reader) val nodeId = SECP256K1.PublicKey.fromBytes(reader.readValue()) return Node(endpoint, nodeId) } } internal fun writeTo(writer: RLPWriter) { endpoint.writeTo(writer) writer.writeValue(nodeId.bytes()) } internal fun rlpSize(): Int = 1 + endpoint.rlpSize() + 3 + 64 } internal fun Peer.toNode(): Node = endpoint?.let { endpoint -> Node(endpoint, nodeId) } ?: throw IllegalArgumentException("Peer has no endpoint") cava-0.6.0/devp2p/src/main/kotlin/net/consensys/cava/devp2p/Packet.kt000066400000000000000000000262741341750772100253570ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.devp2p import net.consensys.cava.bytes.Bytes import net.consensys.cava.bytes.Bytes32 import net.consensys.cava.crypto.Hash import net.consensys.cava.crypto.SECP256K1 import net.consensys.cava.rlp.RLP import net.consensys.cava.rlp.RLPException import net.consensys.cava.rlp.RLPWriter import java.nio.ByteBuffer internal class DecodingException(message: String, cause: Throwable? = null) : Exception(message, cause) internal data class SigHash(val signature: SECP256K1.Signature, val hash: Bytes32) private fun msecToSec(time: Long) = (time + 999) / 1000 private fun secToMsec(time: Long) = time * 1000 internal sealed class Packet( val nodeId: SECP256K1.PublicKey, private val signature: SECP256K1.Signature, val hash: Bytes32, val expiration: Long ) { companion object { const val MIN_SIZE = 104 const val MAX_SIZE = 1280 private const val HASH_INDEX = 0 private const val SIGNATURE_INDEX = 32 private const val PACKET_TYPE_INDEX = 97 private const val PACKET_DATA_INDEX = 98 fun decodeFrom(datagram: ByteBuffer) = decodeFrom(Bytes.wrapByteBuffer(datagram)) fun decodeFrom(datagram: Bytes): Packet { val typeByte = datagram.get(PACKET_TYPE_INDEX) val packetType = PacketType.forType(typeByte) ?: throw DecodingException("Unrecognized packet type: $typeByte") val signature = SECP256K1.Signature.fromBytes( datagram.slice(SIGNATURE_INDEX, PACKET_TYPE_INDEX - SIGNATURE_INDEX) ) val publicKey = SECP256K1.PublicKey.recoverFromSignature( datagram.slice(PACKET_TYPE_INDEX, datagram.size() - PACKET_TYPE_INDEX), signature ) ?: throw DecodingException("Invalid packet signature") val hash = Bytes32.wrap( datagram.slice( HASH_INDEX, SIGNATURE_INDEX ) ) if (Hash.keccak256(datagram.slice(SIGNATURE_INDEX)) != hash) { throw DecodingException("Invalid packet hash") } return packetType.decode(datagram.slice(PACKET_DATA_INDEX), hash, publicKey, signature) } @JvmStatic protected fun expirationFor(now: Long) = now + PACKET_EXPIRATION_PERIOD_MS @JvmStatic protected fun createSignature( packetType: PacketType, keyPair: SECP256K1.KeyPair, encoder: (RLPWriter) -> Unit ): SigHash { val typeByte = Bytes.of(packetType.typeId) val dataBytes = RLP.encodeList { writer -> encoder(writer) } val payloadBytes = Bytes.wrap(typeByte, dataBytes) val signature = SECP256K1.sign(payloadBytes, keyPair) val hash = Hash.keccak256(Bytes.wrap(signature.bytes(), payloadBytes)) return SigHash(signature, hash) } } fun isExpired(now: Long): Boolean = expiration <= now abstract fun encodeTo(dst: ByteBuffer): ByteBuffer protected fun encodeTo(dst: ByteBuffer, packetType: PacketType, contentWriter: (RLPWriter) -> Unit): ByteBuffer { hash.appendTo(dst) signature.bytes().appendTo(dst) dst.put(packetType.typeId) RLP.encodeListTo(dst, contentWriter) return dst } } internal class PingPacket private constructor( nodeId: SECP256K1.PublicKey, signature: SECP256K1.Signature, hash: Bytes32, val from: Endpoint, val to: Endpoint, expiration: Long ) : Packet(nodeId, signature, hash, expiration) { companion object { private const val VERSION = 4 fun create(keyPair: SECP256K1.KeyPair, now: Long, from: Endpoint, to: Endpoint): PingPacket { val expiration = expirationFor(now) val sigHash = createSignature( PacketType.PING, keyPair ) { writer -> encodeTo(writer, from, to, expiration) } return PingPacket( keyPair.publicKey(), sigHash.signature, sigHash.hash, from, to, expiration ) } fun decode( payload: Bytes, hash: Bytes32, publicKey: SECP256K1.PublicKey, signature: SECP256K1.Signature ): PingPacket { try { return RLP.decodeList(payload) { reader -> val version = reader.readInt() val from = reader.readList { r -> Endpoint.readFrom(r) } val to = reader.readList { r -> Endpoint.readFrom(r) } val expiration = reader.readLong() // seconds if (version < VERSION) { throw DecodingException("Unexpected version $VERSION in ping") } PingPacket(publicKey, signature, hash, from, to, secToMsec(expiration)) } } catch (e: RLPException) { throw DecodingException("Invalid ping packet", e) } } private fun encodeTo(writer: RLPWriter, from: Endpoint, to: Endpoint, expiration: Long) { writer.writeInt(VERSION) writer.writeList { w -> from.writeTo(w) } writer.writeList { w -> to.writeTo(w) } writer.writeLong(msecToSec(expiration)) // write in seconds } } override fun encodeTo(dst: ByteBuffer) = encodeTo(dst, PacketType.PING) { writer -> encodeTo(writer, from, to, expiration) } } internal class PongPacket private constructor( nodeId: SECP256K1.PublicKey, signature: SECP256K1.Signature, hash: Bytes32, val to: Endpoint, val pingHash: Bytes32, expiration: Long ) : Packet(nodeId, signature, hash, expiration) { companion object { fun create(keyPair: SECP256K1.KeyPair, now: Long, to: Endpoint, pingHash: Bytes32): PongPacket { val expiration = expirationFor(now) val sigHash = createSignature( PacketType.PONG, keyPair ) { writer -> encodeTo(writer, to, pingHash, expiration) } return PongPacket( keyPair.publicKey(), sigHash.signature, sigHash.hash, to, pingHash, expiration ) } fun decode( payload: Bytes, hash: Bytes32, publicKey: SECP256K1.PublicKey, signature: SECP256K1.Signature ): PongPacket { try { return RLP.decodeList(payload) { reader -> val to = reader.readList { r -> Endpoint.readFrom(r) } val pingHash = Bytes32.wrap(reader.readValue()) val expiration = reader.readLong() // seconds PongPacket(publicKey, signature, hash, to, pingHash, secToMsec(expiration)) } } catch (e: RLPException) { throw DecodingException("Invalid pong packet", e) } } private fun encodeTo(writer: RLPWriter, to: Endpoint, pingHash: Bytes32, expiration: Long) { writer.writeList { w -> to.writeTo(w) } writer.writeValue(pingHash) writer.writeLong(msecToSec(expiration)) } } override fun encodeTo(dst: ByteBuffer) = encodeTo(dst, PacketType.PONG) { writer -> encodeTo(writer, to, pingHash, expiration) } } internal class FindNodePacket private constructor( nodeId: SECP256K1.PublicKey, signature: SECP256K1.Signature, hash: Bytes32, val target: SECP256K1.PublicKey, expiration: Long ) : Packet(nodeId, signature, hash, expiration) { companion object { fun create(keyPair: SECP256K1.KeyPair, now: Long, target: SECP256K1.PublicKey): FindNodePacket { val expiration = expirationFor(now) val sigHash = createSignature( PacketType.FIND_NODE, keyPair ) { writer -> encodeTo(writer, target, expiration) } return FindNodePacket( keyPair.publicKey(), sigHash.signature, sigHash.hash, target, expiration ) } fun decode( payload: Bytes, hash: Bytes32, publicKey: SECP256K1.PublicKey, signature: SECP256K1.Signature ): FindNodePacket { try { return RLP.decodeList(payload) { reader -> val target = SECP256K1.PublicKey.fromBytes(reader.readValue()) val expiration = reader.readLong() FindNodePacket(publicKey, signature, hash, target, secToMsec(expiration)) } } catch (e: RLPException) { throw DecodingException("Invalid find nodes packet", e) } } private fun encodeTo(writer: RLPWriter, target: SECP256K1.PublicKey, expiration: Long) { writer.writeValue(target.bytes()) writer.writeLong(msecToSec(expiration)) } } override fun encodeTo(dst: ByteBuffer) = encodeTo(dst, PacketType.FIND_NODE) { writer -> encodeTo(writer, target, expiration) } } internal class NeighborsPacket private constructor( nodeId: SECP256K1.PublicKey, signature: SECP256K1.Signature, hash: Bytes32, val nodes: List, expiration: Long ) : Packet(nodeId, signature, hash, expiration) { companion object { // an over-estimate of the minimum size, based on a full 64-bit expiration time and a full list length prefix internal const val RLP_MIN_SIZE = 109 fun create(keyPair: SECP256K1.KeyPair, now: Long, nodes: List): NeighborsPacket { val expiration = expirationFor(now) val sigHash = createSignature( PacketType.NEIGHBORS, keyPair ) { writer -> encodeTo(writer, nodes, expiration) } return NeighborsPacket( keyPair.publicKey(), sigHash.signature, sigHash.hash, nodes, expiration ) } fun createRequired(keyPair: SECP256K1.KeyPair, now: Long, nodes: List): List { val result = mutableListOf() var nodeSubset = mutableListOf() var size = RLP_MIN_SIZE for (node in nodes) { val nodeSize = node.rlpSize() size += nodeSize if (size > MAX_SIZE) { result.add(create(keyPair, now, nodeSubset)) nodeSubset = mutableListOf() size = RLP_MIN_SIZE + nodeSize } nodeSubset.add(node) } result.add(create(keyPair, now, nodeSubset)) return result } fun decode( payload: Bytes, hash: Bytes32, publicKey: SECP256K1.PublicKey, signature: SECP256K1.Signature ): NeighborsPacket { try { return RLP.decodeList(payload) { reader -> val nodes = mutableListOf() reader.readList { r -> while (!r.isComplete) { val node = r.readList { nr -> Node.readFrom(nr) } nodes.add(node) } } val expiration = reader.readLong() NeighborsPacket(publicKey, signature, hash, nodes, secToMsec(expiration)) } } catch (e: RLPException) { throw DecodingException("Invalid nodes packet", e) } } private fun encodeTo(writer: RLPWriter, nodes: List, expiration: Long) { writer.writeList { w -> nodes.forEach { node -> w.writeList { nw -> node.writeTo(nw) } } } writer.writeLong(msecToSec(expiration)) } } override fun encodeTo(dst: ByteBuffer) = encodeTo(dst, PacketType.NEIGHBORS) { writer -> encodeTo(writer, nodes, expiration) } } cava-0.6.0/devp2p/src/main/kotlin/net/consensys/cava/devp2p/PacketType.kt000066400000000000000000000045241341750772100262130ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.devp2p import net.consensys.cava.bytes.Bytes import net.consensys.cava.bytes.Bytes32 import net.consensys.cava.crypto.SECP256K1 internal enum class PacketType( val typeId: Byte ) { PING(0x01) { override fun decode( payload: Bytes, hash: Bytes32, publicKey: SECP256K1.PublicKey, signature: SECP256K1.Signature ) = PingPacket.decode(payload, hash, publicKey, signature) }, PONG(0x02) { override fun decode( payload: Bytes, hash: Bytes32, publicKey: SECP256K1.PublicKey, signature: SECP256K1.Signature ) = PongPacket.decode(payload, hash, publicKey, signature) }, FIND_NODE(0x03) { override fun decode( payload: Bytes, hash: Bytes32, publicKey: SECP256K1.PublicKey, signature: SECP256K1.Signature ) = FindNodePacket.decode(payload, hash, publicKey, signature) }, NEIGHBORS(0x04) { override fun decode( payload: Bytes, hash: Bytes32, publicKey: SECP256K1.PublicKey, signature: SECP256K1.Signature ) = NeighborsPacket.decode(payload, hash, publicKey, signature) }; companion object { private const val MAX_VALUE: Byte = 0x7f private val INDEX = arrayOfNulls(MAX_VALUE.toInt()) init { // populate an array by packet type id for index-based lookup in `forType(Byte)` PacketType.values().forEach { type -> INDEX[type.typeId.toInt()] = type } } fun forType(typeId: Byte): PacketType? { return INDEX[typeId.toInt()] } } init { require(typeId <= PacketType.MAX_VALUE) { "Packet typeId must be in range [0x00, 0x80)" } } abstract fun decode( payload: Bytes, hash: Bytes32, publicKey: SECP256K1.PublicKey, signature: SECP256K1.Signature ): Packet } cava-0.6.0/devp2p/src/main/kotlin/net/consensys/cava/devp2p/Peer.kt000066400000000000000000000066711341750772100250420ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.devp2p import net.consensys.cava.crypto.SECP256K1 /** * An Ethereum P2P network peer. */ interface Peer { /** * The nodeId for this peer. */ val nodeId: SECP256K1.PublicKey /** * The endpoint for communicating with this peer, if known. */ val endpoint: Endpoint? /** * The last time the current endpoint of this peer was verified, in milliseconds since the epoch. * * Endpoint is verified by a ping/pong cycle: https://github.com/ethereum/devp2p/blob/master/discv4.md#endpoint-proof */ val lastVerified: Long? /** * The time this peer was last seen at its current endpoint, in milliseconds since the epoch. */ val lastSeen: Long? /** * Get the endpoint for this peer, if it has been verified on or after a specified time. * * @param ifVerifiedOnOrAfter the earliest time, in milliseconds since the epoch, when the * endpoint of this peer must have been verified * @return the endpoint of this peer, if it has been verified on or after the specified time */ fun getEndpoint(ifVerifiedOnOrAfter: Long): Endpoint? /** * Update the peer with a new endpoint. * * If successful, the [endpoint] property will be set, the [lastSeen] timestamp will be updated. If the IP address * or UDP port of the endpoint was changed, then the [lastVerified] timestamp will be cleared. * * @param endpoint the endpoint for the peer * @param time the time this endpoint information was determined, in milliseconds since the epoch * @param ifVerifiedBefore the latest allowable time, in milliseconds since the epoch, when this peer was last * verified at its current endpoint. If this peers endpoint was verified after this time, the endpoint * will not be updated. If `null`, then no check will be made and the endpoint will always be updated. * @return the resulting endpoint of the peer */ fun updateEndpoint(endpoint: Endpoint, time: Long, ifVerifiedBefore: Long? = null): Endpoint /** * Set the [lastVerified] and [lastSeen] time to the provided time, if the endpoint matches. * * Will only update [lastVerified] and/or [lastSeen] if the new time is more recent than their current values. * * @param endpoint the endpoint that was verified, which must match this peer's endpoint * @param time the time when the endpoint was verified, in milliseconds since the epoch * @return `true` if the endpoint matched the endpoint of this peer */ fun verifyEndpoint(endpoint: Endpoint, time: Long): Boolean /** * Set the [lastSeen] time to the current time. * * Will only update [lastSeen] if the new time is more recent than their current values. * * @param time the time when the endpoint was verified, in milliseconds since the epoch * @throws IllegalStateException if there is no endpoint for this peer */ fun seenAt(time: Long) } cava-0.6.0/devp2p/src/main/kotlin/net/consensys/cava/devp2p/PeerRepository.kt000066400000000000000000000132051341750772100271310ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.devp2p import kotlinx.coroutines.GlobalScope import net.consensys.cava.concurrent.AsyncResult import net.consensys.cava.concurrent.coroutines.asyncResult import net.consensys.cava.crypto.SECP256K1 import java.net.URI import java.util.concurrent.ConcurrentHashMap /** * A repository of peers in an Ethereum network. * * Conceptually, this repository stores information about all peers in an Ethereum network, hence the * retrieval methods always return a valid [Peer]. However, the [Peer] objects are only generated on demand and * may be purged from underlying storage if they can be recreated easily. */ interface PeerRepository { /** * Get a peer. * * @param nodeId the node id * @return the peer */ suspend fun get(nodeId: SECP256K1.PublicKey): Peer /** * Get a peer. * * @param nodeId the node id * @return the peer */ fun getAsync(nodeId: SECP256K1.PublicKey): AsyncResult /** * Get a Peer based on a URI. * * The returned peer will use the endpoint from the URI, unless the peer is already active, in * which case its endpoint will be unchanged. * * @param uri the enode URI * @return the peer * @throws IllegalArgumentException if the URI is not a valid enode URI */ suspend fun get(uri: URI): Peer /** * Get a Peer based on a URI. * * The returned peer will use the endpoint from the URI, unless the peer is already active, in * which case its endpoint will be unchanged. * * @param uri the enode URI * @return the peer * @throws IllegalArgumentException if the URI is not a valid enode URI */ fun getAsync(uri: URI): AsyncResult /** * Get a Peer based on a URI string. * * The returned peer will use the endpoint from the URI, unless the peer is already active, in * which case its endpoint will be unchanged. * * @param uri the enode URI * @return the peer * @throws IllegalArgumentException if the URI is not a valid enode URI */ suspend fun get(uri: String) = get(URI.create(uri)) /** * Get a Peer based on a URI string. * * The returned peer will use the endpoint from the URI, unless the peer is already active, in * which case its endpoint will be unchanged. * * @param uri the enode URI * @return the peer * @throws IllegalArgumentException if the URI is not a valid enode URI */ fun getAsync(uri: String): AsyncResult } /** * An in-memory peer repository. * * Note: as the storage is in-memory, no retrieval methods in this implementation will suspend. */ class EphemeralPeerRepository : PeerRepository { private val peers = ConcurrentHashMap() override suspend fun get(nodeId: SECP256K1.PublicKey) = peers.compute(nodeId) { _, peer -> peer ?: EphemeralPeer(nodeId) } as Peer override fun getAsync(nodeId: SECP256K1.PublicKey): AsyncResult = GlobalScope.asyncResult { get(nodeId) } override suspend fun get(uri: URI): Peer { val (nodeId, endpoint) = parseEnodeUri(uri) val peer = get(nodeId) as EphemeralPeer if (peer.endpoint == null) { synchronized(peer) { if (peer.endpoint == null) { peer.endpoint = endpoint } } } return peer } override fun getAsync(uri: URI): AsyncResult = GlobalScope.asyncResult { get(uri) } override fun getAsync(uri: String): AsyncResult = GlobalScope.asyncResult { get(uri) } private inner class EphemeralPeer( override val nodeId: SECP256K1.PublicKey, knownEndpoint: Endpoint? = null ) : Peer { @Volatile override var endpoint: Endpoint? = knownEndpoint @Synchronized override fun getEndpoint(ifVerifiedOnOrAfter: Long): Endpoint? { if ((lastVerified ?: 0) >= ifVerifiedOnOrAfter) { return this.endpoint } return null } @Volatile override var lastVerified: Long? = null @Volatile override var lastSeen: Long? = null @Synchronized override fun updateEndpoint(endpoint: Endpoint, time: Long, ifVerifiedBefore: Long?): Endpoint { val currentEndpoint = this.endpoint if (currentEndpoint == endpoint) { this.seenAt(time) return currentEndpoint } if (currentEndpoint == null || ifVerifiedBefore == null || (lastVerified ?: 0) < ifVerifiedBefore) { if (currentEndpoint?.address != endpoint.address || currentEndpoint.udpPort != endpoint.udpPort) { lastVerified = null } this.endpoint = endpoint this.seenAt(time) return endpoint } return currentEndpoint } @Synchronized override fun verifyEndpoint(endpoint: Endpoint, time: Long): Boolean { if (endpoint != this.endpoint) { return false } seenAt(time) if ((lastVerified ?: 0) < time) { lastVerified = time } return true } @Synchronized override fun seenAt(time: Long) { if (this.endpoint == null) { throw IllegalStateException("Peer has no endpoint") } if ((lastSeen ?: 0) < time) { lastSeen = time } } } } cava-0.6.0/devp2p/src/main/kotlin/net/consensys/cava/devp2p/PeerRoutingTable.kt000066400000000000000000000064301341750772100273530ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.devp2p import com.google.common.cache.Cache import com.google.common.cache.CacheBuilder import net.consensys.cava.crypto.Hash.keccak256 import net.consensys.cava.crypto.SECP256K1 import net.consensys.cava.kademlia.KademliaRoutingTable /** * A routing table for ÐΞVp2p peers. */ interface PeerRoutingTable : Set { /** * Return the nearest nodes to a target id, in order from closest to furthest. * * The sort order is the log distance from the target to the node-id's of the Peers. * * @param targetId the id to find nodes nearest to * @param limit the maximum number of nodes to return * @return a list of nodes from the routing table */ fun nearest(targetId: SECP256K1.PublicKey, limit: Int): List /** * Add a node to the table. * * @param node the node to add * @return `null` if the node was successfully added to the table (or already in the table). Otherwise, a node * will be returned that is a suitable candidate for eviction, and the provided node will be stored in * the replacements list. */ fun add(node: Peer): Peer? /** * Remove a node from the table, potentially adding an alternative from the replacement cache. * * @param node the node to evict * @param `true` if the node was removed */ fun evict(node: Peer): Boolean } const val DEVP2P_BUCKET_SIZE = 16 /** * A Peer routing table for the Ethereum ÐΞVp2p network. * * This is an implementation of a [KademliaRoutingTable] using keccak256 hashed node ids and a k-bucket size of 6. * * @constructor Create a new ÐΞVp2p routing table. * @param selfId the ID of the local node */ class DevP2PPeerRoutingTable(selfId: SECP256K1.PublicKey) : PeerRoutingTable { private val idHashCache: Cache = CacheBuilder.newBuilder().maximumSize((1L + 256) * 16).weakKeys().build() private val table = KademliaRoutingTable( selfId = hashForId(selfId), k = DEVP2P_BUCKET_SIZE, nodeId = { p -> hashForId(p.nodeId) }) override val size: Int get() = table.size override fun contains(element: Peer): Boolean = table.contains(element) override fun containsAll(elements: Collection): Boolean = table.containsAll(elements) override fun isEmpty(): Boolean = table.isEmpty() override fun iterator(): Iterator = table.iterator() override fun nearest(targetId: SECP256K1.PublicKey, limit: Int): List = table.nearest(hashForId(targetId), limit) override fun add(node: Peer): Peer? = table.add(node) override fun evict(node: Peer): Boolean = table.evict(node) private fun hashForId(id: SECP256K1.PublicKey): ByteArray = idHashCache.get(id) { keccak256(id.bytesArray()) } } cava-0.6.0/devp2p/src/test/000077500000000000000000000000001341750772100154045ustar00rootroot00000000000000cava-0.6.0/devp2p/src/test/java/000077500000000000000000000000001341750772100163255ustar00rootroot00000000000000cava-0.6.0/devp2p/src/test/java/net/000077500000000000000000000000001341750772100171135ustar00rootroot00000000000000cava-0.6.0/devp2p/src/test/java/net/consensys/000077500000000000000000000000001341750772100211375ustar00rootroot00000000000000cava-0.6.0/devp2p/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100220515ustar00rootroot00000000000000cava-0.6.0/devp2p/src/test/java/net/consensys/cava/devp2p/000077500000000000000000000000001341750772100232515ustar00rootroot00000000000000cava-0.6.0/devp2p/src/test/java/net/consensys/cava/devp2p/DiscoveryServiceJavaTest.java000066400000000000000000000055121341750772100310510ustar00rootroot00000000000000/* * Copyright 2019 ConsenSys AG. * * 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.devp2p; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.concurrent.AsyncCompletion; import net.consensys.cava.concurrent.AsyncResult; import net.consensys.cava.crypto.SECP256K1; import net.consensys.cava.junit.BouncyCastleExtension; import java.net.URI; import java.util.Collections; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(BouncyCastleExtension.class) class DiscoveryServiceJavaTest { @Test void setUpAndShutDownAsync() throws Exception { DiscoveryService service = DiscoveryService.Companion.open(SECP256K1.KeyPair.random()); service.shutdown(); AsyncCompletion completion = service.awaitTerminationAsync(); completion.join(); assertTrue(completion.isDone()); } @Test void lookupAsync() throws Exception { DiscoveryService service = DiscoveryService.Companion.open(SECP256K1.KeyPair.random()); AsyncResult> result = service.lookupAsync(SECP256K1.KeyPair.random().publicKey()); List peers = result.get(); service.shutdown(); assertTrue(peers.isEmpty()); } @Test void managePeerRepository() throws Exception { SECP256K1.KeyPair peerKeyPair = SECP256K1.KeyPair.random(); EphemeralPeerRepository repository = new EphemeralPeerRepository(); DiscoveryService service = DiscoveryService.Companion.open( SECP256K1.KeyPair.random(), 0, "localhost", Collections.singletonList(URI.create("enode://" + peerKeyPair.publicKey().toHexString() + "@127.0.0.1:10000")), repository); AsyncResult result = repository.getAsync(peerKeyPair.publicKey()); assertEquals(peerKeyPair.publicKey(), result.get().getNodeId()); AsyncResult byURI = repository.getAsync(URI.create("enode://" + peerKeyPair.publicKey().toHexString() + "@127.0.0.1:10000")); assertEquals(peerKeyPair.publicKey(), byURI.get().getNodeId()); AsyncResult byURIString = repository.getAsync("enode://" + peerKeyPair.publicKey().toHexString() + "@127.0.0.1:10000"); assertEquals(peerKeyPair.publicKey(), byURIString.get().getNodeId()); service.shutdown(); } } cava-0.6.0/devp2p/src/test/kotlin/000077500000000000000000000000001341750772100167045ustar00rootroot00000000000000cava-0.6.0/devp2p/src/test/kotlin/net/000077500000000000000000000000001341750772100174725ustar00rootroot00000000000000cava-0.6.0/devp2p/src/test/kotlin/net/consensys/000077500000000000000000000000001341750772100215165ustar00rootroot00000000000000cava-0.6.0/devp2p/src/test/kotlin/net/consensys/cava/000077500000000000000000000000001341750772100224305ustar00rootroot00000000000000cava-0.6.0/devp2p/src/test/kotlin/net/consensys/cava/devp2p/000077500000000000000000000000001341750772100236305ustar00rootroot00000000000000cava-0.6.0/devp2p/src/test/kotlin/net/consensys/cava/devp2p/DiscoveryServiceTest.kt000066400000000000000000000264161341750772100303310ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.devp2p import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import net.consensys.cava.crypto.SECP256K1 import net.consensys.cava.junit.BouncyCastleExtension import net.consensys.cava.net.coroutines.CoroutineDatagramChannel import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.logl.Level import org.logl.logl.SimpleLogger import java.net.InetAddress import java.net.InetSocketAddress import java.net.SocketAddress import java.net.URI import java.nio.ByteBuffer private suspend fun CoroutineDatagramChannel.send(packet: Packet, address: SocketAddress): Int { val buffer = packet.encodeTo(ByteBuffer.allocate(2048)) buffer.flip() return send(buffer, address) } private suspend fun CoroutineDatagramChannel.receivePacket(): Packet { val buffer = ByteBuffer.allocate(2048) receive(buffer) buffer.flip() return Packet.decodeFrom(buffer) } @ExtendWith(BouncyCastleExtension::class) internal class DiscoveryServiceTest { @Test fun shouldStartAndShutdownService() { val discoveryService = DiscoveryService.open(SECP256K1.KeyPair.random()) assertFalse(discoveryService.isShutdown) assertFalse(discoveryService.isTerminated) discoveryService.shutdown() assertTrue(discoveryService.isShutdown) runBlocking { discoveryService.awaitTermination() } assertTrue(discoveryService.isTerminated) } @Test fun shouldRespondToPingAndRecordEndpoint() = runBlocking { val peerRepository = EphemeralPeerRepository() val discoveryService = DiscoveryService.open( loggerProvider = SimpleLogger.withLogLevel(Level.ERROR).toOutputStream(System.err), keyPair = SECP256K1.KeyPair.random(), peerRepository = peerRepository ) val address = InetSocketAddress(InetAddress.getLocalHost(), discoveryService.localPort) val clientKeyPair = SECP256K1.KeyPair.random() val client = CoroutineDatagramChannel.open() val clientEndpoint = Endpoint("192.168.1.1", 5678, 7654) val ping = PingPacket.create(clientKeyPair, System.currentTimeMillis(), clientEndpoint, Endpoint(address)) client.send(ping, address) val pong = client.receivePacket() as PongPacket assertEquals(discoveryService.nodeId, pong.nodeId) assertEquals(ping.hash, pong.pingHash) val peer = peerRepository.get(clientKeyPair.publicKey()) assertNotNull(peer.endpoint) assertEquals(clientEndpoint.tcpPort, peer.endpoint?.tcpPort) discoveryService.shutdownNow() } @Test fun shouldPingBootstrapNodeAndValidate() = runBlocking { val bootstrapKeyPair = SECP256K1.KeyPair.random() val bootstrapClient = CoroutineDatagramChannel.open().bind(InetSocketAddress(0)) val serviceKeyPair = SECP256K1.KeyPair.random() val peerRepository = EphemeralPeerRepository() val routingTable = DevP2PPeerRoutingTable(serviceKeyPair.publicKey()) val discoveryService = DiscoveryService.open( loggerProvider = SimpleLogger.withLogLevel(Level.ERROR).toOutputStream(System.err), keyPair = serviceKeyPair, bootstrapURIs = listOf( URI("enode://" + bootstrapKeyPair.publicKey().toHexString() + "@127.0.0.1:" + bootstrapClient.localPort) ), peerRepository = peerRepository, routingTable = routingTable ) val address = InetSocketAddress(InetAddress.getLocalHost(), discoveryService.localPort) val ping = bootstrapClient.receivePacket() as PingPacket assertEquals(discoveryService.nodeId, ping.nodeId) assertEquals(ping.to, Endpoint("127.0.0.1", bootstrapClient.localPort, bootstrapClient.localPort)) assertEquals(discoveryService.localPort, ping.from.udpPort) assertNull(ping.from.tcpPort) val pong = PongPacket.create(bootstrapKeyPair, System.currentTimeMillis(), ping.from, ping.hash) bootstrapClient.send(pong, address) val findNodes = bootstrapClient.receivePacket() as FindNodePacket assertEquals(discoveryService.nodeId, findNodes.nodeId) assertEquals(discoveryService.nodeId, findNodes.target) val bootstrapPeer = peerRepository.get(bootstrapKeyPair.publicKey()) assertNotNull(bootstrapPeer.lastVerified) assertNotNull(bootstrapPeer.endpoint) assertEquals(bootstrapClient.localPort, bootstrapPeer.endpoint?.tcpPort) assertTrue(routingTable.contains(bootstrapPeer)) discoveryService.shutdownNow() } @Test fun shouldIgnoreBootstrapNodeRespondingWithDifferentNodeId() = runBlocking { val bootstrapKeyPair = SECP256K1.KeyPair.random() val bootstrapClient = CoroutineDatagramChannel.open().bind(InetSocketAddress(0)) val serviceKeyPair = SECP256K1.KeyPair.random() val peerRepository = EphemeralPeerRepository() val routingTable = DevP2PPeerRoutingTable(serviceKeyPair.publicKey()) val discoveryService = DiscoveryService.open( loggerProvider = SimpleLogger.withLogLevel(Level.ERROR).toOutputStream(System.err), keyPair = serviceKeyPair, bootstrapURIs = listOf( URI("enode://" + bootstrapKeyPair.publicKey().toHexString() + "@127.0.0.1:" + bootstrapClient.localPort) ), peerRepository = peerRepository, routingTable = routingTable ) val address = InetSocketAddress(InetAddress.getLocalHost(), discoveryService.localPort) val ping = bootstrapClient.receivePacket() as PingPacket assertEquals(discoveryService.nodeId, ping.nodeId) assertEquals(ping.to, Endpoint("127.0.0.1", bootstrapClient.localPort, bootstrapClient.localPort)) assertEquals(discoveryService.localPort, ping.from.udpPort) assertNull(ping.from.tcpPort) val pong = PongPacket.create(SECP256K1.KeyPair.random(), System.currentTimeMillis(), ping.from, ping.hash) bootstrapClient.send(pong, address) delay(1000) val bootstrapPeer = peerRepository.get(bootstrapKeyPair.publicKey()) assertNull(bootstrapPeer.lastVerified) assertFalse(routingTable.contains(bootstrapPeer)) discoveryService.shutdownNow() } @Test fun shouldPingBootstrapNodeWithAdvertisedAddress() = runBlocking { val bootstrapKeyPair = SECP256K1.KeyPair.random() val boostrapClient = CoroutineDatagramChannel.open().bind(InetSocketAddress(0)) val discoveryService = DiscoveryService.open( loggerProvider = SimpleLogger.withLogLevel(Level.ERROR).toOutputStream(System.err), keyPair = SECP256K1.KeyPair.random(), bootstrapURIs = listOf( URI("enode://" + bootstrapKeyPair.publicKey().bytes().toHexString() + "@127.0.0.1:" + boostrapClient.localPort) ), advertiseAddress = InetAddress.getByName("192.168.66.55"), advertiseUdpPort = 3836, advertiseTcpPort = 8765 ) val ping = boostrapClient.receivePacket() as PingPacket assertEquals(discoveryService.nodeId, ping.nodeId) assertEquals(Endpoint("127.0.0.1", boostrapClient.localPort, boostrapClient.localPort), ping.to) assertEquals(Endpoint("192.168.66.55", 3836, 8765), ping.from) discoveryService.shutdownNow() } @Test fun shouldRetryPingsToBootstrapNodes() = runBlocking { val bootstrapKeyPair = SECP256K1.KeyPair.random() val boostrapClient = CoroutineDatagramChannel.open().bind(InetSocketAddress(0)) val discoveryService = DiscoveryService.open( loggerProvider = SimpleLogger.withLogLevel(Level.ERROR).toOutputStream(System.err), keyPair = SECP256K1.KeyPair.random(), bootstrapURIs = listOf( URI("enode://" + bootstrapKeyPair.publicKey().bytes().toHexString() + "@127.0.0.1:" + boostrapClient.localPort) ) ) val ping1 = boostrapClient.receivePacket() as PingPacket assertEquals(discoveryService.nodeId, ping1.nodeId) assertEquals(Endpoint("127.0.0.1", boostrapClient.localPort, boostrapClient.localPort), ping1.to) val ping2 = boostrapClient.receivePacket() as PingPacket assertEquals(discoveryService.nodeId, ping2.nodeId) assertEquals(Endpoint("127.0.0.1", boostrapClient.localPort, boostrapClient.localPort), ping2.to) val ping3 = boostrapClient.receivePacket() as PingPacket assertEquals(discoveryService.nodeId, ping3.nodeId) assertEquals(Endpoint("127.0.0.1", boostrapClient.localPort, boostrapClient.localPort), ping3.to) discoveryService.shutdownNow() } @Test fun shouldRequirePingPongBeforeRespondingToFindNodesFromUnverifiedPeer() = runBlocking { val peerRepository = EphemeralPeerRepository() val discoveryService = DiscoveryService.open( loggerProvider = SimpleLogger.withLogLevel(Level.ERROR).toOutputStream(System.err), keyPair = SECP256K1.KeyPair.random(), peerRepository = peerRepository ) val address = InetSocketAddress(InetAddress.getLocalHost(), discoveryService.localPort) val clientKeyPair = SECP256K1.KeyPair.random() val client = CoroutineDatagramChannel.open() val findNodes = FindNodePacket.create(clientKeyPair, System.currentTimeMillis(), SECP256K1.KeyPair.random().publicKey()) client.send(findNodes, address) val ping = client.receivePacket() as PingPacket assertEquals(discoveryService.nodeId, ping.nodeId) // check it didn't immediately send neighbors delay(500) assertNull(client.tryReceive(ByteBuffer.allocate(2048))) val pong = PongPacket.create(clientKeyPair, System.currentTimeMillis(), ping.from, ping.hash) client.send(pong, address) val neighbors = client.receivePacket() as NeighborsPacket assertEquals(discoveryService.nodeId, neighbors.nodeId) val peer = peerRepository.get(clientKeyPair.publicKey()) assertNotNull(peer.lastVerified) assertNotNull(peer.endpoint) discoveryService.shutdownNow() } @Disabled @Test fun shouldConnectToNetworkAndDoALookup() { /* ktlint-disable */ val boostrapNodes = listOf( "enode://6332792c4a00e3e4ee0926ed89e0d27ef985424d97b6a45bf0f23e51f0dcb5e66b875777506458aea7af6f9e4ffb69f43f3778ee73c81ed9d34c51c4b16b0b0f@52.232.243.152:30303" // "enode://94c15d1b9e2fe7ce56e458b9a3b672ef11894ddedd0c6f247e0f1d3487f52b66208fb4aeb8179fce6e3a749ea93ed147c37976d67af557508d199d9594c35f09@192.81.208.223:30303" ).map { s -> URI.create(s) } /* ktlint-enable */ val discoveryService = DiscoveryService.open( SECP256K1.KeyPair.random(), bootstrapURIs = boostrapNodes, loggerProvider = SimpleLogger.withLogLevel(Level.DEBUG).toOutputStream(System.out) ) runBlocking { discoveryService.awaitBootstrap() val result = discoveryService.lookup(SECP256K1.KeyPair.random().publicKey()) assertTrue(result.isNotEmpty()) discoveryService.shutdown() discoveryService.awaitTermination() } } } cava-0.6.0/devp2p/src/test/kotlin/net/consensys/cava/devp2p/EndpointTest.kt000066400000000000000000000064521341750772100266170ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.devp2p import net.consensys.cava.rlp.RLP import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows internal class EndpointTest { @Test fun shouldHaveExpectedMinimumSize() { val endpoint1 = Endpoint("127.0.0.1", 65535, 65535) val bytes1 = RLP.encode { r -> endpoint1.writeTo(r) } assertEquals(endpoint1.rlpSize(), bytes1.size()) val endpoint2 = Endpoint("2001:4860:4860::8888", 65535, 65535) val bytes2 = RLP.encode { r -> endpoint2.writeTo(r) } assertEquals(endpoint2.rlpSize(), bytes2.size()) } @Test fun endpointsWithSameHostAndPortsAreEqual() { val endpoint1 = Endpoint("127.0.0.1", 7654, 8765) val endpoint2 = Endpoint("127.0.0.1", 7654, 8765) assertEquals(endpoint1, endpoint2) val endpoint3 = Endpoint("127.0.0.1", 7654, null) val endpoint4 = Endpoint("127.0.0.1", 7654, null) assertEquals(endpoint3, endpoint4) } @Test fun endpointsWithDifferentHostsAreNotEqual() { val endpoint1 = Endpoint("127.0.0.1", 7654, 8765) val endpoint2 = Endpoint("127.0.0.2", 7654, 8765) assertNotEquals(endpoint1, endpoint2) } @Test fun endpointsWithDifferentUDPPortsAreNotEqual() { val endpoint1 = Endpoint("127.0.0.1", 7654, 8765) val endpoint2 = Endpoint("127.0.0.1", 7655, 8765) assertNotEquals(endpoint1, endpoint2) } @Test fun endpointsWithDifferentTCPPortsAreNotEqual() { val endpoint1 = Endpoint("127.0.0.1", 7654, 8765) val endpoint2 = Endpoint("127.0.0.1", 7654, 8766) assertNotEquals(endpoint1, endpoint2) val endpoint3 = Endpoint("127.0.0.1", 7654, null) assertNotEquals(endpoint1, endpoint3) } @Test fun invalidUDPPortThrowsIllegalArgument() { assertThrows { Endpoint("127.0.0.1", 76543321, 8765) } assertThrows { Endpoint("127.0.0.1", 0, 8765) } } @Test fun invalidTCPPortThrowsIllegalArgument() { assertThrows { Endpoint("127.0.0.1", 7654, 87654321) } assertThrows { Endpoint("127.0.0.1", 7654, 0) } } @Test fun shouldEncodeThenDecode() { val endpoint1 = Endpoint("127.0.0.1", 7654, 8765) val encoding1 = RLP.encode { writer -> endpoint1.writeTo(writer) } val endpoint2: Endpoint = RLP.decode(encoding1) { reader -> Endpoint.readFrom(reader) } assertEquals(endpoint1, endpoint2) val endpoint3 = Endpoint("127.0.0.1", 7654, null) val encoding2 = RLP.encode { writer -> endpoint3.writeTo(writer) } val endpoint4: Endpoint = RLP.decode(encoding2) { reader -> Endpoint.readFrom(reader) } assertEquals(endpoint3, endpoint4) } } cava-0.6.0/devp2p/src/test/kotlin/net/consensys/cava/devp2p/EphemeralPeerRepositoryTest.kt000066400000000000000000000223251341750772100316520ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.devp2p import kotlinx.coroutines.runBlocking import net.consensys.cava.crypto.SECP256K1 import net.consensys.cava.junit.BouncyCastleExtension import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith import java.net.InetAddress @ExtendWith(BouncyCastleExtension::class) internal class EphemeralPeerRepositoryTest { private lateinit var peerRepository: PeerRepository private var currentTime: Long = System.currentTimeMillis() @BeforeEach fun setup() { peerRepository = EphemeralPeerRepository() } @Test fun shouldReturnPeerWithNoEndpointForUnknownId() = runBlocking { val peer = peerRepository.get(SECP256K1.KeyPair.random().publicKey()) assertNull(peer.endpoint) assertNull(peer.lastSeen) assertNull(peer.lastVerified) } @Test fun shouldReturnPeerBasedOnURIWithEndpoint() = runBlocking { val peer = peerRepository.get( "enode://c7849b663d12a2b5bf05b1ebf5810364f4870d5f1053fbd7500d38bc54" + "c705b453d7511ca8a4a86003d34d4c8ee0bbfcd387aa724f5b240b3ab4bbb994a1e09b@172.20.0.4:7654" ) assertNotNull(peer.endpoint) assertNull(peer.lastSeen) assertNull(peer.lastVerified) val expectedId = SECP256K1.PublicKey.fromHexString( "c7849b663d12a2b5bf05b1ebf5810364f4870d5f1053fbd7500d38bc54c705" + "b453d7511ca8a4a86003d34d4c8ee0bbfcd387aa724f5b240b3ab4bbb994a1e09b" ) assertEquals(expectedId, peer.nodeId) assertEquals(InetAddress.getByName("172.20.0.4"), peer.endpoint?.address) assertEquals(7654, peer.endpoint?.udpPort) assertEquals(7654, peer.endpoint?.tcpPort) } @Test fun shouldReturnPeerWithDefaultPortsWhenMissingFromURI() = runBlocking { val peer = peerRepository.get( "enode://c7849b663d12a2b5bf05b1ebf5810364f4870d5f1053fbd7500d38bc54" + "c705b453d7511ca8a4a86003d34d4c8ee0bbfcd387aa724f5b240b3ab4bbb994a1e09b@172.20.0.4" ) assertNotNull(peer.endpoint) assertNull(peer.lastSeen) assertNull(peer.lastVerified) val expectedId = SECP256K1.PublicKey.fromHexString( "c7849b663d12a2b5bf05b1ebf5810364f4870d5f1053fbd7500d38bc54c705" + "b453d7511ca8a4a86003d34d4c8ee0bbfcd387aa724f5b240b3ab4bbb994a1e09b" ) assertEquals(expectedId, peer.nodeId) assertEquals(InetAddress.getByName("172.20.0.4"), peer.endpoint?.address) assertEquals(30303, peer.endpoint?.udpPort) assertEquals(30303, peer.endpoint?.tcpPort) } @Test fun shouldReturnPeerWithDifferentPortsWhenQueryParamInURI() = runBlocking { val peer = peerRepository.get( "enode://c7849b663d12a2b5bf05b1ebf5810364f4870d5f1053fbd7500d38bc54" + "c705b453d7511ca8a4a86003d34d4c8ee0bbfcd387aa724f5b240b3ab4bbb994a1e09b@172.20.0.4:54789?discport=23456" ) assertNotNull(peer.endpoint) assertNull(peer.lastSeen) assertNull(peer.lastVerified) val expectedId = SECP256K1.PublicKey.fromHexString( "c7849b663d12a2b5bf05b1ebf5810364f4870d5f1053fbd7500d38bc54c705" + "b453d7511ca8a4a86003d34d4c8ee0bbfcd387aa724f5b240b3ab4bbb994a1e09b" ) assertEquals(expectedId, peer.nodeId) assertEquals(InetAddress.getByName("172.20.0.4"), peer.endpoint?.address) assertEquals(23456, peer.endpoint?.udpPort) assertEquals(54789, peer.endpoint?.tcpPort) } @Test fun shouldThrowWhenNotEnodeURI() { assertThrows { runBlocking { peerRepository.get( "http://c7849b663d12a2b5bf05b1ebf5810364f4870d5f1053fbd7500d38bc54" + "c705b453d7511ca8a4a86003d34d4c8ee0bbfcd387aa724f5b240b3ab4bbb994a1e09b@172.20.0.4:30303" ) } } } @Test fun shouldThrowWhenNoNodeIdInURI() { assertThrows { runBlocking { peerRepository.get("enode://172.20.0.4:30303") } } } @Test fun shouldThrowWhenInvalidPortInURI() { assertThrows { runBlocking { peerRepository.get( "enode://c7849b663d12a2b5bf05b1ebf5810364f4870d5f1053fbd7500d38bc54" + "c705b453d7511ca8a4a86003d34d4c8ee0bbfcd387aa724f5b240b3ab4bbb994a1e09b@172.20.0.4:98766" ) } } } @Test fun shouldThrowWhenOutOfRangeDiscPortInURI() { assertThrows { runBlocking { peerRepository.get( "enode://c7849b663d12a2b5bf05b1ebf5810364f4870d5f1053fbd7500d38bc54" + "c705b453d7511ca8a4a86003d34d4c8ee0bbfcd387aa724f5b240b3ab4bbb994a1e09b@172.20.0.4:54789?discport=98765" ) } } } @Test fun shouldThrowWhenInvalidDiscPortInURI() { assertThrows { runBlocking { peerRepository.get( "enode://c7849b663d12a2b5bf05b1ebf5810364f4870d5f1053fbd7500d38bc54" + "c705b453d7511ca8a4a86003d34d4c8ee0bbfcd387aa724f5b240b3ab4bbb994a1e09b@172.20.0.4:54789?discport=abcd" ) } } } @Test fun shouldIgnoreAdditionalQueryParametersInURI() = runBlocking { val peer = peerRepository.get( "enode://c7849b663d12a2b5bf05b1ebf5810364f4870d5f1053fbd7500d38bc54" + "c705b453d7511ca8a4a86003d34d4c8ee0bbfcd387aa724f5b240b3ab4bbb994a1e09b" + "@172.20.0.4:54789?foo=bar&discport=23456&bar=foo" ) assertNotNull(peer.endpoint) assertNull(peer.lastSeen) assertNull(peer.lastVerified) val expectedId = SECP256K1.PublicKey.fromHexString( "c7849b663d12a2b5bf05b1ebf5810364f4870d5f1053fbd7500d38bc54" + "c705b453d7511ca8a4a86003d34d4c8ee0bbfcd387aa724f5b240b3ab4bbb994a1e09b" ) assertEquals(expectedId, peer.nodeId) assertEquals(InetAddress.getByName("172.20.0.4"), peer.endpoint?.address) assertEquals(23456, peer.endpoint?.udpPort) assertEquals(54789, peer.endpoint?.tcpPort) } @Test fun shouldUpdateEndpointIfNoTimeCriteriaSpecified() = runBlocking { val peer = peerRepository.get( "enode://c7849b663d12a2b5bf05b1ebf5810364f4870d5f1053fbd7500d38bc54" + "c705b453d7511ca8a4a86003d34d4c8ee0bbfcd387aa724f5b240b3ab4bbb994a1e09b@172.20.0.4:54789" ) peer.verifyEndpoint(peer.endpoint!!, currentTime) assertEquals(currentTime, peer.lastSeen) assertEquals(currentTime, peer.lastVerified) val endpoint = Endpoint("127.0.0.1", 30303, 30303) peer.updateEndpoint(endpoint, currentTime + 10) assertEquals(endpoint, peer.endpoint) assertEquals(currentTime + 10, peer.lastSeen) assertNull(peer.lastVerified) } @Test fun shouldNotUpdateEndpointIfTimeCriteriaIsNotMet() = runBlocking { val peer = peerRepository.get( "enode://c7849b663d12a2b5bf05b1ebf5810364f4870d5f1053fbd7500d38bc54" + "c705b453d7511ca8a4a86003d34d4c8ee0bbfcd387aa724f5b240b3ab4bbb994a1e09b@172.20.0.4:54789" ) peer.verifyEndpoint(peer.endpoint!!, currentTime) assertEquals(currentTime, peer.lastSeen) assertEquals(currentTime, peer.lastVerified) val endpoint = peer.endpoint val newEndpoint = Endpoint("127.0.0.1", 30303, 30303) peer.updateEndpoint(newEndpoint, currentTime + 10, currentTime) assertEquals(endpoint, peer.endpoint) assertEquals(currentTime, peer.lastSeen) assertEquals(currentTime, peer.lastVerified) } @Test fun shouldUpdateEndpointIfTimeCriteriaIsMet() = runBlocking { val peer = peerRepository.get( "enode://c7849b663d12a2b5bf05b1ebf5810364f4870d5f1053fbd7500d38bc54" + "c705b453d7511ca8a4a86003d34d4c8ee0bbfcd387aa724f5b240b3ab4bbb994a1e09b@172.20.0.4:54789" ) peer.verifyEndpoint(peer.endpoint!!, currentTime) assertEquals(currentTime, peer.lastSeen) assertEquals(currentTime, peer.lastVerified) val newEndpoint = Endpoint("127.0.0.1", 30303, 30303) peer.updateEndpoint(newEndpoint, currentTime + 10, currentTime + 1) assertEquals(newEndpoint, peer.endpoint) assertEquals(currentTime + 10, peer.lastSeen) assertNull(peer.lastVerified) } @Test fun shouldUpdateLastSeenIfEndpointIsUnchanged() = runBlocking { val peer = peerRepository.get( "enode://c7849b663d12a2b5bf05b1ebf5810364f4870d5f1053fbd7500d38bc54" + "c705b453d7511ca8a4a86003d34d4c8ee0bbfcd387aa724f5b240b3ab4bbb994a1e09b@172.20.0.4:54789" ) peer.verifyEndpoint(peer.endpoint!!, currentTime) assertEquals(currentTime, peer.lastSeen) assertEquals(currentTime, peer.lastVerified) peer.verifyEndpoint(peer.endpoint!!, currentTime) peer.updateEndpoint(peer.endpoint!!, currentTime + 10, currentTime) assertEquals(currentTime + 10, peer.lastSeen) assertEquals(currentTime, peer.lastVerified) } } cava-0.6.0/devp2p/src/test/kotlin/net/consensys/cava/devp2p/FindNodePacketTest.kt000066400000000000000000000050301341750772100276440ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.devp2p import net.consensys.cava.bytes.Bytes import net.consensys.cava.crypto.SECP256K1 import net.consensys.cava.junit.BouncyCastleExtension import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import java.nio.ByteBuffer @ExtendWith(BouncyCastleExtension::class) internal class FindNodePacketTest { @Test fun shouldEncodeThenDecodePacket() { val keyPair = SECP256K1.KeyPair.random() val target = SECP256K1.KeyPair.random().publicKey() val now = System.currentTimeMillis() val pong = FindNodePacket.create(keyPair, now, target) val buffer = ByteBuffer.allocate(Packet.MAX_SIZE) pong.encodeTo(buffer) buffer.flip() val datagram = Bytes.wrapByteBuffer(buffer) val packet = Packet.decodeFrom(datagram) assertTrue(packet is FindNodePacket) val findNodePacket = packet as FindNodePacket assertEquals(keyPair.publicKey(), findNodePacket.nodeId) assertEquals(target, findNodePacket.target) assertEquals(((now + PACKET_EXPIRATION_PERIOD_MS + 999) / 1000) * 1000, findNodePacket.expiration) } @Test fun decodeReferencePacket1() { // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-8.md val datagram = Bytes.fromHexString( "c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91" + "831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe" + "04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d" + "115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be0081290476" + "7bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260a" + "dd7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396") val packet = Packet.decodeFrom(datagram) assertTrue(packet is FindNodePacket) } } cava-0.6.0/devp2p/src/test/kotlin/net/consensys/cava/devp2p/NeighborsPacketTest.kt000066400000000000000000000071161341750772100301050ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.devp2p import net.consensys.cava.bytes.Bytes import net.consensys.cava.crypto.SECP256K1 import net.consensys.cava.junit.BouncyCastleExtension import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import java.nio.ByteBuffer @ExtendWith(BouncyCastleExtension::class) internal class NeighborsPacketTest { @Test fun shouldHaveExpectedMinimumSize() { val packet = NeighborsPacket.create(SECP256K1.KeyPair.random(), System.currentTimeMillis(), emptyList()) val buffer = packet.encodeTo(ByteBuffer.allocate(Packet.MAX_SIZE)) // the minimum also includes a list length prefix of 4 bytes assertEquals(NeighborsPacket.RLP_MIN_SIZE, buffer.position() + 4) } @Test fun shouldEncodeThenDecodePacket() { val keyPair = SECP256K1.KeyPair.random() val neighbors = listOf( Node(Endpoint("10.0.0.54", 6543, 6543), SECP256K1.KeyPair.random().publicKey()), Node(Endpoint("192.168.34.65", 9832, 1453), SECP256K1.KeyPair.random().publicKey()) ) val now = System.currentTimeMillis() val pong = NeighborsPacket.create(keyPair, now, neighbors) val buffer = ByteBuffer.allocate(Packet.MAX_SIZE) pong.encodeTo(buffer) buffer.flip() val datagram = Bytes.wrapByteBuffer(buffer) val packet = Packet.decodeFrom(datagram) assertTrue(packet is NeighborsPacket) val neighborsPacket = packet as NeighborsPacket assertEquals(keyPair.publicKey(), neighborsPacket.nodeId) assertEquals(neighbors, neighborsPacket.nodes) assertEquals(((now + PACKET_EXPIRATION_PERIOD_MS + 999) / 1000) * 1000, neighborsPacket.expiration) } @Test fun decodeReferencePacket1() { // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-8.md val datagram = Bytes.fromHexString( "c679fc8fe0b8b12f06577f2e802d34f6fa257e6137a995f6f4cbfc9ee50ed3710faf6e66f932c4c8" + "d81d64343f429651328758b47d3dbc02c4042f0fff6946a50f4a49037a72bb550f3a7872363a83e1" + "b9ee6469856c24eb4ef80b7535bcf99c0004f9015bf90150f84d846321163782115c82115db84031" + "55e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa8291" + "15d224c523596b401065a97f74010610fce76382c0bf32f84984010203040101b840312c55512422" + "cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e82" + "9f04c2d314fc2d4e255e0d3bc08792b069dbf8599020010db83c4d001500000000abcdef12820d05" + "820d05b84038643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2" + "d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aacf8599020010db885a308d3" + "13198a2e037073488203e78203e8b8408dcab8618c3253b558d459da53bd8fa68935a719aff8b811" + "197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73" + "8443b9a355010203b525a138aa34383fec3d2719a0" ) val packet = Packet.decodeFrom(datagram) assertTrue(packet is NeighborsPacket) } } cava-0.6.0/devp2p/src/test/kotlin/net/consensys/cava/devp2p/PingPacketTest.kt000066400000000000000000000102321341750772100270530ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.devp2p import net.consensys.cava.bytes.Bytes import net.consensys.cava.crypto.SECP256K1 import net.consensys.cava.junit.BouncyCastleExtension import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import java.nio.ByteBuffer @ExtendWith(BouncyCastleExtension::class) internal class PingPacketTest { @Test fun shouldEncodeThenDecodePacket() { val keyPair = SECP256K1.KeyPair.random() val from = Endpoint("10.0.0.54", 6543, 6543) val to = Endpoint("192.168.34.65", 9832, 1453) val now = System.currentTimeMillis() val ping = PingPacket.create(keyPair, now, from, to) val buffer = ByteBuffer.allocate(Packet.MAX_SIZE) ping.encodeTo(buffer) buffer.flip() val datagram = Bytes.wrapByteBuffer(buffer) val packet = Packet.decodeFrom(datagram) assertTrue(packet is PingPacket) val pingPacket = packet as PingPacket assertEquals(keyPair.publicKey(), pingPacket.nodeId) assertEquals(Endpoint("10.0.0.54", 6543, 6543), pingPacket.from) assertEquals(Endpoint("192.168.34.65", 9832, 1453), pingPacket.to) assertEquals(((now + PACKET_EXPIRATION_PERIOD_MS + 999) / 1000) * 1000, pingPacket.expiration) } @Test fun shouldDecodePingPacketWithMissingEndpoint() { val keyPair = SECP256K1.KeyPair.random() val from = Endpoint("10.0.0.54", 6543, 6543) val to = Endpoint("192.168.34.65", 9832, 1453) val now = System.currentTimeMillis() val ping = PingPacket.create(keyPair, now, from, to) val buffer = ByteBuffer.allocate(1024) ping.encodeTo(buffer) buffer.flip() val datagram = Bytes.wrapByteBuffer(buffer) val packet = Packet.decodeFrom(datagram) assertTrue(packet is PingPacket) val pingPacket = packet as PingPacket assertEquals(keyPair.publicKey(), pingPacket.nodeId) assertEquals(Endpoint("10.0.0.54", 6543, 6543), pingPacket.from) assertEquals(Endpoint("192.168.34.65", 9832, 1453), pingPacket.to) assertEquals(((now + PACKET_EXPIRATION_PERIOD_MS + 999) / 1000) * 1000, pingPacket.expiration) } @Test fun decodeReferencePacket1() { // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-8.md val datagram = Bytes.fromHexString( "e9614ccfd9fc3e74360018522d30e1419a143407ffcce748de3e22116b7e8dc92ff74788c0b6663a" + "aa3d67d641936511c8f8d6ad8698b820a7cf9e1be7155e9a241f556658c55428ec0563514365799a" + "4be2be5a685a80971ddcfa80cb422cdd0101ec04cb847f000001820cfa8215a8d790000000000000" + "000000000000000000018208ae820d058443b9a3550102") val packet = Packet.decodeFrom(datagram) assertTrue(packet is PingPacket) } @Test fun decodeReferencePacket2() { // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-8.md val datagram = Bytes.fromHexString( "577be4349c4dd26768081f58de4c6f375a7a22f3f7adda654d1428637412c3d7fe917cadc56d4e5e" + "7ffae1dbe3efffb9849feb71b262de37977e7c7a44e677295680e9e38ab26bee2fcbae207fba3ff3" + "d74069a50b902a82c9903ed37cc993c50001f83e82022bd79020010db83c4d001500000000abcdef" + "12820cfa8215a8d79020010db885a308d313198a2e037073488208ae82823a8443b9a355c5010203" + "040531b9019afde696e582a78fa8d95ea13ce3297d4afb8ba6433e4154caa5ac6431af1b80ba7602" + "3fa4090c408f6b4bc3701562c031041d4702971d102c9ab7fa5eed4cd6bab8f7af956f7d565ee191" + "7084a95398b6a21eac920fe3dd1345ec0a7ef39367ee69ddf092cbfe5b93e5e568ebc491983c09c7" + "6d922dc3") val packet = Packet.decodeFrom(datagram) assertTrue(packet is PingPacket) } } cava-0.6.0/devp2p/src/test/kotlin/net/consensys/cava/devp2p/PongPacketTest.kt000066400000000000000000000051041341750772100270630ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.devp2p import net.consensys.cava.bytes.Bytes import net.consensys.cava.bytes.Bytes32 import net.consensys.cava.crypto.SECP256K1 import net.consensys.cava.junit.BouncyCastleExtension import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import java.nio.ByteBuffer @ExtendWith(BouncyCastleExtension::class) internal class PongPacketTest { @Test fun shouldEncodeThenDecodePacket() { val keyPair = SECP256K1.KeyPair.random() val to = Endpoint("10.0.0.54", 6543, 6543) val pingHash = Bytes32.random() val now = System.currentTimeMillis() val pong = PongPacket.create(keyPair, now, to, pingHash) val buffer = ByteBuffer.allocate(Packet.MAX_SIZE) pong.encodeTo(buffer) buffer.flip() val datagram = Bytes.wrapByteBuffer(buffer) val packet = Packet.decodeFrom(datagram) assertTrue(packet is PongPacket) val pongPacket = packet as PongPacket assertEquals(keyPair.publicKey(), pongPacket.nodeId) assertEquals(Endpoint("10.0.0.54", 6543, 6543), pongPacket.to) assertEquals(pingHash, pongPacket.pingHash) assertEquals(((now + PACKET_EXPIRATION_PERIOD_MS + 999) / 1000) * 1000, pongPacket.expiration) } @Test fun decodeReferencePacket1() { // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-8.md val datagram = Bytes.fromHexString( "09b2428d83348d27cdf7064ad9024f526cebc19e4958f0fdad87c15eb598dd61d08423e0bf66b206" + "9869e1724125f820d851c136684082774f870e614d95a2855d000f05d1648b2d5945470bc187c2d2" + "216fbe870f43ed0909009882e176a46b0102f846d79020010db885a308d313198a2e037073488208" + "ae82823aa0fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c9548443b9" + "a355c6010203c2040506a0c969a58f6f9095004c0177a6b47f451530cab38966a25cca5cb58f0555" + "42124e") val packet = Packet.decodeFrom(datagram) assertTrue(packet is PongPacket) } } cava-0.6.0/eth-reference-tests/000077500000000000000000000000001341750772100163125ustar00rootroot00000000000000cava-0.6.0/eth-reference-tests/build.gradle000066400000000000000000000007021341750772100205700ustar00rootroot00000000000000jar { enabled = false } dependencies { testCompile project(':eth') testCompile project(':merkle-trie') testCompile project(':rlp') testCompile project(':junit') testCompile 'com.fasterxml.jackson.core:jackson-databind:2.9.5' testCompile 'org.bouncycastle:bcprov-jdk15on' testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/eth-reference-tests/src/000077500000000000000000000000001341750772100171015ustar00rootroot00000000000000cava-0.6.0/eth-reference-tests/src/test/000077500000000000000000000000001341750772100200605ustar00rootroot00000000000000cava-0.6.0/eth-reference-tests/src/test/java/000077500000000000000000000000001341750772100210015ustar00rootroot00000000000000cava-0.6.0/eth-reference-tests/src/test/java/net/000077500000000000000000000000001341750772100215675ustar00rootroot00000000000000cava-0.6.0/eth-reference-tests/src/test/java/net/consensys/000077500000000000000000000000001341750772100236135ustar00rootroot00000000000000cava-0.6.0/eth-reference-tests/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100245255ustar00rootroot00000000000000cava-0.6.0/eth-reference-tests/src/test/java/net/consensys/cava/eth/000077500000000000000000000000001341750772100253055ustar00rootroot00000000000000cava-0.6.0/eth-reference-tests/src/test/java/net/consensys/cava/eth/reference/000077500000000000000000000000001341750772100272435ustar00rootroot00000000000000cava-0.6.0/eth-reference-tests/src/test/java/net/consensys/cava/eth/reference/BlockRLPTestSuite.java000066400000000000000000000144531341750772100333770ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.eth.reference; import static com.google.common.base.Preconditions.checkNotNull; import static org.junit.jupiter.api.Assertions.assertEquals; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.crypto.SECP256K1.Signature; import net.consensys.cava.eth.Address; import net.consensys.cava.eth.Block; import net.consensys.cava.eth.BlockBody; import net.consensys.cava.eth.BlockHeader; import net.consensys.cava.eth.Hash; import net.consensys.cava.eth.Transaction; import net.consensys.cava.io.Resources; import net.consensys.cava.junit.BouncyCastleExtension; import net.consensys.cava.units.bigints.UInt256; import net.consensys.cava.units.ethereum.Gas; import net.consensys.cava.units.ethereum.Wei; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.IntStream; import java.util.stream.Stream; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.errorprone.annotations.MustBeClosed; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @ExtendWith(BouncyCastleExtension.class) class BlockRLPTestSuite { private static ObjectMapper mapper = new ObjectMapper(); @ParameterizedTest(name = "{index}. block {0}[{1}]") @MethodSource("readBlockChainTests") void testBlockRLP(String name, long blockIndex, Block block, String rlp, String hash) { Block rlpBlock = Block.fromHexString(rlp); assertEquals(block, rlpBlock); assertEquals(Bytes.fromHexString(rlp), block.toBytes()); assertEquals(Hash.fromHexString(hash), block.header().hash()); assertEquals(Hash.fromHexString(hash), rlpBlock.header().hash()); } @MustBeClosed private static Stream readBlockChainTests() throws IOException { return Resources.find("**/BlockchainTests/**/*.json").flatMap(url -> { try (InputStream is = url.openConnection().getInputStream()) { return readTestCase(is); } catch (IOException e) { throw new UncheckedIOException(e); } }); } @SuppressWarnings({"unchecked", "rawtypes"}) private static Stream readTestCase(InputStream is) throws IOException { Map test = mapper.readerFor(Map.class).readValue(is); String name = test.keySet().iterator().next(); Map testData = test.get(name); List blocks = (List) testData.get("blocks"); return IntStream.range(0, blocks.size()).boxed().filter(i -> blocks.get(i).containsKey("blockHeader")).map(i -> { Map block = blocks.get(i); return Arguments.of(name, i, createBlock(block), block.get("rlp"), ((Map) block.get("blockHeader")).get("hash")); }); } @SuppressWarnings({"unchecked", "rawtypes"}) private static BlockHeader createBlockHeader(Map headerData) { checkNotNull(headerData, headerData.toString()); return new BlockHeader( Hash.fromHexString((String) headerData.get("parentHash")), Hash.fromHexString((String) headerData.get("uncleHash")), Address.fromHexString((String) headerData.get("coinbase")), Hash.fromHexString((String) headerData.get("stateRoot")), Hash.fromHexString((String) headerData.get("transactionsTrie")), Hash.fromHexString((String) headerData.get("receiptTrie")), Bytes.fromHexString((String) headerData.get("bloom")), UInt256.fromHexString((String) headerData.get("difficulty")), UInt256.fromHexString((String) headerData.get("number")), Gas.valueOf(UInt256.fromHexString((String) headerData.get("gasLimit"))), Gas.valueOf(UInt256.fromHexString((String) headerData.get("gasUsed"))), Instant.ofEpochSecond(Bytes.fromHexString((String) headerData.get("timestamp")).toLong()), Bytes.fromHexString((String) headerData.get("extraData")), Hash.fromHexString((String) headerData.get("mixHash")), Bytes.fromHexString((String) headerData.get("nonce"))); } @SuppressWarnings({"unchecked", "rawtypes"}) private static Block createBlock(Map blockData) { Map headerData = (Map) blockData.get("blockHeader"); BlockHeader header = createBlockHeader(headerData); List transactions = new ArrayList<>(); for (Object txDataObj : (List) blockData.get("transactions")) { Map txData = (Map) txDataObj; Address toAddress = null; String toAddressString = (String) txData.get("to"); if (toAddressString != null) { Bytes toAddressBytes = Bytes.fromHexString(toAddressString); toAddress = toAddressBytes.isEmpty() ? null : Address.fromBytes(toAddressBytes); } transactions.add( new Transaction( UInt256.fromHexString((String) txData.get("nonce")), Wei.valueOf(UInt256.fromHexString((String) txData.get("gasPrice"))), Gas.valueOf(UInt256.fromHexString((String) txData.get("gasLimit"))), toAddress, Wei.valueOf(UInt256.fromHexString((String) txData.get("value"))), Bytes.fromHexString((String) txData.get("data")), Signature.create( (byte) ((int) Bytes.fromHexString((String) txData.get("v")).get(0) - 27), Bytes.fromHexString((String) txData.get("r")).toUnsignedBigInteger(), Bytes.fromHexString((String) txData.get("s")).toUnsignedBigInteger()))); } List ommers = new ArrayList<>(); for (Object ommerDataObj : (List) blockData.get("uncleHeaders")) { ommers.add(createBlockHeader((Map) ommerDataObj)); } return new Block(header, new BlockBody(transactions, ommers)); } } MerkleTrieTestSuite.java000066400000000000000000000073261341750772100337540ustar00rootroot00000000000000cava-0.6.0/eth-reference-tests/src/test/java/net/consensys/cava/eth/reference/* * Copyright 2018 ConsenSys AG. * * 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.eth.reference; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertEquals; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.io.Resources; import net.consensys.cava.junit.BouncyCastleExtension; import net.consensys.cava.trie.MerklePatriciaTrie; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.util.List; import java.util.Map; import java.util.stream.Stream; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.errorprone.annotations.MustBeClosed; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @ExtendWith(BouncyCastleExtension.class) class MerkleTrieTestSuite { private Bytes readFromString(String value) { if (value.startsWith("0x")) { return Bytes.fromHexString(value); } else { return Bytes.wrap(value.getBytes(UTF_8)); } } @ParameterizedTest(name = "{index}. {0}") @MethodSource("readAnyOrderTrieTests") @SuppressWarnings({"unchecked", "rawtypes"}) void testAnyOrderTrieTrees(String name, Map input, String root) throws Exception { MerklePatriciaTrie trie = MerklePatriciaTrie.create(this::readFromString); for (Object entry : input.entrySet()) { Map.Entry keyValue = (Map.Entry) entry; trie.putAsync(readFromString((String) keyValue.getKey()), (String) keyValue.getValue()).join(); } assertEquals(Bytes.fromHexString(root), trie.rootHash()); } @ParameterizedTest(name = "{index}. {0}") @MethodSource("readTrieTests") @SuppressWarnings({"unchecked", "rawtypes"}) void testTrieTrees(String name, List input, String root) throws Exception { MerklePatriciaTrie trie = MerklePatriciaTrie.create(this::readFromString); for (Object entry : input) { List keyValue = (List) entry; trie.putAsync(readFromString((String) keyValue.get(0)), (String) keyValue.get(1)).join(); } assertEquals(Bytes.fromHexString(root), trie.rootHash()); } @MustBeClosed private static Stream readTrieTests() throws IOException { return findTests("**/TrieTests/trietest.json"); } @MustBeClosed private static Stream readAnyOrderTrieTests() throws IOException { return findTests("**/TrieTests/trieanyorder.json"); } @MustBeClosed private static Stream findTests(String glob) throws IOException { return Resources.find(glob).flatMap(url -> { try (InputStream in = url.openConnection().getInputStream()) { return prepareTests(in); } catch (IOException e) { throw new UncheckedIOException(e); } }); } @SuppressWarnings({"unchecked", "rawtypes"}) private static Stream prepareTests(InputStream in) throws IOException { Map allTests = new ObjectMapper().readerFor(Map.class).readValue(in); return allTests.entrySet().stream().map( entry -> Arguments.of(entry.getKey(), entry.getValue().get("in"), entry.getValue().get("root"))); } } RLPReferenceTestSuite.java000066400000000000000000000115761341750772100341670ustar00rootroot00000000000000cava-0.6.0/eth-reference-tests/src/test/java/net/consensys/cava/eth/reference/* * Copyright 2018 ConsenSys AG. * * 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.eth.reference; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.io.Resources; import net.consensys.cava.rlp.RLP; import net.consensys.cava.rlp.RLPException; import net.consensys.cava.rlp.RLPReader; import net.consensys.cava.rlp.RLPWriter; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.math.BigInteger; import java.util.List; import java.util.Map; import java.util.stream.Stream; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.errorprone.annotations.MustBeClosed; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; class RLPReferenceTestSuite { private static void writePayload(RLPWriter writer, Object in) { if (in instanceof String && ((String) in).startsWith("#")) { writer.writeBigInteger(new BigInteger(((String) in).substring(1))); } else if (in instanceof String) { writer.writeString((String) in); } else if (in instanceof BigInteger) { writer.writeBigInteger((BigInteger) in); } else if (in instanceof Integer) { writer.writeValue(Bytes.minimalBytes((Integer) in)); } else if (in instanceof List) { writer.writeList((listWriter) -> { for (Object elt : (List) in) { writePayload(listWriter, elt); } }); } else { throw new UnsupportedOperationException(); } } private static Object readPayload(RLPReader reader, Object in) { if (in instanceof List) { return reader.readList((listReader, list) -> { for (Object elt : ((List) in)) { list.add(readPayload(listReader, elt)); } }); } else if (in instanceof BigInteger) { return reader.readBigInteger(); } else if (in instanceof String) { return reader.readString(); } else if (in instanceof Integer) { return reader.readInt(); } else { throw new UnsupportedOperationException(); } } @ParameterizedTest(name = "{index}. write {0}") @MethodSource("readRLPTests") void testWriteRLP(String name, Object in, String out) throws IOException { Bytes encoded = RLP.encode((writer) -> writePayload(writer, in)); assertEquals(Bytes.fromHexString(out), encoded, "Input was of type " + in.getClass()); } @ParameterizedTest(name = "{index}. read {0}") @MethodSource("readRLPTests") void testReadRLP(String name, Object in, String out) { if (in instanceof String && ((String) in).startsWith("#")) { in = new BigInteger(((String) in).substring(1)); } Object payload = in; Object decoded = RLP.decode(Bytes.fromHexString(out), (reader) -> readPayload(reader, payload)); assertEquals(in, decoded); } @ParameterizedTest(name = "{index}. invalid {0}") @MethodSource("readInvalidRLPTests") void testReadInvalidRLP(String name, Object in, String out) { assertThrows(RLPException.class, () -> { if ("incorrectLengthInArray".equals(name)) { RLP.decodeToList(Bytes.fromHexString(out), (reader, list) -> { }); } else { RLP.decodeValue(Bytes.fromHexString(out)); } }); } @MustBeClosed private static Stream readRLPTests() throws IOException { return findTests("**/RLPTests/rlptest.json"); } @MustBeClosed private static Stream readInvalidRLPTests() throws IOException { return findTests("**/RLPTests/invalidRLPTest.json"); } @MustBeClosed private static Stream findTests(String glob) throws IOException { return Resources.find(glob).flatMap(url -> { try (InputStream in = url.openConnection().getInputStream()) { return prepareTests(in); } catch (IOException e) { throw new UncheckedIOException(e); } }); } @SuppressWarnings({"unchecked", "rawtypes"}) private static Stream prepareTests(InputStream in) throws IOException { ObjectMapper mapper = new ObjectMapper(); Map allTests = mapper.readerFor(Map.class).readValue(in); return allTests.entrySet().stream().map( entry -> Arguments.of(entry.getKey(), entry.getValue().get("in"), entry.getValue().get("out"))); } } TransactionTestSuite.java000066400000000000000000000113461341750772100341730ustar00rootroot00000000000000cava-0.6.0/eth-reference-tests/src/test/java/net/consensys/cava/eth/reference/* * Copyright 2018 ConsenSys AG. * * 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.eth.reference; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.eth.Address; import net.consensys.cava.eth.Transaction; import net.consensys.cava.io.Resources; import net.consensys.cava.junit.BouncyCastleExtension; import net.consensys.cava.rlp.RLPException; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.stream.Stream; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.errorprone.annotations.MustBeClosed; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @ExtendWith(BouncyCastleExtension.class) class TransactionTestSuite { private static ObjectMapper mapper = new ObjectMapper(); @ParameterizedTest(name = "{index}. tx {0}/{1}") @MethodSource("readTransactionTests") void testTransaction(String name, String milestone, String rlp, String hash, String sender) { if (hash == null || sender == null) { assertTrue(hash == null && sender == null, "Invalid test case"); testInvalidTransaction(rlp); } else { testValidTransaction(rlp, hash, sender); } } private void testValidTransaction(String rlp, String hash, String sender) { Bytes rlpBytes = Bytes.fromHexString(rlp); Transaction tx = Transaction.fromBytes(rlpBytes); assertEquals(Address.fromBytes(Bytes.fromHexString(sender)), tx.sender()); assertEquals(rlpBytes, tx.toBytes()); assertEquals(Bytes.fromHexString(hash), tx.hash().toBytes()); } private void testInvalidTransaction(String rlp) { Bytes rlpBytes; try { rlpBytes = Bytes.fromHexString(rlp); } catch (IllegalArgumentException e) { return; } Transaction tx; try { tx = Transaction.fromBytes(rlpBytes); } catch (RLPException e) { return; } if (tx.sender() == null) { return; } fail("Expected an invalid transaction but it was successfully read"); } @MustBeClosed private static Stream readTransactionTests() throws IOException { return Resources .find("**/TransactionTests/**/*.json") .filter( url -> !url.getPath().contains("GasLimitOverflow") && !url.getPath().contains("GasLimitxPriceOverflow") && !url.getPath().contains("NotEnoughGas") && !url.getPath().contains("NotEnoughGAS") && !url.getPath().contains("EmptyTransaction")) .flatMap(url -> { try (InputStream in = url.openConnection().getInputStream()) { return readTestCase(in); } catch (IOException e) { throw new UncheckedIOException(e); } }) .sorted(Comparator.comparing(a -> ((String) a.get()[0]))); } @SuppressWarnings({"unchecked", "rawtypes"}) private static Stream readTestCase(InputStream in) throws IOException { Map tests = mapper.readerFor(Map.class).readValue(in); return tests.entrySet().stream().flatMap(entry -> { String name = entry.getKey(); Map testData = entry.getValue(); String rlp = (String) testData.get("rlp"); List arguments = new ArrayList<>(); for (String milestone : new String[] { // "Byzantium", // "Constantinople", // "EIP150", // "EIP158", "Frontier", "Homestead"}) { Map milestoneData = (Map) testData.get(milestone); if (!milestoneData.isEmpty()) { arguments.add(Arguments.of(name, milestone, rlp, milestoneData.get("hash"), milestoneData.get("sender"))); } } if (arguments.isEmpty()) { arguments.add(Arguments.of(name, "(no milestone)", rlp, null, null)); } return arguments.stream(); }); } } cava-0.6.0/eth-reference-tests/src/test/resources/000077500000000000000000000000001341750772100220725ustar00rootroot00000000000000cava-0.6.0/eth-reference-tests/src/test/resources/tests/000077500000000000000000000000001341750772100232345ustar00rootroot00000000000000cava-0.6.0/eth/000077500000000000000000000000001341750772100132165ustar00rootroot00000000000000cava-0.6.0/eth/build.gradle000066400000000000000000000006661341750772100155050ustar00rootroot00000000000000description = 'Classes and utilities for working with Ethereum.' dependencies { compile project(':bytes') compile project(':crypto') compile project(':rlp') compile project(':units') testCompile project(':junit') testCompile 'org.bouncycastle:bcprov-jdk15on' testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/eth/src/000077500000000000000000000000001341750772100140055ustar00rootroot00000000000000cava-0.6.0/eth/src/main/000077500000000000000000000000001341750772100147315ustar00rootroot00000000000000cava-0.6.0/eth/src/main/java/000077500000000000000000000000001341750772100156525ustar00rootroot00000000000000cava-0.6.0/eth/src/main/java/net/000077500000000000000000000000001341750772100164405ustar00rootroot00000000000000cava-0.6.0/eth/src/main/java/net/consensys/000077500000000000000000000000001341750772100204645ustar00rootroot00000000000000cava-0.6.0/eth/src/main/java/net/consensys/cava/000077500000000000000000000000001341750772100213765ustar00rootroot00000000000000cava-0.6.0/eth/src/main/java/net/consensys/cava/eth/000077500000000000000000000000001341750772100221565ustar00rootroot00000000000000cava-0.6.0/eth/src/main/java/net/consensys/cava/eth/Address.java000066400000000000000000000051351341750772100244120ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.eth; import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; import net.consensys.cava.bytes.Bytes; import com.google.common.base.Objects; /** * An Ethereum account address. */ public final class Address { /** * Create an address from Bytes. * *

* The address must be exactly 20 bytes. * * @param bytes The bytes for this address. * @return An address. * @throws IllegalArgumentException If {@code bytes.size() != 20}. */ public static Address fromBytes(Bytes bytes) { requireNonNull(bytes); checkArgument(bytes.size() == SIZE, "Expected %s bytes but got %s", SIZE, bytes.size()); return new Address(bytes); } /** * Parse a hexadecimal string into a {@link Address}. * * @param str The hexadecimal string to parse, which may or may not start with "0x", and should encode exactly 20 * bytes. * @return The value corresponding to {@code str}. * @throws IllegalArgumentException if {@code str} does not correspond to va alid hexadecimal representation * containing 20 bytes. */ public static Address fromHexString(String str) { return fromBytes(Bytes.fromHexString(str)); } private static final int SIZE = 20; private final Bytes delegate; private Address(Bytes value) { this.delegate = value; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Address)) { return false; } Address other = (Address) obj; return delegate.equals(other.delegate); } @Override public int hashCode() { return Objects.hashCode(delegate); } @Override public String toString() { return "Address{" + delegate.toHexString() + '}'; } /** * @return A hex-encoded version of the address. */ public String toHexString() { return delegate.toHexString(); } /** * @return The bytes for this address. */ public Bytes toBytes() { return delegate; } } cava-0.6.0/eth/src/main/java/net/consensys/cava/eth/Block.java000066400000000000000000000067601341750772100240640ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.eth; import static java.util.Objects.requireNonNull; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.rlp.RLP; import net.consensys.cava.rlp.RLPException; import net.consensys.cava.rlp.RLPReader; import net.consensys.cava.rlp.RLPWriter; import com.google.common.base.Objects; /** * An Ethereum block. */ public final class Block { /** * Deserialize a block from RLP encoded bytes. * * @param encoded The RLP encoded block. * @return The deserialized block. * @throws RLPException If there is an error decoding the block. */ public static Block fromBytes(Bytes encoded) { return RLP.decodeList(encoded, Block::readFrom); } /** * Parse a hexadecimal string into a {@link Block}. * * @param str The hexadecimal string to parse, which may or may not start with "0x". * @return The value corresponding to {@code str}. * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation. * @throws RLPException If there is an error decoding the block. */ public static Block fromHexString(String str) { return RLP.decodeList(Bytes.fromHexString(str), Block::readFrom); } /** * Deserialize a block from an RLP input. * * @param reader The RLP reader. * @return The deserialized block. * @throws RLPException If there is an error decoding the block. */ public static Block readFrom(RLPReader reader) { BlockHeader header = reader.readList(BlockHeader::readFrom); BlockBody body = BlockBody.readFrom(reader); return new Block(header, body); } private final BlockHeader header; private final BlockBody body; /** * Creates a block. * * @param header the header of the block. * @param body the body of the block. */ public Block(BlockHeader header, BlockBody body) { requireNonNull(header); requireNonNull(body); this.header = header; this.body = body; } /** * @return the block body. */ public BlockBody body() { return body; } /** * @return the block header. */ public BlockHeader header() { return header; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Block)) { return false; } Block other = (Block) obj; return Objects.equal(header, other.header) && Objects.equal(body, other.body); } @Override public int hashCode() { return Objects.hashCode(header, body); } @Override public String toString() { return "Block{" + "header=" + header + ", body=" + body + '}'; } /** * @return The RLP serialized form of this block. */ public Bytes toBytes() { return RLP.encodeList(this::writeTo); } /** * Write this block to an RLP output. * * @param writer The RLP writer. */ public void writeTo(RLPWriter writer) { writer.writeList(header::writeTo); body.writeTo(writer); } } cava-0.6.0/eth/src/main/java/net/consensys/cava/eth/BlockBody.java000066400000000000000000000070321341750772100246730ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.eth; import static java.util.Objects.requireNonNull; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.rlp.RLP; import net.consensys.cava.rlp.RLPException; import net.consensys.cava.rlp.RLPReader; import net.consensys.cava.rlp.RLPWriter; import java.util.ArrayList; import java.util.List; import com.google.common.base.Objects; /** * An Ethereum block body. */ public final class BlockBody { /** * Deserialize a block body from RLP encoded bytes. * * @param encoded The RLP encoded block. * @return The deserialized block body. * @throws RLPException If there is an error decoding the block body. */ public static BlockBody fromBytes(Bytes encoded) { requireNonNull(encoded); return RLP.decodeList(encoded, BlockBody::readFrom); } static BlockBody readFrom(RLPReader reader) { List txs = new ArrayList<>(); reader.readList((listReader, l) -> { while (!listReader.isComplete()) { txs.add(listReader.readList(Transaction::readFrom)); } }); List ommers = new ArrayList<>(); reader.readList((listReader, l) -> { while (!listReader.isComplete()) { ommers.add(listReader.readList(BlockHeader::readFrom)); } }); return new BlockBody(txs, ommers); } private final List transactions; private final List ommers; /** * Creates a new block body. * * @param transactions the list of transactions in this block. * @param ommers the list of ommers for this block. */ public BlockBody(List transactions, List ommers) { requireNonNull(transactions); requireNonNull(ommers); this.transactions = transactions; this.ommers = ommers; } /** * @return the transactions of the block. */ public List transactions() { return transactions; } /** * @return the list of ommers for this block. */ public List ommers() { return ommers; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof BlockBody)) { return false; } BlockBody other = (BlockBody) obj; return transactions.equals(other.transactions) && ommers.equals(other.ommers); } @Override public int hashCode() { return Objects.hashCode(transactions, ommers); } /** * @return The RLP serialized form of this block body. */ public Bytes toBytes() { return RLP.encodeList(this::writeTo); } @Override public String toString() { return "BlockBody{" + "transactions=" + transactions + ", ommers=" + ommers + '}'; } void writeTo(RLPWriter writer) { writer.writeList(listWriter -> { for (Transaction tx : transactions) { listWriter.writeList(tx::writeTo); } }); writer.writeList(listWriter -> { for (BlockHeader ommer : ommers) { listWriter.writeList(ommer::writeTo); } }); } } cava-0.6.0/eth/src/main/java/net/consensys/cava/eth/BlockHeader.java000066400000000000000000000234351341750772100251730ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.eth; import static java.util.Objects.requireNonNull; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.rlp.RLP; import net.consensys.cava.rlp.RLPReader; import net.consensys.cava.rlp.RLPWriter; import net.consensys.cava.units.bigints.UInt256; import net.consensys.cava.units.ethereum.Gas; import java.time.Instant; import javax.annotation.Nullable; import com.google.common.base.Objects; /** * An Ethereum block header. */ public final class BlockHeader { /** * Deserialize a block header from RLP encoded bytes. * * @param encoded The RLP encoded block. * @return The deserialized block header. */ public static BlockHeader fromBytes(Bytes encoded) { requireNonNull(encoded); return RLP.decodeList(encoded, BlockHeader::readFrom); } /** * Deserialize a block header from an RLP input. * * @param reader The RLP reader. * @return The deserialized block header. */ static BlockHeader readFrom(RLPReader reader) { Bytes parentHashBytes = reader.readValue(); return new BlockHeader( parentHashBytes.isEmpty() ? null : Hash.fromBytes(parentHashBytes), Hash.fromBytes(reader.readValue()), Address.fromBytes(reader.readValue()), Hash.fromBytes(reader.readValue()), Hash.fromBytes(reader.readValue()), Hash.fromBytes(reader.readValue()), reader.readValue(), UInt256.fromBytes(reader.readValue()), UInt256.fromBytes(reader.readValue()), Gas.valueOf(reader.readUInt256()), Gas.valueOf(reader.readUInt256()), Instant.ofEpochSecond(reader.readLong()), reader.readValue(), Hash.fromBytes(reader.readValue()), reader.readValue()); } @Nullable private final Hash parentHash; private final Hash ommersHash; private final Address coinbase; private final Hash stateRoot; private final Hash transactionsRoot; private final Hash receiptsRoot; private final Bytes logsBloom; private final UInt256 difficulty; private final UInt256 number; private final Gas gasLimit; private final Gas gasUsed; private final Instant timestamp; private final Bytes extraData; private final Hash mixHash; private final Bytes nonce; private Hash hash; /** * Creates a new block header. * * @param parentHash the parent hash, or null. * @param ommersHash the ommers hash. * @param coinbase the block's beneficiary address. * @param stateRoot the hash associated with the state tree. * @param transactionsRoot the hash associated with the transactions tree. * @param receiptsRoot the hash associated with the transaction receipts tree. * @param logsBloom the bloom filter of the logs of the block. * @param difficulty the difficulty of the block. * @param number the number of the block. * @param gasLimit the gas limit of the block. * @param gasUsed the gas used for the block. * @param timestamp the timestamp of the block. * @param extraData the extra data stored with the block. * @param mixHash the hash associated with computional work on the block. * @param nonce the nonce of the block. */ public BlockHeader( @Nullable Hash parentHash, Hash ommersHash, Address coinbase, Hash stateRoot, Hash transactionsRoot, Hash receiptsRoot, Bytes logsBloom, UInt256 difficulty, UInt256 number, Gas gasLimit, Gas gasUsed, Instant timestamp, Bytes extraData, Hash mixHash, Bytes nonce) { requireNonNull(ommersHash); requireNonNull(coinbase); requireNonNull(stateRoot); requireNonNull(transactionsRoot); requireNonNull(receiptsRoot); requireNonNull(logsBloom); requireNonNull(difficulty); requireNonNull(number); requireNonNull(gasLimit); requireNonNull(gasUsed); requireNonNull(timestamp); requireNonNull(extraData); requireNonNull(mixHash); requireNonNull(nonce); this.parentHash = parentHash; this.ommersHash = ommersHash; this.coinbase = coinbase; this.stateRoot = stateRoot; this.transactionsRoot = transactionsRoot; this.receiptsRoot = receiptsRoot; this.logsBloom = logsBloom; this.difficulty = difficulty; this.number = number; this.gasLimit = gasLimit; this.gasUsed = gasUsed; this.timestamp = timestamp; this.extraData = extraData; this.mixHash = mixHash; this.nonce = nonce; } /** * @return the block's beneficiary's address. */ public Address coinbase() { return coinbase; } /** * @return the difficulty of the block. */ public UInt256 difficulty() { return difficulty; } /** * @return the extra data stored with the block. */ public Bytes extraData() { return extraData; } /** * @return the gas limit of the block. */ public Gas gasLimit() { return gasLimit; } /** * @return the gas used for the block. */ public Gas gasUsed() { return gasUsed; } /** * @return the hash of the block header. */ public Hash hash() { if (hash == null) { Bytes rlp = toBytes(); hash = Hash.hash(rlp); } return hash; } /** * @return the bloom filter of the logs of the block. */ public Bytes logsBloom() { return logsBloom; } /** * @return the hash associated with computional work on the block. */ public Hash mixHash() { return mixHash; } /** * @return the nonce of the block. */ public Bytes nonce() { return nonce; } /** * @return the number of the block. */ public UInt256 number() { return number; } /** * @return the ommer hash. */ public Hash ommersHash() { return ommersHash; } /** * @return the parent hash, or null if none was available. */ @Nullable public Hash parentHash() { return parentHash; } /** * @return the hash associated with the transaction receipts tree. */ public Hash receiptsRoot() { return receiptsRoot; } /** * @return the hash associated with the state tree. */ public Hash stateRoot() { return stateRoot; } /** * @return the timestamp of the block. */ public Instant timestamp() { return timestamp; } /** * @return the hash associated with the transactions tree. */ public Hash transactionsRoot() { return transactionsRoot; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof BlockHeader)) { return false; } BlockHeader other = (BlockHeader) obj; return Objects.equal(parentHash, other.parentHash) && ommersHash.equals(other.ommersHash) && coinbase.equals(other.coinbase) && stateRoot.equals(other.stateRoot) && transactionsRoot.equals(other.transactionsRoot) && receiptsRoot.equals(other.receiptsRoot) && logsBloom.equals(other.logsBloom) && difficulty.equals(other.difficulty) && number.equals(other.number) && gasLimit.equals(other.gasLimit) && gasUsed.equals(other.gasUsed) && timestamp.equals(other.timestamp) && extraData.equals(other.extraData) && mixHash.equals(other.mixHash) && nonce.equals(other.nonce); } @Override public int hashCode() { return Objects.hashCode( parentHash, ommersHash, coinbase, stateRoot, transactionsRoot, receiptsRoot, logsBloom, difficulty, number, gasLimit, gasUsed, timestamp, extraData, mixHash, nonce); } @Override public String toString() { return "BlockHeader{" + "parentHash=" + parentHash + ", ommersHash=" + ommersHash + ", coinbase=" + coinbase + ", stateRoot=" + stateRoot + ", transactionsRoot=" + transactionsRoot + ", receiptsRoot=" + receiptsRoot + ", logsBloom=" + logsBloom + ", difficulty=" + difficulty + ", number=" + number + ", gasLimit=" + gasLimit + ", gasUsed=" + gasUsed + ", timestamp=" + timestamp + ", extraData=" + extraData + ", mixHash=" + mixHash + ", nonce=" + nonce + '}'; } /** * @return The RLP serialized form of this block header. */ public Bytes toBytes() { return RLP.encodeList(this::writeTo); } /** * Write this block header to an RLP output. * * @param writer The RLP writer. */ void writeTo(RLPWriter writer) { writer.writeValue((parentHash != null) ? parentHash.toBytes() : Bytes.EMPTY); writer.writeValue(ommersHash.toBytes()); writer.writeValue(coinbase.toBytes()); writer.writeValue(stateRoot.toBytes()); writer.writeValue(transactionsRoot.toBytes()); writer.writeValue(receiptsRoot.toBytes()); writer.writeValue(logsBloom); writer.writeValue(difficulty.toMinimalBytes()); writer.writeValue(number.toMinimalBytes()); writer.writeValue(gasLimit.toMinimalBytes()); writer.writeValue(gasUsed.toMinimalBytes()); writer.writeLong(timestamp.getEpochSecond()); writer.writeValue(extraData); writer.writeValue(mixHash.toBytes()); writer.writeValue(nonce); } } cava-0.6.0/eth/src/main/java/net/consensys/cava/eth/Hash.java000066400000000000000000000060331341750772100237060ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.eth; import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; import static net.consensys.cava.crypto.Hash.keccak256; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import com.google.common.base.Objects; /** * An Ethereum hash. */ public final class Hash { /** * Create a Hash from Bytes. * *

* The hash must be exactly 32 bytes. * * @param bytes The bytes for this hash. * @return A hash. * @throws IllegalArgumentException If {@code bytes.size() != 32}. */ public static Hash fromBytes(Bytes bytes) { requireNonNull(bytes); checkArgument(bytes.size() == SIZE, "Expected %s bytes but got %s", SIZE, bytes.size()); return new Hash(Bytes32.wrap(bytes)); } /** * Create a Hash from Bytes32. * * @param bytes The bytes for this hash. * @return A hash. */ public static Hash fromBytes(Bytes32 bytes) { return new Hash(bytes); } /** * Parse a hexadecimal string into a {@link Hash}. * * @param str The hexadecimal string to parse, which may or may not start with "0x". That representation may contain * less than 32 bytes, in which case the result is left padded with zeros. * @return The value corresponding to {@code str}. * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation or * contains more than 32 bytes. */ public static Hash fromHexString(String str) { return new Hash(Bytes32.fromHexStringLenient(str)); } public static Hash hash(Bytes value) { return new Hash(keccak256(value)); } private static final int SIZE = 32; private final Bytes32 delegate; private Hash(Bytes32 value) { requireNonNull(value); this.delegate = value; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Hash)) { return false; } Hash hash = (Hash) obj; return delegate.equals(hash.delegate); } @Override public int hashCode() { return Objects.hashCode(delegate); } @Override public String toString() { return "Hash{" + delegate.toHexString() + '}'; } /** * @return A hex-encoded version of the hash. */ public String toHexString() { return delegate.toHexString(); } /** * @return The bytes for this hash. */ public Bytes toBytes() { return delegate; } } cava-0.6.0/eth/src/main/java/net/consensys/cava/eth/Transaction.java000066400000000000000000000251341341750772100253130ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.eth; import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; import static net.consensys.cava.crypto.Hash.keccak256; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.crypto.SECP256K1; import net.consensys.cava.rlp.RLP; import net.consensys.cava.rlp.RLPException; import net.consensys.cava.rlp.RLPReader; import net.consensys.cava.rlp.RLPWriter; import net.consensys.cava.units.bigints.UInt256; import net.consensys.cava.units.ethereum.Gas; import net.consensys.cava.units.ethereum.Wei; import java.math.BigInteger; import javax.annotation.Nullable; import com.google.common.base.Objects; /** * An Ethereum transaction. */ public final class Transaction { // The base of the signature v-value private static final int V_BASE = 27; /** * Deserialize a transaction from RLP encoded bytes. * * @param encoded The RLP encoded transaction. * @return The de-serialized transaction. * @throws RLPException If there is an error decoding the transaction. */ public static Transaction fromBytes(Bytes encoded) { return fromBytes(encoded, false); } /** * Deserialize a transaction from RLP encoded bytes. * * @param encoded The RLP encoded transaction. * @param lenient If {@code true}, the RLP decoding will be lenient toward any non-minimal encoding. * @return The de-serialized transaction. * @throws RLPException If there is an error decoding the transaction. */ public static Transaction fromBytes(Bytes encoded, boolean lenient) { requireNonNull(encoded); return RLP.decode(encoded, lenient, (reader) -> { Transaction tx = reader.readList(Transaction::readFrom); if (!reader.isComplete()) { throw new RLPException("Additional bytes present at the end of the encoded transaction"); } return tx; }); } /** * Deserialize a transaction from an RLP input. * * @param reader The RLP reader. * @return The de-serialized transaction. * @throws RLPException If there is an error decoding the transaction. */ public static Transaction readFrom(RLPReader reader) { UInt256 nonce = reader.readUInt256(); Wei gasPrice = Wei.valueOf(reader.readUInt256()); Gas gasLimit = Gas.valueOf(reader.readLong()); Bytes addressBytes = reader.readValue(); Address address; try { address = addressBytes.isEmpty() ? null : Address.fromBytes(addressBytes); } catch (IllegalArgumentException e) { throw new RLPException("Value is the wrong size to be an address", e); } Wei value = Wei.valueOf(reader.readUInt256()); Bytes payload = reader.readValue(); byte encodedV = reader.readByte(); Bytes rbytes = reader.readValue(); if (rbytes.size() > 32) { throw new RLPException("r-value of the signature is " + rbytes.size() + ", it should be at most 32 bytes"); } BigInteger r = rbytes.toUnsignedBigInteger(); Bytes sbytes = reader.readValue(); if (sbytes.size() > 32) { throw new RLPException("s-value of the signature is " + sbytes.size() + ", it should be at most 32 bytes"); } BigInteger s = sbytes.toUnsignedBigInteger(); if (!reader.isComplete()) { throw new RLPException("Additional bytes present at the end of the encoding"); } byte v = (byte) ((int) encodedV - V_BASE); SECP256K1.Signature signature; try { signature = SECP256K1.Signature.create(v, r, s); } catch (IllegalArgumentException e) { throw new RLPException("Invalid signature: " + e.getMessage()); } try { return new Transaction(nonce, gasPrice, gasLimit, address, value, payload, signature); } catch (IllegalArgumentException e) { throw new RLPException(e.getMessage(), e); } } private final UInt256 nonce; private final Wei gasPrice; private final Gas gasLimit; @Nullable private final Address to; private final Wei value; private final SECP256K1.Signature signature; private final Bytes payload; private volatile Hash hash; private volatile Address sender; private volatile Boolean validSignature; /** * Create a transaction. * * @param nonce The transaction nonce. * @param gasPrice The transaction gas price. * @param gasLimit The transaction gas limit. * @param to The target contract address, if any. * @param value The amount of Eth to transfer. * @param payload The transaction payload. * @param keyPair A keypair to generate the transaction signature with. */ public Transaction( UInt256 nonce, Wei gasPrice, Gas gasLimit, @Nullable Address to, Wei value, Bytes payload, SECP256K1.KeyPair keyPair) { this( nonce, gasPrice, gasLimit, to, value, payload, generateSignature(nonce, gasPrice, gasLimit, to, value, payload, keyPair)); } /** * Create a transaction. * * @param nonce The transaction nonce. * @param gasPrice The transaction gas price. * @param gasLimit The transaction gas limit. * @param to The target contract address, if any. * @param value The amount of Eth to transfer. * @param payload The transaction payload. * @param signature The transaction signature. */ public Transaction( UInt256 nonce, Wei gasPrice, Gas gasLimit, @Nullable Address to, Wei value, Bytes payload, SECP256K1.Signature signature) { requireNonNull(nonce); checkArgument(nonce.compareTo(UInt256.ZERO) >= 0, "nonce must be >= 0"); requireNonNull(gasPrice); requireNonNull(value); requireNonNull(signature); requireNonNull(payload); this.nonce = nonce; this.gasPrice = gasPrice; this.gasLimit = gasLimit; this.to = to; this.value = value; this.signature = signature; this.payload = payload; } /** * @return The transaction nonce. */ public UInt256 nonce() { return nonce; } /** * @return The transaction gas price. */ public Wei gasPrice() { return gasPrice; } /** * @return The transaction gas limit. */ public Gas gasLimit() { return gasLimit; } /** * @return The target contract address, or null if not present. */ @Nullable public Address to() { return to; } /** * @return {@code true} if the transaction is a contract creation ({@code to} address is {@code null}). */ public boolean isContractCreation() { return to == null; } /** * @return The amount of Eth to transfer. */ public Wei value() { return value; } /** * @return The transaction signature. */ public SECP256K1.Signature signature() { return signature; } /** * @return The transaction payload. */ public Bytes payload() { return payload; } /** * Calculate and return the hash for this transaction. * * @return The hash. */ public Hash hash() { if (hash != null) { return hash; } Bytes rlp = toBytes(); hash = Hash.hash(rlp); return hash; } /** * @return The sender of the transaction, or {@code null} if the signature is invalid. */ @Nullable public Address sender() { if (validSignature != null) { return sender; } return verifySignatureAndGetSender(); } @Nullable private Address verifySignatureAndGetSender() { Bytes data = signatureData(nonce, gasPrice, gasLimit, to, value, payload); SECP256K1.PublicKey publicKey = SECP256K1.PublicKey.recoverFromSignature(data, signature); if (publicKey == null) { validSignature = false; } else { sender = Address.fromBytes(Bytes.wrap(keccak256(publicKey.bytesArray()), 12, 20)); validSignature = true; } return sender; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Transaction)) { return false; } Transaction that = (Transaction) obj; return nonce.equals(that.nonce) && gasPrice.equals(that.gasPrice) && gasLimit.equals(that.gasLimit) && Objects.equal(to, that.to) && value.equals(that.value) && signature.equals(that.signature) && payload.equals(that.payload); } @Override public int hashCode() { return Objects.hashCode(nonce, gasPrice, gasLimit, to, value, signature, payload); } @Override public String toString() { return String.format( "Transaction{nonce=%s, gasPrice=%s, gasLimit=%s, to=%s, value=%s, signature=%s, payload=%s", nonce, gasPrice, gasLimit, to, value, signature, payload); } /** * @return The RLP serialized form of this transaction. */ public Bytes toBytes() { return RLP.encodeList(this::writeTo); } /** * Write this transaction to an RLP output. * * @param writer The RLP writer. */ public void writeTo(RLPWriter writer) { writer.writeUInt256(nonce); writer.writeUInt256(gasPrice.toUInt256()); writer.writeLong(gasLimit.toLong()); writer.writeValue((to != null) ? to.toBytes() : Bytes.EMPTY); writer.writeUInt256(value.toUInt256()); writer.writeValue(payload); writer.writeByte((byte) ((int) signature.v() + V_BASE)); writer.writeBigInteger(signature.r()); writer.writeBigInteger(signature.s()); } private static SECP256K1.Signature generateSignature( UInt256 nonce, Wei gasPrice, Gas gasLimit, @Nullable Address to, Wei value, Bytes payload, SECP256K1.KeyPair keyPair) { return SECP256K1.sign(signatureData(nonce, gasPrice, gasLimit, to, value, payload), keyPair); } private static Bytes signatureData( UInt256 nonce, Wei gasPrice, Gas gasLimit, @Nullable Address to, Wei value, Bytes payload) { return RLP.encodeList(writer -> { writer.writeUInt256(nonce); writer.writeValue(gasPrice.toMinimalBytes()); writer.writeValue(gasLimit.toMinimalBytes()); writer.writeValue((to != null) ? to.toBytes() : Bytes.EMPTY); writer.writeValue(value.toMinimalBytes()); writer.writeValue(payload); }); } } cava-0.6.0/eth/src/main/java/net/consensys/cava/eth/package-info.java000066400000000000000000000005471341750772100253530ustar00rootroot00000000000000/** * Classes and utilities for working in the Ethereum domain. * *

* These classes are included in the standard Cava distribution, or separately when using the gradle dependency * 'net.consensys.cava:cava-eth' (cava-eth.jar). */ @ParametersAreNonnullByDefault package net.consensys.cava.eth; import javax.annotation.ParametersAreNonnullByDefault; cava-0.6.0/eth/src/test/000077500000000000000000000000001341750772100147645ustar00rootroot00000000000000cava-0.6.0/eth/src/test/java/000077500000000000000000000000001341750772100157055ustar00rootroot00000000000000cava-0.6.0/eth/src/test/java/net/000077500000000000000000000000001341750772100164735ustar00rootroot00000000000000cava-0.6.0/eth/src/test/java/net/consensys/000077500000000000000000000000001341750772100205175ustar00rootroot00000000000000cava-0.6.0/eth/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100214315ustar00rootroot00000000000000cava-0.6.0/eth/src/test/java/net/consensys/cava/eth/000077500000000000000000000000001341750772100222115ustar00rootroot00000000000000cava-0.6.0/eth/src/test/java/net/consensys/cava/eth/BlockBodyTest.java000066400000000000000000000031741341750772100255710ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.eth; import static net.consensys.cava.eth.BlockHeaderTest.generateBlockHeader; import static net.consensys.cava.eth.TransactionTest.generateTransaction; import static org.junit.jupiter.api.Assertions.assertEquals; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.junit.BouncyCastleExtension; import java.util.Arrays; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(BouncyCastleExtension.class) class BlockBodyTest { @Test void testRLPRoundtrip() { BlockBody blockBody = new BlockBody( Arrays.asList(generateTransaction(), generateTransaction(), generateTransaction(), generateTransaction()), Arrays.asList( generateBlockHeader(), generateBlockHeader(), generateBlockHeader(), generateBlockHeader(), generateBlockHeader(), generateBlockHeader())); Bytes encoded = blockBody.toBytes(); BlockBody read = BlockBody.fromBytes(encoded); assertEquals(blockBody, read); } } cava-0.6.0/eth/src/test/java/net/consensys/cava/eth/BlockHeaderTest.java000066400000000000000000000037741341750772100260720ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.eth; import static org.junit.jupiter.api.Assertions.assertEquals; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.junit.BouncyCastleExtension; import net.consensys.cava.units.bigints.UInt256; import net.consensys.cava.units.ethereum.Gas; import java.time.Instant; import java.time.temporal.ChronoUnit; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(BouncyCastleExtension.class) class BlockHeaderTest { static BlockHeader generateBlockHeader() { return new BlockHeader( Hash.fromBytes(Bytes.random(32)), Hash.fromBytes(Bytes.random(32)), Address.fromBytes(Bytes.fromHexString("0x0102030405060708091011121314151617181920")), Hash.fromBytes(Bytes.random(32)), Hash.fromBytes(Bytes.random(32)), Hash.fromBytes(Bytes.random(32)), Bytes.random(8), UInt256.fromBytes(Bytes.random(32)), UInt256.fromBytes(Bytes.random(32)), Gas.valueOf(UInt256.fromBytes(Bytes.random(6))), Gas.valueOf(UInt256.fromBytes(Bytes.random(6))), Instant.now().truncatedTo(ChronoUnit.SECONDS), Bytes.random(22), Hash.fromBytes(Bytes.random(32)), Bytes.random(8)); } @Test void rlpRoundtrip() { BlockHeader blockHeader = generateBlockHeader(); BlockHeader read = BlockHeader.fromBytes(blockHeader.toBytes()); assertEquals(blockHeader, read); } } cava-0.6.0/eth/src/test/java/net/consensys/cava/eth/BlockTest.java000066400000000000000000000032621341750772100247510ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.eth; import static net.consensys.cava.eth.BlockHeaderTest.generateBlockHeader; import static net.consensys.cava.eth.TransactionTest.generateTransaction; import static org.junit.jupiter.api.Assertions.assertEquals; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.junit.BouncyCastleExtension; import java.util.Arrays; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(BouncyCastleExtension.class) class BlockTest { @Test void testRoundtripRLP() { Block block = new Block( generateBlockHeader(), new BlockBody( Arrays.asList(generateTransaction(), generateTransaction(), generateTransaction(), generateTransaction()), Arrays.asList( generateBlockHeader(), generateBlockHeader(), generateBlockHeader(), generateBlockHeader(), generateBlockHeader(), generateBlockHeader()))); Bytes encoded = block.toBytes(); Block read = Block.fromBytes(encoded); assertEquals(block, read); } } cava-0.6.0/eth/src/test/java/net/consensys/cava/eth/TransactionTest.java000066400000000000000000000042151341750772100262030ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.eth; import static net.consensys.cava.crypto.Hash.keccak256; import static org.junit.jupiter.api.Assertions.assertEquals; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.crypto.SECP256K1; import net.consensys.cava.junit.BouncyCastleExtension; import net.consensys.cava.units.bigints.UInt256; import net.consensys.cava.units.ethereum.Gas; import net.consensys.cava.units.ethereum.Wei; import java.math.BigInteger; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(BouncyCastleExtension.class) class TransactionTest { static Transaction generateTransaction() { return generateTransaction(SECP256K1.KeyPair.random()); } static Transaction generateTransaction(SECP256K1.KeyPair keyPair) { return new Transaction( UInt256.valueOf(0), Wei.valueOf(BigInteger.valueOf(5L)), Gas.valueOf(10L), Address.fromBytes(Bytes.fromHexString("0x0102030405060708091011121314151617181920")), Wei.valueOf(10L), Bytes.of(1, 2, 3, 4), keyPair); } @Test void testRLPRoundTrip() { Transaction tx = generateTransaction(); Bytes encoded = tx.toBytes(); Transaction read = Transaction.fromBytes(encoded); assertEquals(tx, read); } @Test void shouldGetSenderFromSignature() { SECP256K1.KeyPair keyPair = SECP256K1.KeyPair.random(); Address sender = Address.fromBytes(Bytes.wrap(keccak256(keyPair.publicKey().bytesArray()), 12, 20)); Transaction tx = generateTransaction(keyPair); assertEquals(sender, tx.sender()); } } cava-0.6.0/gradle.properties000066400000000000000000000000331341750772100160060ustar00rootroot00000000000000kotlin.code.style=official cava-0.6.0/gradle/000077500000000000000000000000001341750772100136745ustar00rootroot00000000000000cava-0.6.0/gradle/check-licenses.gradle000066400000000000000000000110201341750772100177260ustar00rootroot00000000000000/** * Check that the licenses of our 3rd parties are in our acceptedLicenses list. * * run it with "gradle checkLicenses" * * To add new accepted licenses you need to update this script. * Some products may be available with multiple licenses. In this case you must update * this script to add it in the downloadLicenses#licenses. */ // Some parts of this code comes from Zipkin/https://github.com/openzipkin/zipkin/pull/852 // Zipkin itself is under Apache License. /** * The lists of the license we accept. */ ext.acceptedLicenses = [ 'Apache License, Version 2.0', 'Bouncy Castle Licence', 'CC0', 'The 2-Clause BSD License', 'The 3-Clause BSD License', 'Common Development and Distribution License 1.0', 'Eclipse Distribution License - v 1.0', 'Eclipse Public License 1.0', 'Eclipse Public License 2.0', 'The MIT License', 'Unicode/ICU License', ]*.toLowerCase() /** * This is the configuration we need for our licenses plugin: 'com.github.hierynomus.license' * This plugin generates a list of dependencies. */ downloadLicenses { includeProjectDependencies = true reportByDependency = false reportByLicenseType = true dependencyConfiguration = 'compileClasspath' ext.apache2 = license( 'Apache License, Version 2.0', 'http://opensource.org/licenses/Apache-2.0') ext.bsd = license( 'The 2-Clause BSD License', 'https://opensource.org/licenses/BSD-2-Clause') ext.bsd3Clause = license( 'The 3-Clause BSD License', 'http://opensource.org/licenses/BSD-3-Clause') ext.cddl1 = license( 'Common Development and Distribution License 1.0', 'https://opensource.org/licenses/CDDL-1.0') ext.edl1 = license( 'Eclipse Distribution License - v 1.0', 'http://www.eclipse.org/org/documents/edl-v10.html') ext.epl1 = license( 'Eclipse Public License 1.0', 'https://opensource.org/licenses/EPL-1.0') ext.epl2 = license( 'Eclipse Public License 2.0', 'https://opensource.org/licenses/EPL-2.0') ext.mit = license( 'The MIT License', 'https://opensource.org/licenses/MIT') aliases = [ (apache2): [ 'Apache', 'Apache 2', 'Apache 2.0', 'Apache-2.0', 'Apache License', 'Apache License 2.0', 'Apache License Version 2.0', 'Apache License, Version 2.0', 'Apache Software Licenses', 'Apache v2', 'ASL, Version 2', 'The Apache License, Version 2.0', 'The Apache Software License, Version 2.0', ], (bsd): [ 'Berkeley Software Distribution (BSD) License', 'BSD', 'BSD licence', 'BSD Licence', 'BSD License', 'New BSD License', 'The BSD Licence', 'The BSD License', ], (bsd3Clause): [ 'BSD 3-Clause', 'BSD 3-Clause "New" or "Revised" License (BSD-3-Clause)', 'The 3-Clause BSD License', 'The BSD 3-Clause License', ], (cddl1): [ 'CDDL-1.0', 'Common Development and Distribution License 1.0', 'Dual license consisting of the CDDL v1.1 and GPL v2', ], (edl1): [ 'Eclipse Distribution License - v 1.0', ], (epl1): [ 'Eclipse Public License - v 1.0', ], (epl2): [ 'Eclipse Public License v2.0', 'Eclipse Public License - v 2.0', ], (mit): [ 'MIT license', 'MIT License', ], ] licenses = [ (group('cava')): apache2, // https://checkerframework.org/manual/#license // The more permissive MIT License applies to code that you might want // to include in your own program, such as the annotations and run-time utility classes. (group('org.checkerframework')): mit ] } task checkLicenses { description "Verify that all dependencies use white-listed licenses." dependsOn ':downloadLicenses' def bads = "" doLast { def xml = new XmlParser().parse("$rootProject.buildDir/reports/license/license-dependency.xml") xml.each { license -> if (!acceptedLicenses.contains((license.@name).toLowerCase())) { def depStrings = [] license.dependency.each { depStrings << it.text() } bads = bads + depStrings + " => -${license.@name}- \n" } } if (bads != "") { throw new GradleException("Some 3rd parties are using licenses not in our accepted licenses list:\n" + bads + "If it's a license acceptable for us, add it in the file check-licenses.gradle.\n" + "Be careful, some 3rd parties may accept multiple licenses.\n" + "In this case, select the one you want to use by changing downloadLicenses.licenses\n" ) } } } check.dependsOn checkLicenses cava-0.6.0/gradle/eclipse-java-consensys-style.xml000066400000000000000000001075441341750772100221540ustar00rootroot00000000000000 cava-0.6.0/gradle/greclipse-gradle-consensys-style.properties000066400000000000000000000046711341750772100244130ustar00rootroot00000000000000#Whether to use 'space', 'tab' or 'mixed' (both) characters for indentation. #The default value is 'tab'. org.eclipse.jdt.core.formatter.tabulation.char=space #Number of spaces used for indentation in case 'space' characters #have been selected. The default value is 4. org.eclipse.jdt.core.formatter.tabulation.size=2 #Number of spaces used for indentation in case 'mixed' characters #have been selected. The default value is 4. org.eclipse.jdt.core.formatter.indentation.size=1 #Whether or not indentation characters are inserted into empty lines. #The default value is 'true'. org.eclipse.jdt.core.formatter.indent_empty_lines=false #Number of spaces used for multiline indentation. #The default value is 2. groovy.formatter.multiline.indentation=1 #Length after which list are considered too long. These will be wrapped. #The default value is 30. groovy.formatter.longListLength=30 #Whether opening braces position shall be the next line. #The default value is 'same'. groovy.formatter.braces.start=same #Whether closing braces position shall be the next line. #The default value is 'next'. groovy.formatter.braces.end=next #Remove unnecessary semicolons. The default value is 'false'. groovy.formatter.remove.unnecessary.semicolons=false org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert cava-0.6.0/gradle/spotless.license.java000066400000000000000000000011111341750772100200260ustar00rootroot00000000000000/* * Copyright $YEAR ConsenSys AG. * * 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. */ cava-0.6.0/gradle/wrapper/000077500000000000000000000000001341750772100153545ustar00rootroot00000000000000cava-0.6.0/gradle/wrapper/gradle-wrapper.jar000066400000000000000000001555611341750772100210030ustar00rootroot00000000000000PKA META-INF/PKAGBWMETA-INF/MANIFEST.MFMLK-. K-*ϳR03-IM+I, dZ)%bµrrPKAorg/PKA org/gradle/PKAorg/gradle/wrapper/PKA"8M+6/ -org/gradle/wrapper/BootstrapMainStarter.classV[wFVbD 8@bH v.6!$C (R*\z}/}m_ZNk+/V33}3;_{~"3AFZƜ zq6sx9dr>.byY!ԁ%eʸk*C݈0e:X b)v`% d\. va2hfO1bE)e9fJi[e$SE{M(+Z!6bhy'oZ*M.2jFV-[ 2 ŖjRWb2c[Qt%t>ZE1C 9c( jQ9Ӱ-;rT䅅tJ.Q֎aK]lG$B WJ{Lt >r"+,Tg\y{y'F.+|oOG^Vd82+z>jvTy^Q[=}l!8Q[N(߉ԾgancU]~ۼ@_,-ii"36/5^WC sjq&Kܛ,^'㥮ίX\%rK8 a]HT{JN7:Z֠y8R˙Q@3nשr/B(*`l5m9M^XeȄp^39,3%զ A{xL! m/҇{7}şL(68~ ϸֲQ9 GphH1Ha?(7_$ e [M9ᡅG LUS3d!ZΠCEIbÆWhDJt ?fc$BZRD$^!?㈽mb#fHfv>YzAh|.sd4ԎI&IωÛK'1%Y<Ͽ=z;5W3 |!J+wzRc}:uI ڱcU+"&Qq`">I(f/WDd|‹xP7k3,nKz%rYhID 9jIqy=U@=gȐKNIA]>NY ^8OP0& Ǻu>J$yK6U);-yL%FsY,>- ffD%md+'[' ]K.ȻH2&WJv,62;RW? S-xyawQ71VAd2 `qɋ82'Y2[LͼI}ͫ. fz$o3lbL7c>Z]NfLWf@١ SΔ~ ֟҅ZBG,ufQ]V}C)3DSԮE]Zja:SՕkEv5- lQr5s:"3Kd TX*KRWb5qWObM5;Fm7ltjݚZ:i@.w=⥒dQ*T2WLQhжjXIUA'QGDz ٞ\ew$(VIϵGtM7beLMMXrŲʦTgaUzkPQEty[&`'4,[ZӾS?B(J99gO)s˾K)aǓ%L[i >_,)z6#yϼ;󕊾8,JX Y*!prYc0q`@P=z/_H;l'9G|V64JS9}Úm&?^Co;*5#=ŪLr,wsEpU| !5T44] U0خ%uT??Cf4~C5*n U| i>m Jj=tнi-͸V\o#vd'^:zο^b"؏c8,ddb(u)=PBq5B/5 8$S&6ISA*pXOIj ދѡQ8 K x~^In' #)TE3USW|PT‰l8 2EtfG0>B˨"*yp+~G ' *TA4%%$%-HSB#hEZkn46 M?sLa|3?OΰR>OfY|: ;pwO=hK6pSp ij 7vf ep1N948 b)C$pA̛y ٙǐE:Fb @}(Z񡟁I+i4t7/EH)xP8(CWmf!Aw=m!_iʭ謭!"o@@𘂯/SH ǿb~3Vjm@M2y+k#Z1"L3L LL%ehe4>Z/2\sZ93w~,Ǔs)ľemY{ iʨl܈i%N&Ô h/? =-u#΍iĹو!mKDs&b2N^}O~.|aܕX٘~f,-BRyMZާ"^52Qy*x% ޯ >|P}*kM`f/2"28 C!dl ͑L*iہ@Y8g,+Z ! ZT}f} ʞ9Ĩ.0k&FPKAyL1org/gradle/wrapper/DownloadProgressListener.classu @E+jDE@ۖEERI#3c[>4v9OĪ.r%J[M DŽ,]8kߟ_PU:]3aG\^&at"-EeY˛8\3K tЎBNk؁PKA)zü 3org/gradle/wrapper/ExclusiveFileAccessManager.classVsWY:vĭ:I[ɉRdqc;i䘺 I҉jWVq̭PH 6>0ig3<^wv%ǷG>{wݿwN!i8/h8@5<@gTfԳ(R-2Y V3.\TU4M`bߊr_n* 67ߌ˭X2W_íx_ j@艡sGOh^23dg93mCSMǟ4htXE@6]9.", R\zR :)>j9rZބ9mKe-Y\'FYZ27̢-3Y.K/srVKrز` +11gGGf@LqXnFoٙ'?3dڶ؟^鋲+er]œѡ¬8ҮŜJ\ Y]fe/T}虫\LCmZsGuZe" Z]r,{D!or -W i@ue"5~գjX =Is73ҟ \,VjJY%saZnE>Qo3]m!2ޖB(p<(w^^]wFiч:a^WF;k;Hղ=|_dil4VMU=ۯ{Su"wgC["8C[D5mI֥*.bToguܛ%c5ܗ'5$ƥ%7p2ƅ*wT46vt~*7.l"I(WN:x@2^uc[Y}~>aج(H}IUX+ ì9Vdы(zYVy%4gdojZ -Ň8"aq,aTDpBh8I/.68..ͻ8`,SMZx h'Kq w(gpM% )AOW1NEECxG)/KS)yha oEC.8D 5F5>KFې t'}S#|[r zS?b,ss?a>8Vg) H'!Lt9BuJ Swl߽{?BnjGLl0U1Q&l3҉Ѭ4i.ҦMg8 Lݦ{یߝ$k_X}}<(bKZN. QQ*x7z&/,woQV͋{T1[bƻU܋(OE~mx~|@.Tq?*x@^|H>b > b9&T,ƤX)܏Ly:ϪxSy|A%_VU_׽Rmyt{ש1Z.hd`UGXMk+cN}7ʔe!fX}{y&(gItOe%oc=v3Yͻ&#@O8NP0ў jOZd$:q+fO_x؁MF`msGWΈBV9E5[@ Z #YMQQȖuj7+.9IQf q OF pQ4*+e#ehe:'p%+;cZ:|+Y̲eӕ&:>lfJ1w2w1)It;#X;^З11+;Үh)*xX ^ %A(xճ۫]JG>. I>(>tGY)s1:y_PE_h?[]ѭf62Jӏ)IvVɓ5ހ \]D3{̵jlK 2ɖqfO;Tճ:ꅂƃjF[FKS2KsQ29xyص^`U3-v-K0m%p_b1Ah6zN-di6KS{@yH˴]S+RڠmSW_ę [0&=ݘߘ7Ue?In1 }+5q.ȵ&cpޓPZlJw' 7fF8-Ga5ѣyN!m7,")NWZ:'b~LNql8c(m3Cz?Ue_QU MQT5z_ }nj4dɨuR鞅 ufg&VF1iJ6bb"2dxy'⌆v56c4aةa7IY4| L+k5|ΤrixpNVy. poAw= }j;'U<i?jYQӧĕ2g kG =e\G2VrC+ǫf>v_8W Lcz"iPr##%(EY Q< @|CE4jb}<<.'Lh6[8JhQ %Fcf}<Ja Mf̒pK\a!w]QL=k|x[]oXS60 C|uJ]c-/5R_jt8&ɴ +h-]"ڔ^B+>5,h֫!XT0􋶖o{='yFW7cÙy} >$Cp`-dk 6ꭸTģ~fkYo jM3M:N#gZ[tΠBV`F_M)lXW L#fZ?KdԌ<752'fd6,3;|ƬHJ٬B0 68wRН&ץш@І-LnK,Iܔ)a[ -^;f,Q 0RljH4ᆪ^آ=< 0vˑX!̠)7Avب*ـ|yjԄ^1j4z4ftt̰mK)H GK*dE!SI K%N  ҀU!3,g)F;;8Ů(j9(i4F_#-mi!ir^PH!?8GPd5O2*cL#ZYS*ʹnTSUdD1}j$i#^C%aM?4C$H`>nVުmL*n,sYTV7p i Ճw:uC\h|;]ީ.~@Ny7L}xY[͆OXOD#8h!6e[Zq&'_j` DG(A(HKD3؈B8oFB~oE1]gM5WBlB8R8ʒ=x},U,X:?OiψHa,5$v:AFCgX.Nx H+z]w7"(Tg|@濠VbC3ggȮ/K_M&d|x+m=0!|s)!=BBH<ؠ\)\"Y[+Tc$ZpƯ:?<4h11"ܘQSQF#lűG++*Z$69RN7MwѮt~}Eh:GOC::$7'>C_1QuG6<=ަ/>}dV:9oGlFs3"ESrg!zAI}#. -S\gjmC=t E,,5! ʻP /SGlQ=y-Hy h9{P OF8F%A:sTI2]O*F#`!#+OB@/opRiHf=U#>H'_ߊӘ/K!dbia);Iw5?-U]وjG84!fƃNza٪KjHԬ!Jlj%>{篔jo}ͦ$S4W}0"RrmD5hj|"rΡsʢQ\1Αq+V=cfu> 8я/NDOt h wЀ hG8:414c0'S"5X,53BN~^a;iJ>g;唺\|>WQaqЇh=\Zi$MSll):% P9t)](^AC-+*p6|nkk0rӯpډ 6?|.X0gN-419h =)u:Q>W;u^QvQNcJjY\ϽF2|\K}4yi1|L_[v bq(,AhOgQq&ThXITA5бq$ZGh=v(Bm;J`PN!e4~M7GNB Bkt sm[i+ɡ݆Nt:Xȍ~m@MwN1ͭݣve#5 @MhԿNک#acOXguT.EIjs)QT{Zpr}bcM΢9ln#V;?w.7}:%jeKF;/ '.zĬ'3bFĐMYhF`qMd(DT*)uLäʙ)N kj^S*uY^!9ӹfmO8 pR KoY@W*!p9[:Wq]⺂VQKFAYjT\/0GA/[IJ+.3ʅkH\״RNs^sh&╢nQëk(OI'E\&Cw8 e#'1r q)^5+W{A#х&e޻5z!џ*=gi #`o!NV[3NX՝tb{7:rC9P2t*hj>i -ETY+'w)T%qg1[]Ԗz0N](%eYVj]a?{&Xu*:-Všk5Y9-fuը2Z04-mHacǝ4a>ķFڎHu/8 |{ى ~vÞ*ŶHWIAg"l4Izi@K{Bj2}))FzIx.{+^ORC5zEh*.雈kf"7K@L>@2cF*%KD>W.u G.Di_@*.JZ} 5ݻ.hەڂj s: ɬQCYbNt 'Jb\ЀbUpm> r#“՝|1O!PU́1hJ8 |>V60vLq-qDP.w=.v')VP<.m HE$hI(ClUR({_qK.̠u{ uKʭ̧@Nq嶹<(#Ãb^ H8@̸H8gdn$ ErQ>]<4:2fu^Na<<083?4dckqǐ{0#bvtUwxQ; 6m!W\5]1,)@F\s ˵w/'.Hqϰ3:NWfu-Ce"!lU#;m"x*@obw=Cj[$B# %qcI![3̔JgX&ovHIFi QV(Iؔ)r /,*ꊧP*qw(P&n_ݖ7ۛPKAi?V8org/gradle/wrapper/PathAssembler$LocalDistribution.classR[KAfոոV%ZV"}SQ!B>&']wd B(1 92}`0"<,9L6a"VY|o+MSYLXf7h$2Тݖ:&NGmT;Ale ITujR $լ.cE%L>תU4z/,#_Z[6eoɮTY}"VM1 D6 .@,h> /nDk%9P1,c3G1JƢ& ~tnDk=k;fF1SQ J\)3c1ldu5پV7=ݠpܳEm ǁ>!aY| =߰[ۉ;%PKARa&org/gradle/wrapper/PathAssembler.classViC=Fc!1Mv5[Jl)v2[Ȇi.iڴMt'm"7u;wf&0{]ޝxY܍pXo\Ǎ 2X* ]*"XHNeoFloʰ'#;O>E >/T|Qij2$ E;_]m/T B̛cTI3!}5"'4A y?0hxOjXƬ4yZSa Y^,f>k~&:?/_jk~}u/6^Q[ 3]L>w~?0F?i3t6h.5nҖcի0*8 zse17=/{' |6f:5fZŲ fDlF)*hx\mQa~Xfɚk$`o"%cYV.Ĭ;3zXsm~;<S#?>3LY[³Q6P\iM{q57GHܓ!`zvpr9@FUu Tpvf)fkSګn8ZsO~!2>FVg<{]oFjGrʪSc:OJu&+x% ޻u={Y&Jџuļ5N\qTj\Dq6s/ O̐fBOUp8=} ?vЧ(gƠ+xAtAw+I..(FTŪOyM& _D8фꐻMS6-*:rrk+{)w Iv?:>! :Z^{oOOZݘPr `S r:3r*H(Sj-c^ Rb1%f7&.HǪlOvͨL|ђಂ Kժ`ŭPS)aLQ)>S9P%R5aT| 쬭tDUFJKjll#F9g kkGI-S廕_2cdO71 m/mR>5s~XE +HD&yBUk7W4%^,71ձNnplxd3f-ן/J;CxR2#Ct923,pqo ἇ!WvAyG wK*QEϱ $_ [LQ8 e ܀:앓 t7.n줋ڇn`6)n8ao[`cxa_O\~Z7p6nC>zfau cͼ|62$=dxa%1 b *F?Ig+$[: (;5#xf &'S<5Sl忋6!^/pE )gI?COn_,PKAK-org/gradle/wrapper/WrapperConfiguration.classmOAgAEpRIxb@r)WrwO/~(M|33;3εGi\| CHHTnXN!vh{k7:vFj'p,|ʐŐ{Z4Tsr;?bMՏrsbx&َ0DK]zꙚXo;' }o6Zk6?d?=r6k,kZnv\ni}/ϖ3ܔ+pւ4*|ٸ5y~ՇC\<|B'C+if;8ëlc/0E<1KL~Z4)E#] 5 e>yէ +d* U2t'+'KBZZU\ݹ|Ѵ "& EcB5ztR'iDrYs$zZCf}in~JWaT +lŘ k}d )f,ABSdr *u৯t(;|*RFWkl-!B "}jdD@8CJyEfV(k^)e%]K+ d#K֮hmsKO NiaHs&ŜMn H8:EJqR&~ma}$6$UF*V7.o,;Ԏnd/q/q2YݗaTw=TkRPe|?q!K*H*椌1Ǐ%&zR)?1Sf) jKS^F_2ާ;b6c9T䄏O }Fԯdl7^)kYVxZ)L_oZzT9,2?Q#gN,FEE_7S>=b|fNFv y5kj&|M&+srz\IC?al%~ʺ+k>><j>\c1gkushܰvC61Dt`1Ț5Or3ҿF6vIq߁{S؅%=8?i0@e8ÖKg0`ؑ \ U!j#")4rh ¬!&rP\@,d#,g⥐t?O%2W>(kF?zpjq laN]| ! UιA,'lw⍔{λHvPXBq}0U5aqJ rXu@P,.h+m{21J 2҃B~H'#崪0r0]Ъ'b*ͻ+i $ Yu8%PJHd"2\WR2<~luuiuuiut+nM6rI~ak$uYl5N3p_k~yfWfG__PKA_#gradle-wrapper-classpath.properties+(JM.)M/JLIM**+MPKAorg/gradle/cli/PKA?<S1org/gradle/cli/AbstractCommandLineConverter.classT]oA= "~C?R*Oh4!aYm` Cߢ/42Pa9{s?PF@;:v Dc@{xY9yx\Ycfs ڑ߱Vg{m[xKH[{ʅ'&?bNӵKV-)%^{m!mQaظ|jUnl㌟R{N}f[ C -kږ=,ȍ؂ak^߫^t ֔-J_,/]ctˡ+;XrCuVQ CW uhCaoܠ1a6C2QľLK &!9a{ Q23Qu {'M~rՐ3|i153 mnDevf߆J~-CH;"d9edc DqָSX`Iw/!):Fy7_|@8_A #FVX! K`R~;x)>=NRnp؜ch⟐ϔ4=c/-ePKAqj ;org/gradle/cli/AbstractPropertiesCommandLineConverter.classVWUde $ԄDmKk(ƆMi+vYB؍ ?9D=}?ңֹ&l)~̝ܙ7[ C%bQ naI|$.|1_ݓ` Sdp Ia =|ɇ"rG" ݙg ¢2su_ΘR\W,Dmk* /j)rc)f%rt^4#.5k!TЎWKn xfW9PRE(bE/jy6gjuݪil녲E`)Mx %w0r~6N!#nQ2$5[ Fыe4?̕LJKSuX֖*%KmR]4}$EIU8&wF9cƋA*5/Itt|yyaz)\Q=pʄP,UT[cyV/Wr`gtnǀ\ܮ:q/!_׮΄#fR;:\daze1ALo#'B&cl b"ve(b_r.dX7 e82*87tWZ]3u+r"Me-&P q^yzS ;} <Hq/IߙS$ s5,\쌓 0ng8cum^.NKZ*>p7/`EkmH;%(Wnm&}u:%хq[1xR4Sm"0؏aƠK`n]@xkp(Jhn|Oغ2^~ `Q0O$cQϕ ^Ƙ{n㄂BJ"e-E~cy&e3}~&S鳧I =xRn0?k*DIu `d5@ڳB}u+mq -fn6F1@ ^ T^k~# "+}B^Tm|Gq%vB׻MRc>B?9]Khpi$D'0{ɟGm򹉫(ЅN'$FyY xq+g'? lk^ ߥ} duAJC8$J7 ?v}O ى]9Ľ8“[ Q$dÄ^:m17 iZ9 isOf/węDG EB\ 1&ȽqNg uv^, T"/E2k: _ze )ݫԐRu~!ʗ؉YZsu_PKAd(org/gradle/cli/CommandLineParser$1.classA 0EhZ v庈k^('P[\x%t|>̼F" ٜb,3M#u4ulKtse\WSƖP,rUŊBӚmsIJR/5-!GJR;}/`3ookPKA,}Q ;org/gradle/cli/CommandLineParser$AfterFirstSubCommand.classVNQN),l wĊ ETH`m۵.]݊shPIL|(BHs朙9\vf":n%"aQu` "L #"$ZJbHjz>ץ\Af J0mnJj.e=FړS C0ٹ *=dNX*mfd);eª+\fڹ =W(zH2eH缪z 2iS td5k:vMMn ES9UZ;[:<@XOa 6ΥG66ė0_" F1igJgg̉n\p….Na^S'd诗 P7Tm[Q}ljܱs] ,3*ܭe0ɲߖthL "oUtbXJTgR6+Uid( # jl0 V΍Tf]w+Y:ڻZڱr4ڼ MF~a\'J4'=`uD}m襵,lnIFˀDgo1s41BDtvГ8׉ް" W+݈b-;>En"%PKA_e)3org/gradle/cli/CommandLineParser$AfterOptions.classmOP( @ 1|Dd^\:ݭGBD?xnWGF 4=s{m<4ԐT/Ii Ij01b!Squ.%[X %׫5Wˬ8{ rĐc ۿ0>>UfHUammyC[N{Cc߱ {/}{Ʒ]!U!,oፆE93Hu~Z7 CW-7.j٢4Ő^M&1B5܌J>հՅ%V|lnkSM /Iu0Š9*2g z-[cgVEYE/4 ZD9jJƸV0sd>3̟;*Dlju^8?z?4L5ݎty`x;2 LC)z>#KiHShn|AbTGV>Gۄƶ#pyIb=1Bh^Jc/" Pr&`|xL+(:rߑ4H {>+tms!PJHRH!z9YT+R!U3lifGRZdIGC}SqߖF$T•`ۤ4 :)@sRa`x.).X4 @ PKA{& <org/gradle/cli/CommandLineParser$BeforeFirstSubCommand.classVRF=+ DdЖԡ&?08$Wq0&eQ,7y>B"iLLߢ䀉 2v{Ϟ={ 2;A%$\!⊌e| V%'r2dܐpsJX-ؐPg>Pdw,x޲骭VjzZ5^WJ0Mnv p.3\w5Wrw53↍f۷r,ἥmq(r Ue VLiZMm4tr[v]ӹA,:9eJ0\4PC{5]Sj؆YuZw& ծr:Y?u  ӽ& 0BmRK&K Ma/s'3?X/մ5WH ?1`J!u,r/%គ5CԏX[o;i=6HaS b*3$ObWut> ζBF[bI/)1:9јaP~kMA:xH $ͪD/+U[?谉=6)ωa륒UM,]=i/W1ji B0PyùvR^m7#r v[(=핻WNC#NҬDK {G3W#]|Aa/rlj(=":c/ @՞^*/?X=yy|@#Ql=dcZQw!I?1(`3]ȥ qWb>)S@  EWFW<0˸^b <ۻ2{P֍]%6|,1j'D^F^jV_4/c"|G*PKA;}RForg/gradle/cli/CommandLineParser$CaseInsensitiveStringComparator.classSMo@}|  iPZJNT )R Hz$۰ŵSWp)ę(UTRξy3o6 wK neU5[ҕ6CTcH`HW<="WR`2x%}E@n;lyG\S Vuj9%OƥX!2ڰTLr\"QΧU[c$+uJl0Qhj(:h[snڬȳ8. Xi(-# 2hCz\+w; ox oXCxQ \ݶ)̡#JEN:|F⨌pD2>2>AG9=Sfʰfi3 R1#SG(>|$ ?^3)BrКguM4ͦ.FzI<m np)*]+r eE*GǩXyR3Nkԧ x|t 5r9ɤ9i;ԣ%^U|ƴ)a`dXbm,]Fwr׌me~$*+C5H]Yt}^F~/ۤP'ju&VeR̂z&+OzH2iӂ`WLf:l ̙B K02V{B?hUνΝݹ3u.p$W0 &r~n1ق&[9fq-D-z_o$Wg.dyHѠa`{ǒiUް~tS~E93O5p*0b'V/O?PKA=org/gradle/cli/CommandLineParser$OptionAwareParserState.classUmOP~V֭+n"|1dl`J_!N4Y:+[Kn;џ/45QsK_z9szϹ_(b584!W=&ӐǤ) * *f4PTqS-xnU/.٢Qh^oZ,<1k0K dfz;pF!˦ewfeͮS lxƫM oV0Y.CɎk.Z. 2Z;A*3]-j' P&+L4!ECܕ֦mďڋ.eӢx'䍁VۢfR]RTGIg1gtưXRsŬp n -~aWXc:QNWrg?e[&xU0a- U}>b[}VjjM!ʦ;tGE_:UHd+: dsTF?lc1$2GoPKA3i7org/gradle/cli/CommandLineParser$OptionComparator.classUmOP~Q6|Aq 2@h,`2C2nfIג# _FgQ]lRLsys=_,4fGfe)02$>e+Dƪ5:CϺiC<2H%g3 TLo7{}cY855\SC3=Ρo:vi;.Cl-YqB,V]cZ256 {_ymws9(7QW+O=iEh a ʁqdha׵v]lʷwx;5jY:ZȲuіyu Un~Sԭ򯄚ii] VNӭhXG9AB)<ņg +l|Pn9)l9v0lF,/=7xgD (1܋e=,H G ]6UW%`amoYS@+NW;' oWF-cbt(!&FvCh˅)bIa=BɾaT(nB\CjQ.Ϝ ~-;Ez!ov֤3$ž0.@9֏80)u)` n2YN$e(av+d!^g7K8b:\HJk,^pi"/CmӸA$,yeI.P$i Ԉ,}r xoPKA 8org/gradle/cli/CommandLineParser$OptionParserState.classR]KA=Y&$Hl⋤6PZZHodN9}*lA"<;w={?pO>2bC&CTҜ1dW n+ Tb4 w#TqGW\K{$][& k#cD~+%t+I"rҎu#"ᐫ3ψ4P qcua!acwSQ?V5 e4Wa1Zb1Od毴U^G `i>jM87 Q'*R3;aKz3/ ԥ@hE\bؚ? Cmɭva,;- $_ R(. 6SuXKj%|3?%t&y0OxO=(y ap#PKA]3org/gradle/cli/CommandLineParser$OptionString.classSNQ=w۲eYJ-_( e)ԘLP l7eq.F'/$&>q݂`?vܙ3gf'9,wSi 9aesT-|wD]TgHƐ[rfG2t=Rω3LZZ= dů =+gַ rmQɷGMg2vB}Ma|yvPqytkAͬڦ:fů׹W%^ |j SgNpU^:}bF#n}x ȏUP^^)xOBc_$[,W H޵u7@ ŒHqY9 Yb^*Ѩt<wj:`IXIu xPu?ۊθ#؀Fۗ2-I!%aI 4N ReD!tFL(m`d.u/uiavq+/\Q~BrXL<}**n N2t#駐R1RVt>L^IlLMA?2݈CNb9y9M4&QhaF>f6.40$ЪpdPKA{֛{=org/gradle/cli/CommandLineParser$OptionStringComparator.classTOPmB`"LEB,!Y2dD.Ғ#+/{M7Dq99;_`:ӡCC^CQG`k Jk J.ˠV}afў?{yk^C8·q0dvCso͊wt,|z>Qu]˯8",­bBV40 Cw CWbnZ S rArh==b .K Y&IZ$ H3)\At#F V1fOgHvLHP_ $N)/x<,0:J|K<ZvInG$p4N03i#M% 46W,>3ig T LR1';IH@ĀK L+Ub&Ir(` HQ7a;PKA$2org/gradle/cli/CommandLineParser$ParserState.classSoPN) ssQP`lTf0H41!vu)3?&>GmdiҞ|~pF;6rɣj Z[hr/}ǯz`v¡GX;=A@'Bڞ8̏c"Ob^JOuE#%㱐C]!ͫN X|{lc쇒^O Xrn~tjYVB9faPJkOSvq;3̡P^}3ʩrhIV_ m|~FO4j7fbZ", Vuo5-ao:{p ۋV,0L_"W`n 7V i'9(^LX&3A)*LAڜehf90k F$p؄5ߛ̶Q hȇ,I[3,=Nq&2|RÊisF wYn PKAin?org/gradle/cli/CommandLineParser$UnknownOptionParserState.classUMSA}&a@ Q%""UZr6$[fl U|Tyx/,{6[!B$t{y;yVøLt*'̤0S `3Q\\G1ݲuw^fXq\#gh鹂e斝R֫:!$ECh-=u|hMl?J $ml!-6y$Ȧ ӌ>Aa;}):Ӫ_ WJJY ΒYRu히o>Z= xc0@#{X/2Uy$&^$"&;YqEB%vG|ȨqD:ҋ)H"evTTsjUp%9@Ҿb;#agˢ0E★8MU^"Y\+PKA%CQ*&org/gradle/cli/CommandLineParser.classYy`յM|l,J!BXDI`X!H83K]j֥kki5jF*־׾U}ŷuy}R w73IGs9s9{ϼp,y-;pJ5Use5&<5E$q6ē'^1L3+&K^X2,OՌ4d[%'M1'c}2N8D jfj&>).rO2D1zE(AOg/TS8JUS+<fL2ː L̕ن1|DUD 1XyaZR- YhbHfdRCDT5LY.ujt!a\TKՖ|Rnj{!kM4)0GjCrez[e]&坍lͦl+}UiʔlIZl1@`P cH1 [Evx$jve"ñ`N`k8ºP Z*mjZBvUs(XmU0l;_DPdw}pݒp@6=q^\]#-B6] HN uP*Dwbo ROQq6’M-/Aɡ澟4'Zݞf;:ܒ4SEi PyBt {׮E : lfd8rgo !4$r#d-(I, :E{PǞz1:{pbvXnؾ]-+jDȼ.(Se 'X.E2D>sʧr 47۱Xќ3%D)=38* 9 wn _P{%bVbmƐK 2EFKH'7kUsz)k7KnxXr0-Mn++-!t2Vu'Ḳrӕ [rnyK/XOOJJ YEW}ɒ~b^Wj_>D-˘ihHԒH0ԢGh׃eK",9%_-:# %8 -|ZTAV6X0suVDyQ ,o}WgI:2Gj 2 q3 $K+o g+t`59Ǘ,(TpB2 nL[@-XS>a^h =q;))VÎܦxVP.ܨ>\$O+87ܦ17ֱ-ՂYka*Lؘń}CfɌ!<}w0{zyi u+fji(Xh>y(vzz:CYe__KeG.끧漟pRs\q(1w^L򬝙Qg=^x`+t(:f2׹na6<"u |~w,lLA1,z>^Rn!\&%DnQs#s$# ^}OH&VK,ÛҊd7ޖxG^ÏM+!H mخb;{?a>2?B~D\:j5ԥzq֐I: 1sk)QϩNG?Or`y0KωnZ~QelLHg_qΑq"%=z]eK,|:"}́ >D|OcnW/ӏ8jJ'63|wIQmӉ {z+{X^T+Wæ ʼn3 r+v7T[~uqve&NW BeN*U5Kq>ԱTNO9Nvqo%'Pn)~h0>vter vOs7Go[渗|r"W=vWgxuIhlmʿ*ƣ6~ -ן4minsM"?`}KiRe vZ/wE>KC5WS觹-=;Yv/-gĪO'SCOݬops7@Q:'}<}8N!=$~ igO飚LUp=2'*d-Ȝ;vo>I=j phN1.]aj~(v=T9s-G%ɓ/h0>PCXj)c糏` fu7~/[9mHZ[oPKA, c&org/gradle/cli/ParsedCommandLine.classW_W~f/̲LP.&&,K$)ĄiC$脝,.;dwCkM[ZwMתxJM~[Psfve̼>}rvy8?Eт"j$qUbagxE؉Wj9\S|FE_Q4+*E"x=o7Ʒk\ ~ףk5^:?*~`5oV6lqΙ4w. z_63}I3BPpQgѳ>`pʥ9=1f2fz.oF9=3yksFVIæ;>J1g-)ָj\0G|nԕyݾik 쪣fִKNFN1)X)Cx& sS⛂1kFϜsX,kPBUDOR0藬mAFlniW m gS)_~UyYuHM/ Z+ LW 3r^$/<DQpt^Wb5+5SݬYI͆ExIUX1#Y;3VzX02^7++H~آ{&mӆ}|i(P"z*%陂,E/޿ p5gH>5:ir3 SL i G3AI=?ˎЋx;hx?j8 :鋂` 9'4sXh~a RXkFoWqC{ܙ9'c巕W~23/5<57y?m1;x$cnM. g1a?ⶊ;>NI뙢Wǯ.b]Xֲc)O˪xK*nkx) k]InsT,rDFsY'\*6lPV.GC$\oԗa:EvmYm{l/Mm An|b6MRttY[>hω%O3 Ncʶv 㡠5[5[?L!;&V$^AB@#)Fg*v(j ;'PGP2V񀂁pkՋ I*6T%n#ZZESSK,yK9(Th|q?͔Z}C~؍I쁍x3$QG1*\@ÇGd8FQq_ @J&S5QJ)#Nà1*&{TqF\%Cdi#z` +59bB-ޔ0ǴC8:g*]Cxh@H,spXHn{^C4ٰ{{ {5\ -#nz9 k$qfUyA0=ìȓ${;FHEqR59(i O=bJG0H\3LF*GT4!x *>J.#\YFlJObpc8*IϿz\|؈?(aOv)'n/ib5N$^_G8tci /;WZOdYG'Fāc^w{t;iq P"7ޡ.#/7(DwݭO1#{]M Jd)Aݞnj9 DGk(w&xeO2 EmLI7]x' sȺ1=fY )ԭM gW\)I湫 +.bM2&DY&J X+p-x$w^9)HkQ' 9ݳp)h%EWzWpV5=1u˘Slq?Ow^Usˋ{{8([rt,(- PKAytE,org/gradle/cli/ParsedCommandLineOption.classS]O@=ݯGePaeQ"hHV1YawRlIgN[`Y$ә{=?“4(H#b 冊$4-L%q[器+r⾊ [lpWAɶ(Ha1p#-g,C/{9*I Kxs UEJ%a:w^u"]a*s!"k~E쳟}V_*bTG;-%bXo"p(=@&_2w%^*$APPS =1D:!䉩If%ޱn05W(vbRD $"xj2Dϧxr7k> !V.p].GӱD\8"y RPKA\vB| :org/gradle/cli/ProjectPropertiesCommandLineConverter.classKO@D|?Pâu#Q+$C;1m  JW&.(1D,9vo/[@yl汕G)v }FHWkwLS!]nY7ZK:̿cJDZRysV;H+-)nkS#cruLXgh|BjFYDΏ%L%񎅎*_?ֈ:("<ڄbJՍ ؊tf^*K ߵ XUVi01k p8wZ8T0g?PaΛm=C Ss | 1\Zq-}C_JEˉjE+ w'PKA 8=|9org/gradle/cli/SystemPropertiesCommandLineConverter.classJ@ثmjE5BPąR/P~ӑ$&BJW 'iAY3͜l "lYlE <& d@HgL{:rRs:C*X4NĬQ ۴;hZ3a ѽG!]Gv7S"5eb o}ɸGtFMz9y~X{()spL`7e.KV, TXxɢfDTEGPWJmh~49AjxѰ sh gԙn85].FԒs9Q΢*s/@Ug J*ce+s+1 $p6/t-,;h-.Z >kZPKAWgradle-cli-classpath.properties+(JM.)**+MPKAbuild-receipt.properties+K-*+MJ-5343PKkPKA META-INF/PKAGBW)META-INF/MANIFEST.MFPKAorg/PKA org/gradle/PKAorg/gradle/wrapper/PKA"8M+6/ -org/gradle/wrapper/BootstrapMainStarter.classPKAVţ#org/gradle/wrapper/Download$1.classPKArd@4org/gradle/wrapper/Download$ProxyAuthenticator.classPKA l! org/gradle/wrapper/Download.classPKAyL1org/gradle/wrapper/DownloadProgressListener.classPKA)zü 3org/gradle/wrapper/ExclusiveFileAccessManager.classPKAz- org/gradle/wrapper/GradleUserHomeLookup.classPKA& *#org/gradle/wrapper/GradleWrapperMain.classPKA".org/gradle/wrapper/IDownload.classPKA;'<7"/org/gradle/wrapper/Install$1.classPKAel.d+ 7org/gradle/wrapper/Install.classPKA:o4Korg/gradle/wrapper/Logger.classPKAi?V8Norg/gradle/wrapper/PathAssembler$LocalDistribution.classPKARa&Porg/gradle/wrapper/PathAssembler.classPKA' 0Xorg/gradle/wrapper/SystemPropertiesHandler.classPKAK-.]org/gradle/wrapper/WrapperConfiguration.classPKAXE/ :( `org/gradle/wrapper/WrapperExecutor.classPKA_#igradle-wrapper-classpath.propertiesPKAiorg/gradle/cli/PKA?<S1 jorg/gradle/cli/AbstractCommandLineConverter.classPKAqj ;lorg/gradle/cli/AbstractPropertiesCommandLineConverter.classPKA}yGK1[qorg/gradle/cli/CommandLineArgumentException.classPKAg)rorg/gradle/cli/CommandLineConverter.classPKA &Qtorg/gradle/cli/CommandLineOption.classPKAd(zorg/gradle/cli/CommandLineParser$1.classPKA,}Q ;{org/gradle/cli/CommandLineParser$AfterFirstSubCommand.classPKA_e)37org/gradle/cli/CommandLineParser$AfterOptions.classPKA{& <,org/gradle/cli/CommandLineParser$BeforeFirstSubCommand.classPKA;}RForg/gradle/cli/CommandLineParser$CaseInsensitiveStringComparator.classPKA;/=Corg/gradle/cli/CommandLineParser$KnownOptionParserState.classPKAϤ<org/gradle/cli/CommandLineParser$MissingOptionArgState.classPKA=org/gradle/cli/CommandLineParser$OptionAwareParserState.classPKA3i7org/gradle/cli/CommandLineParser$OptionComparator.classPKA 8ՙorg/gradle/cli/CommandLineParser$OptionParserState.classPKA]3ӛorg/gradle/cli/CommandLineParser$OptionString.classPKA{֛{=Ӟorg/gradle/cli/CommandLineParser$OptionStringComparator.classPKA$2šorg/gradle/cli/CommandLineParser$ParserState.classPKAin?org/gradle/cli/CommandLineParser$UnknownOptionParserState.classPKA%CQ*&Zorg/gradle/cli/CommandLineParser.classPKA, c&Corg/gradle/cli/ParsedCommandLine.classPKAytE,org/gradle/cli/ParsedCommandLineOption.classPKA\vB| :org/gradle/cli/ProjectPropertiesCommandLineConverter.classPKA 8=|9|org/gradle/cli/SystemPropertiesCommandLineConverter.classPKAWOgradle-cli-classpath.propertiesPKAkbuild-receipt.propertiesPK22_cava-0.6.0/gradle/wrapper/gradle-wrapper.properties000066400000000000000000000003101341750772100224000ustar00rootroot00000000000000distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists cava-0.6.0/gradlew000077500000000000000000000122601341750772100140120ustar00rootroot00000000000000#!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" cava-0.6.0/gradlew.bat000066400000000000000000000043241341750772100145560ustar00rootroot00000000000000@if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega cava-0.6.0/io/000077500000000000000000000000001341750772100130455ustar00rootroot00000000000000cava-0.6.0/io/build.gradle000066400000000000000000000007051341750772100153260ustar00rootroot00000000000000description = 'Classes and utilities for handling file and network IO.' dependencies { compile 'com.google.guava:guava' compileOnly project(':bytes') testCompile project(':bytes') testCompile project(':junit') testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testRuntime files('src/test/resources/resourceresolver-test.jar') testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/io/src/000077500000000000000000000000001341750772100136345ustar00rootroot00000000000000cava-0.6.0/io/src/main/000077500000000000000000000000001341750772100145605ustar00rootroot00000000000000cava-0.6.0/io/src/main/java/000077500000000000000000000000001341750772100155015ustar00rootroot00000000000000cava-0.6.0/io/src/main/java/net/000077500000000000000000000000001341750772100162675ustar00rootroot00000000000000cava-0.6.0/io/src/main/java/net/consensys/000077500000000000000000000000001341750772100203135ustar00rootroot00000000000000cava-0.6.0/io/src/main/java/net/consensys/cava/000077500000000000000000000000001341750772100212255ustar00rootroot00000000000000cava-0.6.0/io/src/main/java/net/consensys/cava/io/000077500000000000000000000000001341750772100216345ustar00rootroot00000000000000cava-0.6.0/io/src/main/java/net/consensys/cava/io/Base64.java000066400000000000000000000036561341750772100235350ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.io; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; import net.consensys.cava.bytes.Bytes; /** * Utility methods for encoding and decoding base64 strings. */ public final class Base64 { private Base64() {} /** * Encode a byte array to a base64 encoded string. * * @param bytes The bytes to encode. * @return A base64 encoded string. */ public static String encodeBytes(byte[] bytes) { requireNonNull(bytes); return new String(java.util.Base64.getEncoder().encode(bytes), UTF_8); } /** * Encode bytes to a base64 encoded string. * * @param bytes The bytes to encode. * @return A base64 encoded string. */ public static String encode(Bytes bytes) { requireNonNull(bytes); return encodeBytes(bytes.toArrayUnsafe()); } /** * Decode a base64 encoded string to a byte array. * * @param b64 The base64 encoded string. * @return A byte array. */ public static byte[] decodeBytes(String b64) { requireNonNull(b64); return java.util.Base64.getDecoder().decode(b64.getBytes(UTF_8)); } /** * Decode a base64 encoded string to bytes. * * @param b64 The base64 encoded string. * @return The decoded bytes. */ public static Bytes decode(String b64) { return Bytes.wrap(decodeBytes(b64)); } } cava-0.6.0/io/src/main/java/net/consensys/cava/io/IOConsumer.java000066400000000000000000000017321341750772100245250ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.io; import java.io.IOException; /** * Represents an operation that accepts a single input argument and returns no result. */ @FunctionalInterface public interface IOConsumer { /** * Performs this operation on the given argument. * * @param t the input argument * @throws IOException If an IO error occurs. */ void accept(T t) throws IOException; } cava-0.6.0/io/src/main/java/net/consensys/cava/io/NullOutputStream.java000066400000000000000000000014771341750772100260170ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.io; import java.io.OutputStream; final class NullOutputStream extends OutputStream { static final NullOutputStream INSTANCE = new NullOutputStream(); @Override public void write(int b) { // do nothing } } cava-0.6.0/io/src/main/java/net/consensys/cava/io/Resources.java000066400000000000000000000237711341750772100244630ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.io; import static net.consensys.cava.io.Streams.enumerationStream; import java.io.IOException; import java.io.UncheckedIOException; import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLConnection; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.Paths; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.regex.PatternSyntaxException; import java.util.stream.Stream; import javax.annotation.Nullable; import com.google.common.annotations.VisibleForTesting; import com.google.errorprone.annotations.MustBeClosed; /** * Methods for resolving resources. * * Supports recursive discovery and glob matching on the filesystem and in jar archives. */ public final class Resources { private Resources() {} // Lazily initialized on access private static final class Defaults { static final ClassLoader CLASSLOADER; static { ClassLoader cl; try { cl = Thread.currentThread().getContextClassLoader(); } catch (Throwable e) { // can't load thread context classloader cl = Resources.class.getClassLoader(); if (cl == null) { cl = ClassLoader.getSystemClassLoader(); } } CLASSLOADER = cl; } } /** * Resolve resources using the default class loader. * * @param glob The glob pattern for matching resource paths against. * @return A stream of URLs to all resources. * @throws IOException If an I/O error occurs. */ @MustBeClosed public static Stream find(String glob) throws IOException { return find(Defaults.CLASSLOADER, glob); } /** * Resolve resources using the default class loader. * * @param classLoader The class loader to use for resolving resources. * @param glob The glob pattern for matching resource paths against. * @return A stream of URLs to all resources. * @throws IOException If an I/O error occurs. */ @MustBeClosed @SuppressWarnings("MustBeClosedChecker") public static Stream find(@Nullable ClassLoader classLoader, String glob) throws IOException { if (glob.isEmpty()) { return Stream.empty(); } String[] globParts = globRoot(glob); String root = globParts[0]; while (!root.isEmpty() && root.charAt(0) == '/') { root = root.substring(1); } Enumeration resources = (classLoader != null) ? classLoader.getResources(root) : ClassLoader.getSystemResources(root); Stream stream = enumerationStream(resources); if ("".equals(root)) { // stream will only contain file system references - will need to add all jar roots as well. stream = Stream.concat(stream, classLoaderJarRoots(classLoader)); } if (globParts.length == 1) { return stream; } String rest = globParts[1]; try { return stream.flatMap(url -> { try { // the mapped stream is closed after its contents have been placed into this stream return find(url, rest); } catch (IOException e) { throw new UncheckedIOException(e); } }); } catch (UncheckedIOException e) { throw e.getCause(); } } private static Stream classLoaderJarRoots(@Nullable ClassLoader classLoader) { return classLoaderJarRoots(classLoader, new HashMap<>()).stream().map(uri -> { try { return uri.toURL(); } catch (MalformedURLException e) { // Should not happen throw new RuntimeException(e); } }); } private static Collection classLoaderJarRoots(@Nullable ClassLoader classLoader, Map results) { if (classLoader instanceof URLClassLoader) { URL[] urls = ((URLClassLoader) classLoader).getURLs(); for (URL url : urls) { String urlPath = url.getPath(); if (!Files.isRegularFile(Paths.get(urlPath)) || !(urlPath.endsWith(".jar") || urlPath.endsWith(".war") || urlPath.endsWith(".zip"))) { continue; } URI jarUri; try { jarUri = new URI("jar:" + url.toString() + "!/"); } catch (URISyntaxException e) { // Should not happen throw new RuntimeException(e); } results.put(url.getPath(), jarUri); } } if (classLoader == ClassLoader.getSystemClassLoader()) { // "java.class.path" manifest evaluation... classPathManifestEntries(results); } if (classLoader == null) { return results.values(); } return classLoaderJarRoots(classLoader.getParent(), results); } private static void classPathManifestEntries(Map results) { String classPath = System.getProperty("java.class.path"); if (classPath == null) { return; } StringTokenizer st = new StringTokenizer(classPath, System.getProperty("path.separator")); while (st.hasMoreTokens()) { String entry = st.nextToken().trim(); if (entry.isEmpty()) { continue; } Path path = Paths.get(entry); if (!Files.isRegularFile(path)) { continue; } URI jarUri; try { jarUri = new URI("jar:" + path.toUri().toString() + "!/"); } catch (URISyntaxException e) { // Should not happen throw new RuntimeException(e); } results.put(path.toString(), jarUri); } } @MustBeClosed private static Stream find(URL baseUrl, String glob) throws IOException { if (!isJarURL(baseUrl)) { return findFileResources(baseUrl, glob); } else { return findJarResources(baseUrl, glob); } } private static boolean isJarURL(URL url) { String protocol = url.getProtocol(); return ("jar".equals(protocol) || "war".equals(protocol) || "zip".equals(protocol)); } @MustBeClosed private static Stream findFileResources(URL rootDirUrl, String glob) throws IOException { Path rootDir = Paths.get(rootDirUrl.getPath()); if (!Files.isDirectory(rootDir) || !Files.isReadable(rootDir)) { return Stream.empty(); } PathMatcher pathMatcher = rootDir.getFileSystem().getPathMatcher("glob:" + glob); return Files .walk(rootDir, FileVisitOption.FOLLOW_LINKS) .filter(path -> pathMatcher.matches(rootDir.relativize(path))) .map(path -> { try { return path.toUri().toURL(); } catch (MalformedURLException e) { // should not happen throw new RuntimeException(e); } }); } private static Stream findJarResources(URL baseUrl, String glob) throws IOException { URLConnection connection = baseUrl.openConnection(); if (!(connection instanceof JarURLConnection)) { // Not a jar file return Stream.empty(); } JarURLConnection jarConnection = (JarURLConnection) connection; jarConnection.setUseCaches(false); JarFile jarFile = jarConnection.getJarFile(); JarEntry jarEntry = jarConnection.getJarEntry(); String rootEntryPath = (jarEntry == null) ? "" : jarEntry.getName(); int rootEntryLength = rootEntryPath.length(); // Use a PathMatcher for the default filesystem, which should be ok for the path segments from the resource URIs FileSystem fileSystem = FileSystems.getDefault(); PathMatcher pathMatcher = fileSystem.getPathMatcher("glob:" + glob); return enumerationStream(jarFile.entries()).flatMap(entry -> { String entryPath = entry.getName(); if (!entryPath.startsWith(rootEntryPath) || entryPath.length() == rootEntryLength) { return Stream.empty(); } String relativePath = entryPath.substring(rootEntryLength); if (!pathMatcher.matches(fileSystem.getPath(relativePath))) { return Stream.empty(); } URL entryURL; try { entryURL = new URL(baseUrl, relativePath); } catch (MalformedURLException e) { // should not happen throw new RuntimeException(e); } return Stream.of(entryURL); }); } @VisibleForTesting static String[] globRoot(String glob) { int length = glob.length(); int j = 0; for (int i = 0; i < length; ++i) { char c = glob.charAt(i); switch (c) { case '/': j = i; break; case '\\': i++; if (i >= length) { throw new PatternSyntaxException("Invalid escape character at end of glob pattern", glob, length - 1); } break; case '*': case '?': case '[': case '{': if (i == 0) { return new String[] {"", glob}; } else { return new String[] {unescape(glob.substring(0, j)), glob.substring(j + 1)}; } default: // move on to next char } } return new String[] {unescape(glob)}; } private static String unescape(String glob) { int length = glob.length(); StringBuilder builder = new StringBuilder(length); for (int i = 0; i < length; ++i) { char c = glob.charAt(i); if (c == '\\') { ++i; assert (i < length); } builder.append(glob.charAt(i)); } return builder.toString(); } } cava-0.6.0/io/src/main/java/net/consensys/cava/io/Streams.java000066400000000000000000000044161341750772100241220ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.io; import static java.util.Objects.requireNonNull; import java.io.OutputStream; import java.io.PrintStream; import java.util.Enumeration; import java.util.Spliterator; import java.util.Spliterators; import java.util.function.Consumer; import java.util.stream.Stream; import java.util.stream.StreamSupport; /** * Utilities for working with streams. */ public final class Streams { private Streams() {} private static final PrintStream NULL_PRINT_STREAM = new PrintStream(NullOutputStream.INSTANCE); /** * @return An {@link OutputStream} that discards all input. */ public static OutputStream nullOutputStream() { return NullOutputStream.INSTANCE; } /** * @return A {@link PrintStream} that discards all input. */ public static PrintStream nullPrintStream() { return NULL_PRINT_STREAM; } /** * Stream an {@link Enumeration}. * * @param enumeration The enumeration. * @param The type of objects in the enumeration. * @return A stream over the enumeration. */ public static Stream enumerationStream(Enumeration enumeration) { requireNonNull(enumeration); return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) { @Override public boolean tryAdvance(Consumer action) { if (enumeration.hasMoreElements()) { action.accept(enumeration.nextElement()); return true; } return false; } @Override public void forEachRemaining(Consumer action) { while (enumeration.hasMoreElements()) { action.accept(enumeration.nextElement()); } } }, false); } } cava-0.6.0/io/src/main/java/net/consensys/cava/io/file/000077500000000000000000000000001341750772100225535ustar00rootroot00000000000000cava-0.6.0/io/src/main/java/net/consensys/cava/io/file/Files.java000066400000000000000000000151741341750772100244700ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.io.file; import static com.google.common.base.Preconditions.checkNotNull; import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.file.Files.delete; import static java.nio.file.Files.walkFileTree; import static java.nio.file.StandardCopyOption.ATOMIC_MOVE; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static java.util.Objects.requireNonNull; import net.consensys.cava.io.IOConsumer; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Writer; import java.nio.charset.Charset; import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileVisitResult; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileAttribute; /** * Utility methods for working with files. */ public final class Files { private Files() {} /** * Create a file, if it does not already exist. * * @param path The path to the file to create. * @param attrs An optional list of file attributes to set atomically when creating the file. * @return {@code true} if the file was created. * @throws IOException If an I/O error occurs or the parent directory does not exist. */ public static boolean createFileIfMissing(Path path, FileAttribute... attrs) throws IOException { requireNonNull(path); try { java.nio.file.Files.createFile(path, attrs); } catch (FileAlreadyExistsException e) { return false; } return true; } /** * Delete a directory and all files contained within it. * * @param directory The directory to delete. * @throws IOException If an I/O error occurs. */ public static void deleteRecursively(Path directory) throws IOException { checkNotNull(directory); walkFileTree(directory, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { delete(dir); return FileVisitResult.CONTINUE; } }); } /** * Copies the content of a resource to a file. * * @param resourceName The resource name. * @param destination The destination file. * @param options Options specifying how the destination file should be opened. * @return The destination file. * @throws IOException If an I/O error occurs. */ public static Path copyResource(String resourceName, Path destination, OpenOption... options) throws IOException { requireNonNull(resourceName); requireNonNull(destination); try (OutputStream out = java.nio.file.Files.newOutputStream(destination, options)) { copyResource(resourceName, out); } return destination; } /** * Copies the content of a resource to an output stream. * * @param resourceName The resource name. * @param out The output stream. * @return The total bytes written. * @throws IOException If an I/O error occurs. */ public static long copyResource(String resourceName, OutputStream out) throws IOException { try (InputStream in = Files.class.getClassLoader().getResourceAsStream(resourceName)) { long total = 0L; byte[] buf = new byte[4096]; int n; while ((n = in.read(buf)) > 0) { out.write(buf, 0, n); total += n; } return total; } } /** * Write a temporary file and then replace target. * * @param path The target file to be replaced (if it exists). * @param bytes The bytes to be written. * @throws IOException If an I/O error occurs. */ public static void atomicReplace(Path path, byte[] bytes) throws IOException { requireNonNull(bytes); Path directory = path.getParent(); java.nio.file.Files.createDirectories(directory); Path tempFile = java.nio.file.Files.createTempFile(directory, "." + path.getName(0), ".tmp"); try { java.nio.file.Files.write(tempFile, bytes); java.nio.file.Files.move(tempFile, path, REPLACE_EXISTING, ATOMIC_MOVE); } catch (Throwable e) { try { java.nio.file.Files.delete(tempFile); } catch (IOException e2) { e.addSuppressed(e2); } throw e; } } /** * Write a temporary file and then replace target. * * @param path The target file to be replaced (if it exists). * @param fn A consumer that will be provided a buffered {@link Writer} instance that will write to the file. * @throws IOException If an I/O error occurs. */ public static void atomicReplace(Path path, IOConsumer fn) throws IOException { atomicReplace(path, UTF_8, fn); } /** * Write a temporary file and then replace target. * * @param path The target file to be replaced (if it exists). * @param charset The charset of the file. * @param fn A consumer that will be provided a buffered {@link Writer} instance that will write to the file. * @throws IOException If an I/O error occurs. */ public static void atomicReplace(Path path, Charset charset, IOConsumer fn) throws IOException { requireNonNull(charset); requireNonNull(fn); Path directory = path.getParent(); java.nio.file.Files.createDirectories(directory); Path tempFile = java.nio.file.Files.createTempFile(directory, "." + path.getName(0), ".tmp"); Writer writer = null; try { writer = java.nio.file.Files.newBufferedWriter(tempFile, charset); fn.accept(writer); writer.flush(); writer.close(); java.nio.file.Files.move(tempFile, path, REPLACE_EXISTING, ATOMIC_MOVE); } catch (Throwable e) { if (writer != null) { try { writer.close(); } catch (IOException e2) { e.addSuppressed(e2); } } try { java.nio.file.Files.delete(tempFile); } catch (IOException e2) { e.addSuppressed(e2); } throw e; } } } cava-0.6.0/io/src/main/java/net/consensys/cava/io/file/package-info.java000066400000000000000000000005331341750772100257430ustar00rootroot00000000000000/** * Classes and utilities for handling file 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). */ @ParametersAreNonnullByDefault package net.consensys.cava.io.file; import javax.annotation.ParametersAreNonnullByDefault; cava-0.6.0/io/src/main/java/net/consensys/cava/io/package-info.java000066400000000000000000000005421341750772100250240ustar00rootroot00000000000000/** * 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). */ @ParametersAreNonnullByDefault package net.consensys.cava.io; import javax.annotation.ParametersAreNonnullByDefault; cava-0.6.0/io/src/test/000077500000000000000000000000001341750772100146135ustar00rootroot00000000000000cava-0.6.0/io/src/test/java/000077500000000000000000000000001341750772100155345ustar00rootroot00000000000000cava-0.6.0/io/src/test/java/net/000077500000000000000000000000001341750772100163225ustar00rootroot00000000000000cava-0.6.0/io/src/test/java/net/consensys/000077500000000000000000000000001341750772100203465ustar00rootroot00000000000000cava-0.6.0/io/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100212605ustar00rootroot00000000000000cava-0.6.0/io/src/test/java/net/consensys/cava/io/000077500000000000000000000000001341750772100216675ustar00rootroot00000000000000cava-0.6.0/io/src/test/java/net/consensys/cava/io/Base64Test.java000066400000000000000000000026771341750772100244320ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.io; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import net.consensys.cava.bytes.Bytes; import org.junit.jupiter.api.Test; class Base64Test { @Test void shouldEncodeByteArray() { String s = Base64.encodeBytes(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}); assertEquals("AQIDBAUGBwg=", s); } @Test void shouldEncodeBytesValue() { String s = Base64.encode(Bytes.of(1, 2, 3, 4, 5, 6, 7, 8)); assertEquals("AQIDBAUGBwg=", s); } @Test void shouldDecodeToByteArray() { byte[] bytes = Base64.decodeBytes("AQIDBAUGBwg="); assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}, bytes); } @Test void shouldDecodeToBytesValue() { Bytes bytes = Base64.decode("AQIDBAUGBwg="); assertEquals(Bytes.of(1, 2, 3, 4, 5, 6, 7, 8), bytes); } } cava-0.6.0/io/src/test/java/net/consensys/cava/io/ResourcesTest.java000066400000000000000000000063301341750772100253460ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.io; import static org.junit.jupiter.api.Assertions.assertEquals; import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import javax.annotation.Nonnull; import org.junit.jupiter.api.Test; class ResourcesTest { @Test void shouldSplitGlob() { assertEquals(Arrays.asList("foo", "*.bar"), Arrays.asList(Resources.globRoot("foo/*.bar"))); assertEquals(Arrays.asList("foo", "bar.?"), Arrays.asList(Resources.globRoot("foo/bar.?"))); assertEquals(Arrays.asList("foo/baz", "*.bar"), Arrays.asList(Resources.globRoot("foo/baz/*.bar"))); assertEquals(Arrays.asList("foo/baz", "bar.?"), Arrays.asList(Resources.globRoot("foo/baz/bar.?"))); assertEquals(Collections.singletonList("foo/*.bar"), Arrays.asList(Resources.globRoot("foo/\\*.bar"))); assertEquals(Arrays.asList("foo/*.bar", "*.baz"), Arrays.asList(Resources.globRoot("foo/\\*.bar/*.baz"))); assertEquals(Arrays.asList("", "*.bar"), Arrays.asList(Resources.globRoot("*.bar"))); assertEquals(Arrays.asList("", "**/*.bar"), Arrays.asList(Resources.globRoot("**/*.bar"))); } @Test @SuppressWarnings("MustBeClosedChecker") void shouldIterateResourcesOnFileSystemAndInJars() throws Exception { List all = Resources.find("net/consensys/cava/io/file/resourceresolver/**").collect(Collectors.toList()); assertEquals(12, all.size(), () -> describeExpectation(12, all)); List txtFiles = Resources.find("net/**/test*.txt").collect(Collectors.toList()); assertEquals(6, txtFiles.size(), () -> describeExpectation(6, txtFiles)); List txtFilesFromRoot = Resources.find("/**/test?.txt").collect(Collectors.toList()); assertEquals(5, txtFilesFromRoot.size(), () -> describeExpectation(5, txtFilesFromRoot)); List txtFilesFromRoot2 = Resources.find("//**/test*.txt").collect(Collectors.toList()); assertEquals(6, txtFilesFromRoot2.size(), () -> describeExpectation(6, txtFilesFromRoot2)); List txtFilesFromRoot3 = Resources.find("///**/test*.txt").collect(Collectors.toList()); assertEquals(6, txtFilesFromRoot3.size(), () -> describeExpectation(6, txtFilesFromRoot3)); List txtFilesInDir = Resources.find("**/anotherdir/*.txt").collect(Collectors.toList()); assertEquals(1, txtFilesInDir.size(), () -> describeExpectation(1, txtFilesInDir)); } @Nonnull private String describeExpectation(int count, List urls) { return "Should have contained " + count + " items, but got " + urls.size() + ": \n " + urls.stream().map(URL::toString).collect(Collectors.joining("\n ")); } } cava-0.6.0/io/src/test/java/net/consensys/cava/io/StreamsTest.java000066400000000000000000000023561341750772100250160ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.io; import static net.consensys.cava.io.Streams.enumerationStream; import static org.junit.jupiter.api.Assertions.*; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; class StreamsTest { @Test void shouldStreamAnEnumeration() { Enumeration enumeration = Collections.enumeration(Arrays.asList("RED", "BLUE", "GREEN")); List result = enumerationStream(enumeration).map(String::toLowerCase).collect(Collectors.toList()); assertEquals(Arrays.asList("red", "blue", "green"), result); } } cava-0.6.0/io/src/test/java/net/consensys/cava/io/file/000077500000000000000000000000001341750772100226065ustar00rootroot00000000000000cava-0.6.0/io/src/test/java/net/consensys/cava/io/file/FilesTest.java000066400000000000000000000043451341750772100253610ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.io.file; import static net.consensys.cava.io.file.Files.copyResource; import static net.consensys.cava.io.file.Files.deleteRecursively; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.junit.TempDirectory; import net.consensys.cava.junit.TempDirectoryExtension; import java.nio.file.Files; import java.nio.file.Path; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) class FilesTest { @Test void deleteRecursivelyShouldDeleteEverything() throws Exception { Path directory = Files.createTempDirectory(this.getClass().getSimpleName()); Path testData = directory.resolve("test_data"); Files.createFile(testData); Path testDir = directory.resolve("test_dir"); Files.createDirectory(testDir); Path testData2 = testDir.resolve("test_data"); Files.createFile(testData2); assertTrue(Files.exists(directory)); assertTrue(Files.exists(testData)); assertTrue(Files.exists(testDir)); assertTrue(Files.exists(testData2)); deleteRecursively(directory); assertFalse(Files.exists(directory)); assertFalse(Files.exists(testData)); assertFalse(Files.exists(testDir)); assertFalse(Files.exists(testData2)); } @Test void canCopyResources(@TempDirectory Path tempDir) throws Exception { Path file = copyResource("net/consensys/cava/io/file/test.txt", tempDir.resolve("test.txt")); assertTrue(Files.exists(file)); assertEquals(81, Files.size(file)); } } cava-0.6.0/io/src/test/resources/000077500000000000000000000000001341750772100166255ustar00rootroot00000000000000cava-0.6.0/io/src/test/resources/net/000077500000000000000000000000001341750772100174135ustar00rootroot00000000000000cava-0.6.0/io/src/test/resources/net/consensys/000077500000000000000000000000001341750772100214375ustar00rootroot00000000000000cava-0.6.0/io/src/test/resources/net/consensys/cava/000077500000000000000000000000001341750772100223515ustar00rootroot00000000000000cava-0.6.0/io/src/test/resources/net/consensys/cava/io/000077500000000000000000000000001341750772100227605ustar00rootroot00000000000000cava-0.6.0/io/src/test/resources/net/consensys/cava/io/file/000077500000000000000000000000001341750772100236775ustar00rootroot00000000000000cava-0.6.0/io/src/test/resources/net/consensys/cava/io/file/resourceresolver/000077500000000000000000000000001341750772100273105ustar00rootroot00000000000000cava-0.6.0/io/src/test/resources/net/consensys/cava/io/file/resourceresolver/subdir/000077500000000000000000000000001341750772100306005ustar00rootroot00000000000000cava-0.6.0/io/src/test/resources/net/consensys/cava/io/file/resourceresolver/subdir/test3.yaml000066400000000000000000000000001341750772100325140ustar00rootroot00000000000000cava-0.6.0/io/src/test/resources/net/consensys/cava/io/file/resourceresolver/test1.txt000066400000000000000000000000001341750772100310770ustar00rootroot00000000000000cava-0.6.0/io/src/test/resources/net/consensys/cava/io/file/resourceresolver/test2.txt000066400000000000000000000000001341750772100311000ustar00rootroot00000000000000cava-0.6.0/io/src/test/resources/net/consensys/cava/io/file/test.txt000066400000000000000000000001211341750772100254110ustar00rootroot00000000000000ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 01234567890123345678901234 cava-0.6.0/io/src/test/resources/resourceresolver-test.jar000066400000000000000000000046161341750772100237200ustar00rootroot00000000000000PK{`BM META-INF/PKPK{`BMMETA-INF/MANIFEST.MFe1 0= V:A< 4)/ς޶ww^hbjLp^˼d(B$c0VVg^V6'~sy$xg>nbfT>I+PKngxPK AMnet/PK AMnet/consensys/PK AMnet/consensys/cava/PK AMnet/consensys/cava/io/PK AMnet/consensys/cava/io/file/PK b`BM,net/consensys/cava/io/file/resourceresolver/PK AM5net/consensys/cava/io/file/resourceresolver/test4.txtPK AM5net/consensys/cava/io/file/resourceresolver/test3.txtPK e`BM3net/consensys/cava/io/file/resourceresolver/subdir/PK e`BM=net/consensys/cava/io/file/resourceresolver/subdir/test3.tomlPK n`BM7net/consensys/cava/io/file/resourceresolver/anotherdir/PK k`BM@net/consensys/cava/io/file/resourceresolver/anotherdir/test5.txtPK n`BMAnet/consensys/cava/io/file/resourceresolver/anotherdir/test6.yamlPK{`BM META-INF/PK{`BMngx=META-INF/MANIFEST.MFPK AMnet/PK AMnet/consensys/PK AMEnet/consensys/cava/PK AMvnet/consensys/cava/io/PK AMnet/consensys/cava/io/file/PK b`BM,net/consensys/cava/io/file/resourceresolver/PK AM5-net/consensys/cava/io/file/resourceresolver/test4.txtPK AM5net/consensys/cava/io/file/resourceresolver/test3.txtPK e`BM3net/consensys/cava/io/file/resourceresolver/subdir/PK e`BM=$net/consensys/cava/io/file/resourceresolver/subdir/test3.tomlPK n`BM7net/consensys/cava/io/file/resourceresolver/anotherdir/PK k`BM@net/consensys/cava/io/file/resourceresolver/anotherdir/test5.txtPK n`BMA2net/consensys/cava/io/file/resourceresolver/anotherdir/test6.yamlPKcava-0.6.0/junit/000077500000000000000000000000001341750772100135675ustar00rootroot00000000000000cava-0.6.0/junit/build.gradle000066400000000000000000000010251341750772100160440ustar00rootroot00000000000000description = 'Utilities for better junit testing.' dependencies { compile project(':io') compileOnly 'com.github.kstyrc:embedded-redis' compileOnly 'io.vertx:vertx-core' compileOnly 'org.bouncycastle:bcprov-jdk15on' compileOnly 'org.junit.jupiter:junit-jupiter-api' testCompile 'com.github.kstyrc:embedded-redis' testCompile 'io.lettuce:lettuce-core' testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/junit/src/000077500000000000000000000000001341750772100143565ustar00rootroot00000000000000cava-0.6.0/junit/src/main/000077500000000000000000000000001341750772100153025ustar00rootroot00000000000000cava-0.6.0/junit/src/main/java/000077500000000000000000000000001341750772100162235ustar00rootroot00000000000000cava-0.6.0/junit/src/main/java/net/000077500000000000000000000000001341750772100170115ustar00rootroot00000000000000cava-0.6.0/junit/src/main/java/net/consensys/000077500000000000000000000000001341750772100210355ustar00rootroot00000000000000cava-0.6.0/junit/src/main/java/net/consensys/cava/000077500000000000000000000000001341750772100217475ustar00rootroot00000000000000cava-0.6.0/junit/src/main/java/net/consensys/cava/junit/000077500000000000000000000000001341750772100231005ustar00rootroot00000000000000cava-0.6.0/junit/src/main/java/net/consensys/cava/junit/BouncyCastleExtension.java000066400000000000000000000021341341750772100302330ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.junit; import java.security.Security; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; /** * A junit5 extension, that installs a BouncyCastle security provider. * */ public class BouncyCastleExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) throws Exception { Security.addProvider(new BouncyCastleProvider()); } } cava-0.6.0/junit/src/main/java/net/consensys/cava/junit/RedisPort.java000066400000000000000000000017221341750772100256600ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.junit; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * A parameter annotation for injecting the running Redis server port into junit5 tests. */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface RedisPort { } cava-0.6.0/junit/src/main/java/net/consensys/cava/junit/RedisServerExtension.java000066400000000000000000000073431341750772100301040ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.junit; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.security.SecureRandom; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import redis.embedded.RedisServer; /** * A junit5 extension, that sets up an ephemeral Redis server for tests. * * The ephemeral Redis server is created with a random free port for the test suite and injected into any tests with * parameters of type {@link Integer} annotated with {@link RedisPort} * * NOTE: Redis does not support picking a random port on its own. This extension tries its best to test free ports and * avoid collisions. */ public final class RedisServerExtension implements ParameterResolver, AfterAllCallback { private static Set rangesIssued = ConcurrentHashMap.newKeySet(); private static SecureRandom random = new SecureRandom(); private static int findFreeRange() { int range = random.nextInt(326); if (!rangesIssued.add(range)) { return findFreeRange(); } return range; } private static int findFreePort() { int range = findFreeRange() * 100 + 32768; int port = range; while (port < range + 100) { try { ServerSocket socket = new ServerSocket(port, 0, InetAddress.getLocalHost()); socket.setReuseAddress(false); socket.close(); return port; } catch (IOException e) { port++; } } throw new IllegalStateException("Could not reserve a port in range " + range + " and " + range + 100); } private RedisServer redisServer; private Thread shutdownThread = new Thread(() -> { if (redisServer != null) { redisServer.stop(); } }); @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return Integer.class.equals(parameterContext.getParameter().getType()) && parameterContext.isAnnotated(RedisPort.class); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { if (redisServer == null) { String localhost = InetAddress.getLoopbackAddress().getHostAddress(); redisServer = RedisServer .builder() .setting("bind " + localhost) .setting("maxmemory 128mb") .setting("maxmemory-policy allkeys-lru") .setting("appendonly no") .setting("save \"\"") .port(findFreePort()) .build(); Runtime.getRuntime().addShutdownHook(shutdownThread); redisServer.start(); } return redisServer.ports().get(0); } @Override public void afterAll(ExtensionContext context) { if (redisServer != null) { redisServer.stop(); Runtime.getRuntime().removeShutdownHook(shutdownThread); } } } cava-0.6.0/junit/src/main/java/net/consensys/cava/junit/TempDirectory.java000066400000000000000000000017151341750772100265410ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.junit; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * A parameter annotation for injecting a temporary directory into junit5 tests. */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface TempDirectory { } cava-0.6.0/junit/src/main/java/net/consensys/cava/junit/TempDirectoryExtension.java000066400000000000000000000044171341750772100304400ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.junit; import static java.nio.file.Files.createTempDirectory; import static net.consensys.cava.io.file.Files.deleteRecursively; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Path; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; /** * A junit5 extension, that provides a temporary directory for tests. * * The temporary directory is created for the test suite and injected into any tests with parameters annotated by * {@link TempDirectory}. */ public final class TempDirectoryExtension implements ParameterResolver, AfterAllCallback { private Path tempDirectory; @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return parameterContext.getParameter().isAnnotationPresent(TempDirectory.class); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { if (tempDirectory == null) { try { tempDirectory = createTempDirectory(extensionContext.getRequiredTestClass().getSimpleName()); } catch (IOException e) { throw new UncheckedIOException(e); } } return tempDirectory; } @Override public void afterAll(ExtensionContext context) throws Exception { if (tempDirectory != null) { deleteRecursively(tempDirectory); } } } cava-0.6.0/junit/src/main/java/net/consensys/cava/junit/VertxExtension.java000066400000000000000000000036311341750772100267530ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.junit; import io.vertx.core.Vertx; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; /** * A junit5 extension, that provides a Vert.X instance for tests. * * The Vert.X instance created for the test suite and injected into any tests with parameters annotated by * {@link VertxInstance}. */ public class VertxExtension implements ParameterResolver, AfterAllCallback { private Vertx vertx = null; @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return parameterContext.getParameter().isAnnotationPresent(VertxInstance.class); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { if (vertx == null) { System.setProperty("vertx.disableFileCPResolving", "true"); vertx = Vertx.vertx(); } return vertx; } @Override public void afterAll(ExtensionContext context) { if (vertx != null) { vertx.close(); } } } cava-0.6.0/junit/src/main/java/net/consensys/cava/junit/VertxInstance.java000066400000000000000000000017231341750772100265430ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.junit; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * A parameter annotation for injecting a temporary Vert.X instance into junit5 tests. */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface VertxInstance { } cava-0.6.0/junit/src/main/java/net/consensys/cava/junit/package-info.java000066400000000000000000000004001341750772100262610ustar00rootroot00000000000000/** * 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.junit; cava-0.6.0/junit/src/test/000077500000000000000000000000001341750772100153355ustar00rootroot00000000000000cava-0.6.0/junit/src/test/java/000077500000000000000000000000001341750772100162565ustar00rootroot00000000000000cava-0.6.0/junit/src/test/java/net/000077500000000000000000000000001341750772100170445ustar00rootroot00000000000000cava-0.6.0/junit/src/test/java/net/consensys/000077500000000000000000000000001341750772100210705ustar00rootroot00000000000000cava-0.6.0/junit/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100220025ustar00rootroot00000000000000cava-0.6.0/junit/src/test/java/net/consensys/cava/junit/000077500000000000000000000000001341750772100231335ustar00rootroot00000000000000cava-0.6.0/junit/src/test/java/net/consensys/cava/junit/RedisServerExtensionTest.java000066400000000000000000000026731341750772100310000ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.junit; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.net.InetAddress; import io.lettuce.core.RedisClient; import io.lettuce.core.RedisURI; import io.lettuce.core.api.StatefulRedisConnection; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(RedisServerExtension.class) class RedisServerExtensionTest { @Test void shouldHaveAccessToARedisServer(@RedisPort Integer port) { assertNotNull(port); assertTrue(port >= 32768, "Port must be more than 32768, was:" + port); RedisClient client = RedisClient.create(RedisURI.create(InetAddress.getLoopbackAddress().getHostAddress(), port)); try (StatefulRedisConnection conn = client.connect()) { assertTrue(conn.isOpen()); } } } cava-0.6.0/junit/src/test/java/net/consensys/cava/junit/TempDirectoryExtensionTest.java000066400000000000000000000021611341750772100313250ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.junit; import static org.junit.jupiter.api.Assertions.assertTrue; import java.nio.file.Files; import java.nio.file.Path; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) class TempDirectoryExtensionTest { @Test void shouldHaveAccessToATemporaryDirectory(@TempDirectory Path tempDir) throws Exception { assertTrue(Files.exists(tempDir)); assertTrue(Files.isDirectory(tempDir)); Files.createFile(tempDir.resolve("file")); } } cava-0.6.0/kademlia/000077500000000000000000000000001341750772100142055ustar00rootroot00000000000000cava-0.6.0/kademlia/build.gradle000066400000000000000000000005371341750772100164710ustar00rootroot00000000000000description = 'Distributed hash table implementations' dependencies { compile "com.google.guava:guava" compile "org.jetbrains.kotlin:kotlin-stdlib" testCompile project(':junit') testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/kademlia/src/000077500000000000000000000000001341750772100147745ustar00rootroot00000000000000cava-0.6.0/kademlia/src/main/000077500000000000000000000000001341750772100157205ustar00rootroot00000000000000cava-0.6.0/kademlia/src/main/kotlin/000077500000000000000000000000001341750772100172205ustar00rootroot00000000000000cava-0.6.0/kademlia/src/main/kotlin/net/000077500000000000000000000000001341750772100200065ustar00rootroot00000000000000cava-0.6.0/kademlia/src/main/kotlin/net/consensys/000077500000000000000000000000001341750772100220325ustar00rootroot00000000000000cava-0.6.0/kademlia/src/main/kotlin/net/consensys/cava/000077500000000000000000000000001341750772100227445ustar00rootroot00000000000000cava-0.6.0/kademlia/src/main/kotlin/net/consensys/cava/kademlia/000077500000000000000000000000001341750772100245135ustar00rootroot00000000000000cava-0.6.0/kademlia/src/main/kotlin/net/consensys/cava/kademlia/KademliaRoutingTable.kt000066400000000000000000000215161341750772100311070ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.kademlia import com.google.common.cache.Cache import com.google.common.cache.CacheBuilder import java.util.function.Function /** * Determine the XOR-distance between this and an equal-length byte array. * * @param other the byte-array to calculate the distance to * @return the distance as an integer * @throws IllegalArgumentException if [other] is not the same length as this */ infix fun ByteArray.xorDist(other: ByteArray): Int { require(size == other.size) { "arrays are of different lengths" } var distance = size * 8 for (i in indices) { val xor = (this[i].toInt() xor other[i].toInt()) and 0xff if (xor == 0) { distance -= 8 } else { distance -= (Integer.numberOfLeadingZeros(xor) - 24) break } } return distance } /** * Compare two equal-length byte arrays for their XOR-distance to this. * * @param a the first byte array * @param b the second byte array * @return -1 if [a] is closer, +1 if [b] is closer, or 0 if they are the same distance to this * @throws IllegalArgumentException if [a] or [b] are not the same length as this */ fun ByteArray.xorDistCmp(a: ByteArray, b: ByteArray): Int { require(size == a.size && size == b.size) { "arrays are of different lengths" } for (i in indices) { val distA = (this[i].toInt() xor a[i].toInt()) and 0xff val distB = (this[i].toInt() xor b[i].toInt()) and 0xff if (distA > distB) { return 1 } else if (distA < distB) { return -1 } } return 0 } /** * Insert an element into a mutable list, based on a comparison function. * * @param element the element to insert * @param comparison the comparison function */ fun MutableList.orderedInsert(element: E, comparison: (E, E) -> Int) { var i = this.binarySearch { e -> comparison(e, element) } if (i < 0) { i = -i - 1 } this.add(i, element) } /** * A Kademlia Routing Table * * @constructor Create a new routing table. * @param selfId the ID of the local node * @param k the size of each bucket (k value) * @param maxReplacements the maximum number of replacements to cache in each bucket * @param nodeId a function for obtaining the id of a network node * @param the network node type * * @author Chris Leishman - https://cleishm.github.io/ */ class KademliaRoutingTable( private val selfId: ByteArray, k: Int, maxReplacements: Int = k, private val nodeId: (T) -> ByteArray ) : Set { companion object { /** * Create a new routing table. * * @param selfId the ID of the local node * @param k the size of each bucket (k value) * @param nodeId a function for obtaining the id of a network node * @param the network node type * @return A new routing table */ @JvmStatic fun create(selfId: ByteArray, k: Int, nodeId: Function): KademliaRoutingTable = KademliaRoutingTable(selfId, k, nodeId = nodeId::apply) /** * Create a new routing table. * * @param selfId the ID of the local node * @param k the size of each bucket (k value) * @param maxReplacements the maximum number of replacements to cache in each bucket * @param nodeId a function for obtaining the id of a network node * @param the network node type * @return A new routing table */ @JvmStatic fun create( selfId: ByteArray, k: Int, maxReplacements: Int, nodeId: Function ): KademliaRoutingTable = KademliaRoutingTable(selfId, k, maxReplacements, nodeId::apply) } init { require(selfId.isNotEmpty()) { "self id must not be empty" } require(k > 0) { "k value must be positive" } } private val idBitSize = selfId.size * 8 private val buckets: Array> = Array(idBitSize + 1) { Bucket(k, maxReplacements) } private val distanceCache: Cache = CacheBuilder.newBuilder().maximumSize((1L + idBitSize) * k).weakKeys().build() override val size: Int get() = buckets.fold(0) { acc, bucket -> acc + bucket.size } override fun isEmpty(): Boolean = buckets.find { bucket -> !bucket.isEmpty() } == null override fun iterator(): Iterator = buckets.asSequence().flatMap { bucket -> bucket.asSequence() }.iterator() override fun contains(element: T): Boolean = bucketFor(element).contains(element) override fun containsAll(elements: Collection): Boolean { val peers = elements.toMutableSet() buckets.forEach { bucket -> peers.removeAll(peers.filter { peer -> bucket.contains(peer) }) if (peers.isEmpty()) { return true } } return false } /** * Return the nearest nodes to a target id, in order from closest to furthest. * * The sort order is the log distance from the target id to the node ids. * * @param targetId the id to find nodes nearest to * @param limit the maximum number of nodes to return * @return a list of nodes from the routing table */ fun nearest(targetId: ByteArray, limit: Int): List { val results = mutableListOf() for (bucket in buckets) { for (node in bucket) { val nodeId = idForNode(node) results.orderedInsert(node) { a, _ -> targetId.xorDistCmp(idForNode(a), nodeId) } if (results.size > limit) { results.removeAt(results.lastIndex) } } } return results } /** * Add a node to the table. * * @param node the node to add * @return `null` if the node was successfully added to the table (or already in the table). Otherwise, a node * will be returned that is a suitable candidate for eviction, and the provided node will be stored in * the replacements list. */ fun add(node: T): T? = bucketFor(node).add(node) /** * Remove a node from the table, potentially adding an alternative from the replacement cache. * * @param node the node to evict * @param `true` if the node was removed */ fun evict(node: T): Boolean = bucketFor(node).evict(node) /** * Clear all nodes (and replacements) from the table. */ fun clear() { buckets.forEach { bucket -> bucket.clear() } } private fun idForNode(node: T): ByteArray { val id = nodeId(node) require(id.size == selfId.size) { "id obtained for node is not the correct length" } require(!id.contentEquals(selfId)) { "id obtained for node is the same as self" } return id } private fun bucketFor(node: T) = buckets[logDistToSelf(node)] private fun logDistToSelf(node: T): Int = distanceCache.get(node) { idForNode(node) xorDist selfId } private class Bucket private constructor( // ordered with most recent first private val entries: MutableList, private val k: Int, private val maxReplacements: Int ) : List by entries { constructor(k: Int, maxReplacements: Int) : this(mutableListOf(), k, maxReplacements) // ordered with most recent last private val replacementCache = mutableListOf() init { require(k > 0) { "k value must be positive" } } @Synchronized fun add(node: E): E? { // remove from the replacement cache, if present replacementCache.remove(node) // check if the list contains this node, and move to the front if so for (i in entries.indices) { if (entries[i] == node) { // already in table, so move to front entries.removeAt(i) entries.add(0, node) return null } } assert(entries.size <= k) if (entries.size == k) { // bucket is full, so add to the replacement cache assert(replacementCache.size <= maxReplacements) if (replacementCache.size == maxReplacements) { replacementCache.removeAt(0) } replacementCache.add(node) return entries.last() } // add entry to the front of the bucket entries.add(0, node) return null } // remove and replace from replacement cache @Synchronized fun evict(node: E): Boolean { if (!entries.remove(node)) { return false } if (!replacementCache.isEmpty()) { val replacement = replacementCache.removeAt(replacementCache.lastIndex) entries.add(0, replacement) } return true } @Synchronized fun clear() { entries.clear() replacementCache.clear() } } } cava-0.6.0/kademlia/src/test/000077500000000000000000000000001341750772100157535ustar00rootroot00000000000000cava-0.6.0/kademlia/src/test/kotlin/000077500000000000000000000000001341750772100172535ustar00rootroot00000000000000cava-0.6.0/kademlia/src/test/kotlin/net/000077500000000000000000000000001341750772100200415ustar00rootroot00000000000000cava-0.6.0/kademlia/src/test/kotlin/net/consensys/000077500000000000000000000000001341750772100220655ustar00rootroot00000000000000cava-0.6.0/kademlia/src/test/kotlin/net/consensys/cava/000077500000000000000000000000001341750772100227775ustar00rootroot00000000000000cava-0.6.0/kademlia/src/test/kotlin/net/consensys/cava/kademlia/000077500000000000000000000000001341750772100245465ustar00rootroot00000000000000cava-0.6.0/kademlia/src/test/kotlin/net/consensys/cava/kademlia/KademliaRoutingTableTest.kt000066400000000000000000000105221341750772100317750ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.kademlia import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test internal class KademliaRoutingTableTest { private val shortId = byteArrayOf(0x00) data class Node(val nodeId: ByteArray) { constructor(id: Byte) : this(byteArrayOf(id)) override fun equals(other: Any?): Boolean { if (this === other) { return true } if (other !is Node) { return false } return nodeId.contentEquals(other.nodeId) } override fun hashCode(): Int { return nodeId.contentHashCode() } } @Test fun shouldBeEmptyOnConstruction() { val table = KademliaRoutingTable(shortId, 16) { n -> n.nodeId } assertTrue(table.isEmpty()) assertEquals(0, table.size) } @Test fun shouldAddToEmptyTable() { val table = KademliaRoutingTable(shortId, 16) { n -> n.nodeId } val node = Node(0x01) assertNull(table.add(node)) assertTrue(table.contains(node)) assertEquals(1, table.size) } @Test fun shouldProposeEvictionIfBucketIsFull() { val table = KademliaRoutingTable(shortId, 2) { n -> n.nodeId } val node1 = Node(0x04) assertNull(table.add(node1)) val node2 = Node(0x05) assertNull(table.add(node2)) assertTrue(table.contains(node1)) assertTrue(table.contains(node2)) assertEquals(2, table.size) val node3 = Node(0x06) assertEquals(node1, table.add(node3)) assertTrue(table.contains(node1)) assertTrue(table.contains(node2)) assertFalse(table.contains(node3)) assertEquals(2, table.size) } @Test fun shouldReplaceEvictedNode() { val table = KademliaRoutingTable(shortId, 2) { n -> n.nodeId } val node1 = Node(0x04) val node2 = Node(0x05) table.add(node1) table.add(node2) val node3 = Node(0x06) assertEquals(node1, table.add(node3)) assertTrue(table.evict(node1)) assertFalse(table.contains(node1)) assertTrue(table.contains(node2)) assertTrue(table.contains(node3)) assertEquals(2, table.size) } @Test fun shouldReturnNearestOrderedNodesUpToLimit() { val table = KademliaRoutingTable(shortId, 16) { n -> n.nodeId } table.add(Node(0x05)) table.add(Node(0x04)) val node3 = Node(0x03) table.add(node3) val node2 = Node(0x02) table.add(node2) val node1 = Node(0x01) table.add(node1) assertEquals(5, table.size) val nearest = table.nearest(shortId, 3) assertEquals(3, nearest.size) assertTrue(nearest.containsAll(listOf(node1, node2, node3))) assertOrderedByLogDist(shortId, nearest) } @Test fun shouldReturnAllNodesWhenTableIsSmallerThanLimit() { val table = KademliaRoutingTable(shortId, 16) { n -> n.nodeId } table.add(Node(0x05)) table.add(Node(0x04)) table.add(Node(0x03)) table.add(Node(0x02)) table.add(Node(0x01)) assertEquals(5, table.size) val nearest = table.nearest(shortId, 10) assertEquals(5, nearest.size) assertOrderedByLogDist(shortId, nearest) } @Test fun shouldClearAllNodes() { val table = KademliaRoutingTable(shortId, 16) { n -> n.nodeId } table.add(Node(0x05)) table.add(Node(0x04)) table.add(Node(0x03)) table.add(Node(0x02)) table.add(Node(0x01)) assertEquals(5, table.size) table.clear() assertTrue(table.isEmpty()) assertEquals(0, table.size) } private fun assertOrderedByLogDist(target: ByteArray, nodes: List) { var dist = 0 for (n in nodes) { val nDist = target xorDist n.nodeId assertTrue(nDist >= dist) { "list is not ordered by distance" } dist = nDist } } } cava-0.6.0/kademlia/src/test/kotlin/net/consensys/cava/kademlia/LogarithmicDistanceTest.kt000066400000000000000000000036741341750772100316750ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.kademlia import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test internal class LogarithmicDistanceTest { @Test fun shouldHaveDistanceZeroToSelf() { val a = ByteArray(4) { 56 } assertEquals(0, a xorDist a) } @Test fun shouldHaveMaximumDistanceToInverse() { val a = byteArrayOf(0x0f, 0x0f, 0x0f, 0x0f) val b = byteArrayOf(0xf0.toByte(), 0xf0.toByte(), 0xf0.toByte(), 0xf0.toByte()) assertEquals(32, a xorDist b) } @Test fun shouldCalculateDistance() { assertEquals(1, byteArrayOf(0x00) xorDist byteArrayOf(0x01)) assertEquals(2, byteArrayOf(0x00) xorDist byteArrayOf(0x02)) assertEquals(2, byteArrayOf(0x00) xorDist byteArrayOf(0x03)) assertEquals(3, byteArrayOf(0x00) xorDist byteArrayOf(0x04)) assertEquals(3, byteArrayOf(0x00) xorDist byteArrayOf(0x05)) assertEquals(3, byteArrayOf(0x00) xorDist byteArrayOf(0x06)) assertEquals(4, byteArrayOf(0x00) xorDist byteArrayOf(0x0f)) assertEquals(8, byteArrayOf(0x00) xorDist byteArrayOf(0xff.toByte())) } @Test fun shouldCompareDistances() { assertEquals(-1, byteArrayOf(0x00).xorDistCmp(byteArrayOf(0x01), byteArrayOf(0x02))) assertEquals(1, byteArrayOf(0x00).xorDistCmp(byteArrayOf(0x02), byteArrayOf(0x01))) assertEquals(0, byteArrayOf(0x00).xorDistCmp(byteArrayOf(0x05), byteArrayOf(0x05))) } } cava-0.6.0/kademlia/src/test/kotlin/net/consensys/cava/kademlia/OrderedInsertTest.kt000066400000000000000000000031771341750772100305270ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.kademlia import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test internal class OrderedInsertTest { @Test fun shouldInsertToEmptyList() { val list = mutableListOf() list.orderedInsert(1) { a, b -> a.compareTo(b) } assertEquals(listOf(1), list) } @Test fun shouldInsertInOrderedPosition() { val list = mutableListOf(1, 2, 5, 7) list.orderedInsert(4) { a, b -> a.compareTo(b) } assertEquals(listOf(1, 2, 4, 5, 7), list) } @Test fun shouldInsertDuplicateInOrderedPosition() { val list = mutableListOf(1, 2, 5, 7) list.orderedInsert(5) { a, b -> a.compareTo(b) } assertEquals(listOf(1, 2, 5, 5, 7), list) } @Test fun shouldInsertAtStart() { val list = mutableListOf(2, 5, 7) list.orderedInsert(1) { a, b -> a.compareTo(b) } assertEquals(listOf(1, 2, 5, 7), list) } @Test fun shouldInsertAtEnd() { val list = mutableListOf(2, 5, 7) list.orderedInsert(8) { a, b -> a.compareTo(b) } assertEquals(listOf(2, 5, 7, 8), list) } } cava-0.6.0/kv/000077500000000000000000000000001341750772100130565ustar00rootroot00000000000000cava-0.6.0/kv/build.gradle000066400000000000000000000021761341750772100153430ustar00rootroot00000000000000description = 'Key value store implementations.' dependencies { compile project(':bytes') compile project(':concurrent-coroutines') compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core' compile 'org.jetbrains.kotlinx:kotlinx-coroutines-guava' compile 'org.jetbrains.kotlinx:kotlinx-coroutines-jdk8' compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' compileOnly 'com.jolbox:bonecp' compileOnly 'io.lettuce:lettuce-core' compileOnly 'org.fusesource.leveldbjni:leveldbjni-all' compileOnly 'org.mapdb:mapdb' testCompile project(':concurrent') testCompile project(':junit') testCompile 'com.jolbox:bonecp' testCompile 'com.github.kstyrc:embedded-redis' testCompile 'com.h2database:h2' testCompile 'com.winterbe:expekt' testCompile 'io.lettuce:lettuce-core' testCompile 'org.fusesource.leveldbjni:leveldbjni-all' testCompile 'org.jetbrains.spek:spek-api' testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testCompile 'org.mapdb:mapdb' testRuntime 'org.jetbrains.spek:spek-junit-platform-engine' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/kv/src/000077500000000000000000000000001341750772100136455ustar00rootroot00000000000000cava-0.6.0/kv/src/main/000077500000000000000000000000001341750772100145715ustar00rootroot00000000000000cava-0.6.0/kv/src/main/java/000077500000000000000000000000001341750772100155125ustar00rootroot00000000000000cava-0.6.0/kv/src/main/java/net/000077500000000000000000000000001341750772100163005ustar00rootroot00000000000000cava-0.6.0/kv/src/main/java/net/consensys/000077500000000000000000000000001341750772100203245ustar00rootroot00000000000000cava-0.6.0/kv/src/main/java/net/consensys/cava/000077500000000000000000000000001341750772100212365ustar00rootroot00000000000000cava-0.6.0/kv/src/main/java/net/consensys/cava/kv/000077500000000000000000000000001341750772100216565ustar00rootroot00000000000000cava-0.6.0/kv/src/main/java/net/consensys/cava/kv/RedisBytesCodec.java000066400000000000000000000027671341750772100255500ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.kv; import net.consensys.cava.bytes.Bytes; import java.nio.ByteBuffer; import javax.annotation.Nullable; import io.lettuce.core.codec.RedisCodec; class RedisBytesCodec implements RedisCodec { @Override public ByteBuffer encodeKey(@Nullable Bytes key) { if (key == null) { return ByteBuffer.allocate(0); } return ByteBuffer.wrap(key.toArrayUnsafe()); } @Override public Bytes decodeKey(@Nullable ByteBuffer bytes) { if (bytes == null) { return null; } return Bytes.wrapByteBuffer(bytes); } @Override public ByteBuffer encodeValue(@Nullable Bytes value) { if (value == null) { return ByteBuffer.allocate(0); } return ByteBuffer.wrap(value.toArrayUnsafe()); } @Override public Bytes decodeValue(@Nullable ByteBuffer bytes) { if (bytes == null) { return null; } return Bytes.wrapByteBuffer(bytes); } } cava-0.6.0/kv/src/main/java/net/consensys/cava/kv/package-info.java000066400000000000000000000005451341750772100250510ustar00rootroot00000000000000/** * Classes and utilities for working with key/value stores. * *

* These classes are included in the complete Cava distribution, or separately when using the gradle dependency * `net.consensys.cava:cava-kv` (`cava-kv.jar`). */ @ParametersAreNonnullByDefault package net.consensys.cava.kv; import javax.annotation.ParametersAreNonnullByDefault; cava-0.6.0/kv/src/main/kotlin/000077500000000000000000000000001341750772100160715ustar00rootroot00000000000000cava-0.6.0/kv/src/main/kotlin/net/000077500000000000000000000000001341750772100166575ustar00rootroot00000000000000cava-0.6.0/kv/src/main/kotlin/net/consensys/000077500000000000000000000000001341750772100207035ustar00rootroot00000000000000cava-0.6.0/kv/src/main/kotlin/net/consensys/cava/000077500000000000000000000000001341750772100216155ustar00rootroot00000000000000cava-0.6.0/kv/src/main/kotlin/net/consensys/cava/kv/000077500000000000000000000000001341750772100222355ustar00rootroot00000000000000cava-0.6.0/kv/src/main/kotlin/net/consensys/cava/kv/KeyValueStore.kt000066400000000000000000000064761341750772100253540ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.kv import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import net.consensys.cava.bytes.Bytes import net.consensys.cava.concurrent.AsyncCompletion import net.consensys.cava.concurrent.AsyncResult import net.consensys.cava.concurrent.coroutines.asyncCompletion import net.consensys.cava.concurrent.coroutines.asyncResult import java.io.Closeable /** * A key-value store. */ interface KeyValueStore : Closeable { /** * Retrieves data from the store. * * @param key The key for the content. * @return The stored data, or null if no data was stored under the specified key. */ suspend fun get(key: Bytes): Bytes? /** * Retrieves data from the store. * * @param key The key for the content. * @return An [AsyncResult] that will complete with the stored content, * or an empty optional if no content was available. */ fun getAsync(key: Bytes): AsyncResult = getAsync(Dispatchers.Default, key) /** * Retrieves data from the store. * * @param key The key for the content. * @param dispatcher The co-routine dispatcher for asynchronous tasks. * @return An [AsyncResult] that will complete with the stored content, * or an empty optional if no content was available. */ fun getAsync(dispatcher: CoroutineDispatcher, key: Bytes): AsyncResult = GlobalScope.asyncResult(dispatcher) { get(key) } /** * Puts data into the store. * * @param key The key to associate with the data, for use when retrieving. * @param value The data to store. */ suspend fun put(key: Bytes, value: Bytes) /** * Puts data into the store. * * Note: if the storage implementation already contains content for the given key, it does not need to replace the * existing content. * * @param key The key to associate with the data, for use when retrieving. * @param value The data to store. * @return An [AsyncCompletion] that will complete when the content is stored. */ fun putAsync(key: Bytes, value: Bytes): AsyncCompletion = putAsync(Dispatchers.Default, key, value) /** * Puts data into the store. * * Note: if the storage implementation already contains content for the given key, it does not need to replace the * existing content. * * @param key The key to associate with the data, for use when retrieving. * @param value The data to store. * @param dispatcher The co-routine dispatcher for asynchronous tasks. * @return An [AsyncCompletion] that will complete when the content is stored. */ fun putAsync(dispatcher: CoroutineDispatcher, key: Bytes, value: Bytes): AsyncCompletion = GlobalScope.asyncCompletion(dispatcher) { put(key, value) } } cava-0.6.0/kv/src/main/kotlin/net/consensys/cava/kv/LevelDBKeyValueStore.kt000066400000000000000000000055041341750772100265410ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.kv import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import net.consensys.cava.bytes.Bytes import org.fusesource.leveldbjni.JniDBFactory import org.iq80.leveldb.DB import org.iq80.leveldb.Options import java.io.IOException import java.nio.file.Files import java.nio.file.Path /** * A key-value store backed by LevelDB. * * @param dbPath The path to the levelDB database. * @param options Options for the levelDB database. * @param dispatcher The co-routine context for blocking tasks. * @return A key-value store. * @throws IOException If an I/O error occurs. * @constructor Open a LevelDB-backed key-value store. */ class LevelDBKeyValueStore @Throws(IOException::class) constructor( dbPath: Path, options: Options = Options().createIfMissing(true).cacheSize((100 * 1048576).toLong()), private val dispatcher: CoroutineDispatcher = Dispatchers.IO ) : KeyValueStore { companion object { /** * Open a LevelDB-backed key-value store. * * @param dbPath The path to the levelDB database. * @return A key-value store. * @throws IOException If an I/O error occurs. */ @JvmStatic @Throws(IOException::class) fun open(dbPath: Path) = LevelDBKeyValueStore(dbPath) /** * Open a LevelDB-backed key-value store. * * @param dbPath The path to the levelDB database. * @param options Options for the levelDB database. * @return A key-value store. * @throws IOException If an I/O error occurs. */ @JvmStatic @Throws(IOException::class) fun open(dbPath: Path, options: Options) = LevelDBKeyValueStore(dbPath, options) } private val db: DB init { Files.createDirectories(dbPath) db = JniDBFactory.factory.open(dbPath.toFile(), options) } override suspend fun get(key: Bytes): Bytes? = withContext(dispatcher) { val rawValue = db[key.toArrayUnsafe()] if (rawValue == null) { null } else { Bytes.wrap(rawValue) } } override suspend fun put(key: Bytes, value: Bytes) = withContext(dispatcher) { db.put(key.toArrayUnsafe(), value.toArrayUnsafe()) } /** * Closes the underlying LevelDB instance. */ override fun close() = db.close() } cava-0.6.0/kv/src/main/kotlin/net/consensys/cava/kv/MapDBKeyValueStore.kt000066400000000000000000000054371341750772100262140ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.kv import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import net.consensys.cava.bytes.Bytes import org.mapdb.DB import org.mapdb.DBMaker import org.mapdb.DataInput2 import org.mapdb.DataOutput2 import org.mapdb.HTreeMap import java.io.IOException import java.nio.file.Files import java.nio.file.Path /** * A key-value store backed by a MapDB instance. * * @param dbPath The path to the MapDB database. * @param dispatcher The co-routine dispatcher for blocking tasks. * @return A key-value store. * @throws IOException If an I/O error occurs. * @constructor Open a MapDB-backed key-value store. */ class MapDBKeyValueStore @Throws(IOException::class) constructor( dbPath: Path, private val dispatcher: CoroutineDispatcher = Dispatchers.IO ) : KeyValueStore { companion object { /** * Open a MapDB-backed key-value store. * * @param dbPath The path to the MapDB database. * @return A key-value store. * @throws IOException If an I/O error occurs. */ @JvmStatic @Throws(IOException::class) fun open(dbPath: Path) = MapDBKeyValueStore(dbPath) } private val db: DB private val storageData: HTreeMap init { Files.createDirectories(dbPath.parent) db = DBMaker.fileDB(dbPath.toFile()).transactionEnable().closeOnJvmShutdown().make() storageData = db.hashMap( "storageData", BytesSerializer(), BytesSerializer() ).createOrOpen() } override suspend fun get(key: Bytes): Bytes? = withContext(dispatcher) { storageData[key] } override suspend fun put(key: Bytes, value: Bytes) = withContext(dispatcher) { storageData[key] = value db.commit() } /** * Closes the underlying MapDB instance. */ override fun close() = db.close() } private class BytesSerializer : org.mapdb.Serializer { override fun serialize(out: DataOutput2, value: Bytes) { out.packInt(value.size()) out.write(value.toArrayUnsafe()) } override fun deserialize(input: DataInput2, available: Int): Bytes { val size = input.unpackInt() val bytes = ByteArray(size) input.readFully(bytes) return Bytes.wrap(bytes) } } cava-0.6.0/kv/src/main/kotlin/net/consensys/cava/kv/MapKeyValueStore.kt000066400000000000000000000032321341750772100257750ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.kv import net.consensys.cava.bytes.Bytes /** * A key-value store backed by an in-memory Map. * * @param map The backing map for this store. * @return A key-value store. * @constructor Open an in-memory key-value store. */ class MapKeyValueStore constructor(private val map: MutableMap = HashMap()) : KeyValueStore { companion object { /** * Open an in-memory key-value store. * * This store will use a [java.util.HashMap] as a backing store. * * @return A key-value store. */ @JvmStatic fun open(): MapKeyValueStore = MapKeyValueStore() /** * Open an in-memory key-value store. * * @param map The backing map for this store. * @return A key-value store. */ @JvmStatic fun open(map: MutableMap) = MapKeyValueStore(map) } override suspend fun get(key: Bytes): Bytes? = map[key] override suspend fun put(key: Bytes, value: Bytes) { map[key] = value } /** * Has no effect in this KeyValueStore implementation. */ override fun close() {} } cava-0.6.0/kv/src/main/kotlin/net/consensys/cava/kv/RedisKeyValueStore.kt000066400000000000000000000063521341750772100263340ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.kv import io.lettuce.core.RedisClient import io.lettuce.core.RedisURI import io.lettuce.core.api.StatefulRedisConnection import io.lettuce.core.api.async.RedisAsyncCommands import io.lettuce.core.codec.RedisCodec import kotlinx.coroutines.future.await import net.consensys.cava.bytes.Bytes import java.net.InetAddress import java.util.concurrent.CompletionStage /** * A key-value store backed by Redis. * * @param uri The uri to the Redis store. * @constructor Open a Redis-backed key-value store. */ class RedisKeyValueStore(uri: String) : KeyValueStore { companion object { /** * Open a Redis-backed key-value store. * * @param uri The uri to the Redis store. * @return A key-value store. */ @JvmStatic fun open(uri: String) = RedisKeyValueStore(uri) /** * Open a Redis-backed key-value store. * * @param port The port for the Redis store. * @return A key-value store. */ @JvmStatic fun open(port: Int) = RedisKeyValueStore(port) /** * Open a Redis-backed key-value store. * * @param address The address for the Redis store. * @return A key-value store. */ @JvmStatic fun open(address: InetAddress) = RedisKeyValueStore(6379, address) /** * Open a Redis-backed key-value store. * * @param port The port for the Redis store. * @param address The address for the Redis store. * @return A key-value store. */ @JvmStatic fun open(port: Int, address: InetAddress) = RedisKeyValueStore(port, address) /** * A [RedisCodec] for working with cava Bytes classes. * * @return A [RedisCodec] for working with cava Bytes classes. */ @JvmStatic fun codec(): RedisCodec = RedisBytesCodec() } private val conn: StatefulRedisConnection private val asyncCommands: RedisAsyncCommands /** * Open a Redis-backed key-value store. * * @param port The port for the Redis store. * @param address The address for the Redis store. */ @JvmOverloads constructor( port: Int = 6379, address: InetAddress = InetAddress.getLoopbackAddress() ) : this(RedisURI.create(address.hostAddress, port).toURI().toString()) init { val redisClient = RedisClient.create(uri) conn = redisClient.connect(RedisKeyValueStore.codec()) asyncCommands = conn.async() } override suspend fun get(key: Bytes): Bytes? = asyncCommands.get(key).await() override suspend fun put(key: Bytes, value: Bytes) { val future: CompletionStage = asyncCommands.set(key, value) future.await() } override fun close() { conn.close() } } cava-0.6.0/kv/src/main/kotlin/net/consensys/cava/kv/SQLKeyValueStore.kt000066400000000000000000000072221341750772100257220ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.kv import com.jolbox.bonecp.BoneCP import com.jolbox.bonecp.BoneCPConfig import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.guava.await import kotlinx.coroutines.withContext import net.consensys.cava.bytes.Bytes import java.io.IOException /** * A key-value store backed by a relational database. * * @param jdbcurl The JDBC url to connect to the database. * @param tableName the name of the table to use for storage. * @param keyColumn the key column of the store. * @param valueColumn the value column of the store. * @param dispatcher The co-routine context for blocking tasks. * @return A key-value store. * @throws IOException If an I/O error occurs. * @constructor Open a relational database backed key-value store. */ class SQLKeyValueStore @Throws(IOException::class) constructor( jdbcurl: String, val tableName: String = "store", val keyColumn: String = "key", val valueColumn: String = "value", private val dispatcher: CoroutineDispatcher = Dispatchers.IO ) : KeyValueStore { companion object { /** * Open a relational database backed key-value store. * * @param jdbcUrl The JDBC url to connect to the database. * @return A key-value store. * @throws IOException If an I/O error occurs. */ @JvmStatic @Throws(IOException::class) fun open(jdbcUrl: String) = SQLKeyValueStore(jdbcUrl) /** * Open a relational database backed key-value store. * * @param jdbcUrl The JDBC url to connect to the database. * @param tableName the name of the table to use for storage. * @param keyColumn the key column of the store. * @param valueColumn the value column of the store. * @return A key-value store. * @throws IOException If an I/O error occurs. */ @JvmStatic @Throws(IOException::class) fun open(jdbcUrl: String, tableName: String, keyColumn: String, valueColumn: String) = SQLKeyValueStore(jdbcUrl, tableName, keyColumn, valueColumn) } private val connectionPool: BoneCP init { val config = BoneCPConfig() config.jdbcUrl = jdbcurl connectionPool = BoneCP(config) } override suspend fun get(key: Bytes): Bytes? = withContext(dispatcher) { connectionPool.asyncConnection.await().use { val stmt = it.prepareStatement("SELECT $valueColumn FROM $tableName WHERE $keyColumn = ?") stmt.setBytes(1, key.toArrayUnsafe()) stmt.execute() val rs = stmt.resultSet if (rs.next()) { Bytes.wrap(rs.getBytes(1)) } else { null } } } override suspend fun put(key: Bytes, value: Bytes) = withContext(dispatcher) { connectionPool.asyncConnection.await().use { val stmt = it.prepareStatement("INSERT INTO $tableName($keyColumn, $valueColumn) VALUES(?,?)") stmt.setBytes(1, key.toArrayUnsafe()) stmt.setBytes(2, value.toArrayUnsafe()) stmt.execute() Unit } } /** * Closes the underlying connection pool. */ override fun close() = connectionPool.shutdown() } cava-0.6.0/kv/src/test/000077500000000000000000000000001341750772100146245ustar00rootroot00000000000000cava-0.6.0/kv/src/test/java/000077500000000000000000000000001341750772100155455ustar00rootroot00000000000000cava-0.6.0/kv/src/test/java/net/000077500000000000000000000000001341750772100163335ustar00rootroot00000000000000cava-0.6.0/kv/src/test/java/net/consensys/000077500000000000000000000000001341750772100203575ustar00rootroot00000000000000cava-0.6.0/kv/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100212715ustar00rootroot00000000000000cava-0.6.0/kv/src/test/java/net/consensys/cava/kv/000077500000000000000000000000001341750772100217115ustar00rootroot00000000000000cava-0.6.0/kv/src/test/java/net/consensys/cava/kv/KeyValueStoreTest.java000066400000000000000000000044701341750772100261630ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.kv; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.concurrent.AsyncCompletion; import net.consensys.cava.junit.TempDirectory; import net.consensys.cava.junit.TempDirectoryExtension; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) class KeyValueStoreTest { @Test void testPutAndGet() throws Exception { Map map = new HashMap<>(); KeyValueStore store = MapKeyValueStore.open(map); AsyncCompletion completion = store.putAsync(Bytes.of(123), Bytes.of(10, 12, 13)); completion.join(); Bytes value = store.getAsync(Bytes.of(123)).get(); assertNotNull(value); assertEquals(Bytes.of(10, 12, 13), value); assertEquals(Bytes.of(10, 12, 13), map.get(Bytes.of(123))); } @Test void testNoValue() throws Exception { Map map = new HashMap<>(); KeyValueStore store = MapKeyValueStore.open(map); assertNull(store.getAsync(Bytes.of(123)).get()); } @Test void testLevelDBWithoutOptions(@TempDirectory Path tempDirectory) throws Exception { try (LevelDBKeyValueStore leveldb = LevelDBKeyValueStore.open(tempDirectory.resolve("foo").resolve("bar"))) { AsyncCompletion completion = leveldb.putAsync(Bytes.of(123), Bytes.of(10, 12, 13)); completion.join(); Bytes value = leveldb.getAsync(Bytes.of(123)).get(); assertNotNull(value); assertEquals(Bytes.of(10, 12, 13), value); } } } cava-0.6.0/kv/src/test/java/net/consensys/cava/kv/RedisKeyValueStoreTest.java000066400000000000000000000051421341750772100271470ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.kv; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.concurrent.AsyncCompletion; import net.consensys.cava.junit.RedisPort; import net.consensys.cava.junit.RedisServerExtension; import java.net.InetAddress; import io.lettuce.core.RedisClient; import io.lettuce.core.RedisURI; import io.lettuce.core.api.StatefulRedisConnection; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(RedisServerExtension.class) class RedisKeyValueStoreTest { @Test void testPutAndGet(@RedisPort Integer redisPort) throws Exception { KeyValueStore store = RedisKeyValueStore.open(redisPort); AsyncCompletion completion = store.putAsync(Bytes.of(123), Bytes.of(10, 12, 13)); completion.join(); Bytes value = store.getAsync(Bytes.of(123)).get(); assertNotNull(value); assertEquals(Bytes.of(10, 12, 13), value); RedisClient client = RedisClient.create(RedisURI.create(InetAddress.getLoopbackAddress().getHostAddress(), redisPort)); try (StatefulRedisConnection conn = client.connect(new RedisBytesCodec())) { assertEquals(Bytes.of(10, 12, 13), conn.sync().get(Bytes.of(123))); } } @Test void testNoValue(@RedisPort Integer redisPort) throws Exception { KeyValueStore store = RedisKeyValueStore.open(redisPort, InetAddress.getLoopbackAddress()); assertNull(store.getAsync(Bytes.of(124)).get()); } @Test void testRedisCloseable(@RedisPort Integer redisPort) throws Exception { try (RedisKeyValueStore redis = RedisKeyValueStore.open("redis://127.0.0.1:" + redisPort)) { AsyncCompletion completion = redis.putAsync(Bytes.of(125), Bytes.of(10, 12, 13)); completion.join(); Bytes value = redis.getAsync(Bytes.of(125)).get(); assertNotNull(value); assertEquals(Bytes.of(10, 12, 13), value); } } } cava-0.6.0/kv/src/test/kotlin/000077500000000000000000000000001341750772100161245ustar00rootroot00000000000000cava-0.6.0/kv/src/test/kotlin/net/000077500000000000000000000000001341750772100167125ustar00rootroot00000000000000cava-0.6.0/kv/src/test/kotlin/net/consensys/000077500000000000000000000000001341750772100207365ustar00rootroot00000000000000cava-0.6.0/kv/src/test/kotlin/net/consensys/cava/000077500000000000000000000000001341750772100216505ustar00rootroot00000000000000cava-0.6.0/kv/src/test/kotlin/net/consensys/cava/kv/000077500000000000000000000000001341750772100222705ustar00rootroot00000000000000cava-0.6.0/kv/src/test/kotlin/net/consensys/cava/kv/KeyValueStoreSpec.kt000066400000000000000000000126441341750772100262140ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.kv import com.google.common.io.MoreFiles import com.google.common.io.RecursiveDeleteOption import com.winterbe.expekt.should import kotlinx.coroutines.runBlocking import net.consensys.cava.bytes.Bytes import net.consensys.cava.kv.Vars.foo import net.consensys.cava.kv.Vars.foobar import org.iq80.leveldb.DBException import org.jetbrains.spek.api.Spek import org.jetbrains.spek.api.dsl.describe import org.jetbrains.spek.api.dsl.it import java.nio.file.Files import java.nio.file.Paths import java.sql.DriverManager import java.util.concurrent.RejectedExecutionException object Vars { val foo = Bytes.wrap("foo".toByteArray())!! val foobar = Bytes.wrap("foobar".toByteArray())!! } object KeyValueStoreSpec : Spek({ val backingMap = mutableMapOf() val kv = MapKeyValueStore(backingMap) describe("a map-backed key value store") { it("should allow to store values") { runBlocking { kv.put(foo, foo) backingMap.get(foo).should.equal(foo) } } it("should allow to retrieve values") { runBlocking { kv.put(foobar, foo) kv.get(foobar).should.equal(foo) } } it("should return an empty optional when no value is present") { runBlocking { kv.get(Bytes.wrap("foofoobar".toByteArray())).should.be.`null` } } } }) object MapDBKeyValueStoreSpec : Spek({ val testDir = Files.createTempDirectory("data") val kv = MapDBKeyValueStore(testDir.resolve("data.db")) describe("a MapDB-backed key value store") { it("should allow to retrieve values") { runBlocking { kv.put(foobar, foo) kv.get(foobar).should.equal(foo) } } it("should return an empty optional when no value is present") { runBlocking { kv.get(Bytes.wrap("foofoobar".toByteArray())).should.be.`null` } } it("should not allow usage after the DB is closed") { val kv2 = MapDBKeyValueStore(testDir.resolve("data2.db")) kv2.close() runBlocking { var caught = false try { kv2.put(foobar, foo) } catch (e: IllegalAccessError) { caught = true } caught.should.be.`true` } } afterGroup { kv.close() MoreFiles.deleteRecursively(testDir, RecursiveDeleteOption.ALLOW_INSECURE) } } }) object LevelDBKeyValueStoreSpec : Spek({ val path = Files.createTempDirectory("leveldb") val kv = LevelDBKeyValueStore(path) afterGroup { kv.close() MoreFiles.deleteRecursively(path, RecursiveDeleteOption.ALLOW_INSECURE) } describe("a levelDB-backed key value store") { it("should allow to retrieve values") { runBlocking { kv.put(foobar, foo) kv.get(foobar).should.equal(foo) } } it("should return an empty optional when no value is present") { runBlocking { kv.get(Bytes.wrap("foofoobar".toByteArray())).should.be.`null` } } it("should not allow usage after the DB is closed") { val kv2 = LevelDBKeyValueStore(path.resolve("subdb")) kv2.close() runBlocking { var caught = false try { kv2.put(foobar, foo) } catch (e: DBException) { caught = true } caught.should.be.`true` } } } }) object SQLKeyValueStoreSpec : Spek({ Files.deleteIfExists(Paths.get(System.getProperty("java.io.tmpdir"), "testdb.mv.db")) Files.deleteIfExists(Paths.get(System.getProperty("java.io.tmpdir"), "testdb.trace.db")) val jdbcUrl = "jdbc:h2:${System.getProperty("java.io.tmpdir")}/testdb" DriverManager.getConnection(jdbcUrl).use { val st = it.createStatement() st.executeUpdate("create table store(key binary, value binary, primary key(key))") st.executeUpdate("create table store2(id binary, val binary, primary key(id))") } val kv = SQLKeyValueStore(jdbcUrl) val otherkv = SQLKeyValueStore.open(jdbcUrl, "store2", "id", "val") afterGroup { kv.close() otherkv.close() } describe("a SQL-backed key value store") { it("should allow to retrieve values") { runBlocking { kv.put(foobar, foo) kv.get(foobar).should.equal(foo) } } it("should allow to retrieve values when configured with a different table") { runBlocking { otherkv.put(foobar, foo) otherkv.get(foobar).should.equal(foo) } } it("should return an empty optional when no value is present") { runBlocking { kv.get(Bytes.wrap("foofoobar".toByteArray())).should.be.`null` } } it("should not allow usage after the DB is closed") { val kv2 = SQLKeyValueStore("jdbc:h2:mem:testdb") kv2.close() runBlocking { var caught = false try { kv2.put(foobar, foo) } catch (e: RejectedExecutionException) { caught = true } caught.should.be.`true` } } } }) cava-0.6.0/merkle-trie/000077500000000000000000000000001341750772100146565ustar00rootroot00000000000000cava-0.6.0/merkle-trie/build.gradle000066400000000000000000000010301341750772100171270ustar00rootroot00000000000000description = 'Patricia Merkle Trie implementations.' dependencies { compile project(':bytes') compile project(':concurrent-coroutines') compile project(':crypto') compile project(':rlp') compile 'com.google.guava:guava' compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core' testCompile project(':junit') testCompile 'org.bouncycastle:bcprov-jdk15on' testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/merkle-trie/src/000077500000000000000000000000001341750772100154455ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/main/000077500000000000000000000000001341750772100163715ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/main/java/000077500000000000000000000000001341750772100173125ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/main/java/net/000077500000000000000000000000001341750772100201005ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/main/java/net/consensys/000077500000000000000000000000001341750772100221245ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/main/java/net/consensys/cava/000077500000000000000000000000001341750772100230365ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/main/java/net/consensys/cava/trie/000077500000000000000000000000001341750772100240015ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/main/java/net/consensys/cava/trie/CompactEncoding.java000066400000000000000000000073761341750772100277160ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie; import static com.google.common.base.Preconditions.checkArgument; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.MutableBytes; /** * Compact (Hex-prefix) encoding and decoding. * *

* An implementation of Compact * (Hex-prefix) encoding. */ public final class CompactEncoding { private CompactEncoding() {} public static final byte LEAF_TERMINATOR = 0x10; /** * Calculate a RADIX-16 path for a given byte sequence. * * @param bytes The byte sequence to calculate the path for. * @return The Radix-16 path. */ public static Bytes bytesToPath(Bytes bytes) { MutableBytes path = MutableBytes.create(bytes.size() * 2 + 1); int j = 0; for (int i = 0; i < bytes.size(); i += 1, j += 2) { byte b = bytes.get(i); path.set(j, (byte) ((b >>> 4) & 0x0f)); path.set(j + 1, (byte) (b & 0x0f)); } path.set(j, LEAF_TERMINATOR); return path; } /** * Encode a Radix-16 path. * * @param path A Radix-16 path. * @return A compact-encoded path. */ public static Bytes encode(Bytes path) { int size = path.size(); boolean isLeaf = size > 0 && path.get(size - 1) == LEAF_TERMINATOR; if (isLeaf) { size = size - 1; } MutableBytes encoded = MutableBytes.create((size + 2) / 2); int i = 0; int j = 0; if (size % 2 == 1) { // add first nibble to magic byte high = (byte) (isLeaf ? 0x03 : 0x01); byte low = path.get(i++); if ((low & 0xf0) != 0) { throw new IllegalArgumentException("Invalid path: contains elements larger than a nibble"); } encoded.set(j++, (byte) (high << 4 | low)); } else { byte high = (byte) (isLeaf ? 0x02 : 0x00); encoded.set(j++, (byte) (high << 4)); } while (i < size) { byte high = path.get(i++); byte low = path.get(i++); if ((high & 0xf0) != 0 || (low & 0xf0) != 0) { throw new IllegalArgumentException("Invalid path: contains elements larger than a nibble"); } encoded.set(j++, (byte) (high << 4 | low)); } return encoded; } /** * Decode a compact-encoded path to Radix-16. * * @param encoded A compact-encoded path. * @return A Radix-16 path. */ public static Bytes decode(Bytes encoded) { int size = encoded.size(); checkArgument(size > 0); byte magic = encoded.get(0); checkArgument((magic & 0xc0) == 0, "Invalid compact encoding"); boolean isLeaf = (magic & 0x20) != 0; int pathLength = ((size - 1) * 2) + (isLeaf ? 1 : 0); MutableBytes path; int i = 0; if ((magic & 0x10) != 0) { // need to use lower nibble of magic path = MutableBytes.create(pathLength + 1); path.set(i++, (byte) (magic & 0x0f)); } else { path = MutableBytes.create(pathLength); } for (int j = 1; j < size; j++) { byte b = encoded.get(j); path.set(i++, (byte) ((b >>> 4) & 0x0f)); path.set(i++, (byte) (b & 0x0f)); } if (isLeaf) { path.set(i, LEAF_TERMINATOR); } return path; } } cava-0.6.0/merkle-trie/src/main/java/net/consensys/cava/trie/package-info.java000066400000000000000000000010331341750772100271650ustar00rootroot00000000000000/** * 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). */ @ParametersAreNonnullByDefault package net.consensys.cava.trie; import javax.annotation.ParametersAreNonnullByDefault; cava-0.6.0/merkle-trie/src/main/kotlin/000077500000000000000000000000001341750772100176715ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/main/kotlin/net/000077500000000000000000000000001341750772100204575ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/000077500000000000000000000000001341750772100225035ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/000077500000000000000000000000001341750772100234155ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/000077500000000000000000000000001341750772100243605ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/BranchNode.kt000066400000000000000000000101161341750772100267220ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import net.consensys.cava.bytes.Bytes import net.consensys.cava.bytes.Bytes32 import net.consensys.cava.bytes.MutableBytes import net.consensys.cava.crypto.Hash.keccak256 import net.consensys.cava.rlp.RLP import java.lang.ref.WeakReference private val NULL_NODE: NullNode<*> = NullNode.instance() internal class BranchNode( private val children: List>, private val value: V?, private val nodeFactory: NodeFactory, private val valueSerializer: (V) -> Bytes ) : Node { companion object { const val RADIX = CompactEncoding.LEAF_TERMINATOR.toInt() } @Volatile private var rlp: WeakReference? = null @Volatile private var hash: Bytes32? = null init { assert(children.size == RADIX) } override suspend fun accept(visitor: NodeVisitor, path: Bytes): Node = visitor.visit(this, path) override suspend fun path(): Bytes = Bytes.EMPTY override suspend fun value(): V? = value fun child(index: Byte): Node = children[index.toInt()] override fun rlp(): Bytes { val prevEncoded = rlp?.get() if (prevEncoded != null) { return prevEncoded } val encoded = RLP.encodeList { out -> for (i in 0 until RADIX) { out.writeRLP(children[i].rlpRef()) } if (value != null) { out.writeValue(valueSerializer(value)) } else { out.writeValue(Bytes.EMPTY) } } rlp = WeakReference(encoded) return encoded } override fun rlpRef(): Bytes { val rlp = rlp() return if (rlp.size() < 32) rlp else RLP.encodeValue(hash()) } override fun hash(): Bytes32 { hash?.let { return it } val hashed = keccak256(rlp()) hash = hashed return hashed } override suspend fun replacePath(path: Bytes): Node = nodeFactory.createExtension(path, this) suspend fun replaceChild(index: Byte, updatedChild: Node): Node { val newChildren = ArrayList(children) newChildren[index.toInt()] = updatedChild if (updatedChild === NULL_NODE) { if (value != null && !hasChildren()) { return nodeFactory.createLeaf(Bytes.of(index), value) } else if (value == null) { val flattened = maybeFlatten(newChildren) if (flattened != null) { return flattened } } } return nodeFactory.createBranch(newChildren, value) } suspend fun replaceValue(value: V): Node = nodeFactory.createBranch(children, value) suspend fun removeValue(): Node = maybeFlatten(children) ?: nodeFactory.createBranch(children, null) private fun hasChildren(): Boolean { for (child in children) { if (child !== NULL_NODE) { return true } } return false } } private suspend fun maybeFlatten(children: List>): Node? { val onlyChildIndex = findOnlyChild(children) if (onlyChildIndex < 0) { return null } val onlyChild = children[onlyChildIndex] // replace the path of the only child and return it val onlyChildPath = onlyChild.path() val completePath = MutableBytes.create(1 + onlyChildPath.size()) completePath.set(0, onlyChildIndex.toByte()) onlyChildPath.copyTo(completePath, 1) return onlyChild.replacePath(completePath) } private fun findOnlyChild(children: List>): Int { var onlyChildIndex = -1 assert(children.size == BranchNode.RADIX.toInt()) for (i in 0 until BranchNode.RADIX) { if (children[i] !== NULL_NODE) { if (onlyChildIndex >= 0) { return -1 } onlyChildIndex = i } } return onlyChildIndex } cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/DefaultNodeFactory.kt000066400000000000000000000040151341750772100304420ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import net.consensys.cava.bytes.Bytes import java.util.Collections internal class DefaultNodeFactory(private val valueSerializer: (V) -> Bytes) : NodeFactory { private val nullNode: NullNode = NullNode.instance() override suspend fun createExtension(path: Bytes, child: Node): Node = ExtensionNode(path, child, this) override suspend fun createBranch(leftIndex: Byte, left: Node, rightIndex: Byte, right: Node): Node { assert(leftIndex <= BranchNode.RADIX) assert(rightIndex <= BranchNode.RADIX) assert(leftIndex != rightIndex) val children: MutableList> = Collections.nCopies(BranchNode.RADIX, nullNode).toMutableList() return when { leftIndex.toInt() == BranchNode.RADIX -> { children[rightIndex.toInt()] = right createBranch(children, left.value()) } rightIndex.toInt() == BranchNode.RADIX -> { children[leftIndex.toInt()] = left createBranch(children, right.value()) } else -> { children[leftIndex.toInt()] = left children[rightIndex.toInt()] = right createBranch(children, null) } } } override suspend fun createBranch(newChildren: List>, value: V?): Node { return BranchNode(newChildren, value, this, valueSerializer) } override suspend fun createLeaf(path: Bytes, value: V): Node { return LeafNode(path, value, this, valueSerializer) } } cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/ExtensionNode.kt000066400000000000000000000046731341750772100275140ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import net.consensys.cava.bytes.Bytes import net.consensys.cava.bytes.Bytes32 import net.consensys.cava.crypto.Hash.keccak256 import net.consensys.cava.rlp.RLP import java.lang.ref.WeakReference internal class ExtensionNode( private val path: Bytes, private val child: Node, private val nodeFactory: NodeFactory ) : Node { @Volatile private var rlp: WeakReference? = null @Volatile private var hash: Bytes32? = null init { assert(path.size() > 0) assert(path.get(path.size() - 1) != CompactEncoding.LEAF_TERMINATOR) { "Extension path ends in a leaf terminator" } } override suspend fun accept(visitor: NodeVisitor, path: Bytes): Node = visitor.visit(this, path) override suspend fun path(): Bytes = path override suspend fun value(): V? = throw UnsupportedOperationException() fun child(): Node = child override fun rlp(): Bytes { val prevEncoded = rlp?.get() if (prevEncoded != null) { return prevEncoded } val encoded = RLP.encodeList { writer -> writer.writeValue(CompactEncoding.encode(path)) writer.writeRLP(child.rlpRef()) } rlp = WeakReference(encoded) return encoded } override fun rlpRef(): Bytes { val rlp = rlp() return if (rlp.size() < 32) rlp else RLP.encodeValue(hash()) } override fun hash(): Bytes32 { hash?.let { return it } val hashed = keccak256(rlp()) hash = hashed return hashed } suspend fun replaceChild(updatedChild: Node): Node { // collapse this extension - if the child is a branch, it will create a new extension val childPath = updatedChild.path() return updatedChild.replacePath(Bytes.concatenate(path, childPath)) } override suspend fun replacePath(path: Bytes): Node { return if (path.size() == 0) child else nodeFactory.createExtension(path, child) } } cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/GetVisitor.kt000066400000000000000000000036551341750772100270300ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import net.consensys.cava.bytes.Bytes internal class GetVisitor : NodeVisitor { override suspend fun visit(extensionNode: ExtensionNode, path: Bytes): Node { val extensionPath = extensionNode.path() val commonPathLength = extensionPath.commonPrefixLength(path) assert(commonPathLength < path.size()) { "Visiting path doesn't end with a non-matching terminator" } if (commonPathLength < extensionPath.size()) { // path diverges before the end of the extension, so it cannot match return NullNode.instance() } return extensionNode.child().accept(this, path.slice(commonPathLength)) } override suspend fun visit(branchNode: BranchNode, path: Bytes): Node { assert(path.size() > 0) { "Visiting path doesn't end with a non-matching terminator" } val childIndex = path.get(0) if (childIndex == CompactEncoding.LEAF_TERMINATOR) { return branchNode } return branchNode.child(childIndex).accept(this, path.slice(1)) } override suspend fun visit(leafNode: LeafNode, path: Bytes): Node { val leafPath = leafNode.path() if (leafPath.commonPrefixLength(path) != leafPath.size()) { return NullNode.instance() } return leafNode } override suspend fun visit(nullNode: NullNode, path: Bytes): Node = NullNode.instance() } cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/LeafNode.kt000066400000000000000000000037051341750772100264020ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import net.consensys.cava.bytes.Bytes import net.consensys.cava.bytes.Bytes32 import net.consensys.cava.crypto.Hash.keccak256 import net.consensys.cava.rlp.RLP import java.lang.ref.WeakReference internal class LeafNode( private val path: Bytes, private val value: V, private val nodeFactory: NodeFactory, private val valueSerializer: (V) -> Bytes ) : Node { @Volatile private var rlp: WeakReference? = null @Volatile private var hash: Bytes32? = null override suspend fun accept(visitor: NodeVisitor, path: Bytes): Node = visitor.visit(this, path) override suspend fun path(): Bytes = path override suspend fun value(): V? = value override fun rlp(): Bytes { val prevEncoded = rlp?.get() if (prevEncoded != null) { return prevEncoded } val encoded = RLP.encodeList { writer -> writer.writeValue(CompactEncoding.encode(path)) writer.writeValue(valueSerializer(value)) } rlp = WeakReference(encoded) return encoded } override fun rlpRef(): Bytes { val rlp = rlp() return if (rlp.size() < 32) rlp else RLP.encodeValue(hash()) } override fun hash(): Bytes32 { hash?.let { return it } val hashed = keccak256(rlp()) hash = hashed return hashed } override suspend fun replacePath(path: Bytes): Node = nodeFactory.createLeaf(path, value) } cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/MerklePatriciaTrie.kt000066400000000000000000000076521341750772100304520ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import net.consensys.cava.bytes.Bytes import net.consensys.cava.bytes.Bytes32 import net.consensys.cava.concurrent.AsyncCompletion import net.consensys.cava.concurrent.AsyncResult import net.consensys.cava.trie.CompactEncoding.bytesToPath import java.util.function.Function import kotlin.text.Charsets.UTF_8 internal fun bytesIdentity(b: Bytes): Bytes = b internal fun stringSerializer(s: String): Bytes = Bytes.wrap(s.toByteArray(UTF_8)) internal fun stringDeserializer(b: Bytes): String = String(b.toArrayUnsafe(), UTF_8) /** * An in-memory [MerkleTrie]. * * @param The type of values stored by this trie. * @param valueSerializer A function for serializing values to bytes. * @constructor Creates an empty trie. */ class MerklePatriciaTrie(valueSerializer: (V) -> Bytes) : MerkleTrie { companion object { /** * Create a trie with keys and values of type [Bytes]. */ @JvmStatic fun storingBytes(): MerklePatriciaTrie = MerklePatriciaTrie(::bytesIdentity) /** * Create a trie with value of type [String]. * * Strings are stored in UTF-8 encoding. */ @JvmStatic fun storingStrings(): MerklePatriciaTrie = MerklePatriciaTrie(::stringSerializer) /** * Create a trie. * * @param valueSerializer A function for serializing values to bytes. * @param The serialized type. * @return A new merkle trie. */ @JvmStatic fun create(valueSerializer: Function): MerklePatriciaTrie = MerklePatriciaTrie(valueSerializer::apply) } private val getVisitor = GetVisitor() private val removeVisitor = RemoveVisitor() private val nodeFactory: DefaultNodeFactory = DefaultNodeFactory(valueSerializer) private var root: Node = NullNode.instance() override suspend fun get(key: Bytes): V? = root.accept(getVisitor, bytesToPath(key)).value() // This implementation does not suspend, so we can use the unconfined context @UseExperimental(ExperimentalCoroutinesApi::class) override fun getAsync(key: Bytes): AsyncResult = runBlocking(Dispatchers.Unconfined) { AsyncResult.completed(get(key)) } override suspend fun put(key: Bytes, value: V?) { if (value == null) { return remove(key) } this.root = root.accept(PutVisitor(nodeFactory, value), bytesToPath(key)) } // This implementation does not suspend, so we can use the unconfined context @UseExperimental(ExperimentalCoroutinesApi::class) override fun putAsync(key: Bytes, value: V?): AsyncCompletion = runBlocking(Dispatchers.Unconfined) { put(key, value) AsyncCompletion.completed() } override suspend fun remove(key: Bytes) { this.root = root.accept(removeVisitor, bytesToPath(key)) } // This implementation does not suspend, so we can use the unconfined context @UseExperimental(ExperimentalCoroutinesApi::class) override fun removeAsync(key: Bytes): AsyncCompletion = runBlocking(Dispatchers.Unconfined) { remove(key) AsyncCompletion.completed() } override fun rootHash(): Bytes32 = root.hash() /** * @return A string representation of the object. */ override fun toString(): String = javaClass.simpleName + "[" + rootHash() + "]" } cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/MerkleStorage.kt000066400000000000000000000046771341750772100275020ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import net.consensys.cava.bytes.Bytes import net.consensys.cava.bytes.Bytes32 import net.consensys.cava.concurrent.AsyncCompletion import net.consensys.cava.concurrent.AsyncResult import net.consensys.cava.concurrent.coroutines.await /** * Storage for use in a [StoredMerklePatriciaTrie]. */ interface MerkleStorage { /** * Get the stored content under the given hash. * * @param hash The hash for the content. * @return The stored content, or {@code null} if not found. */ suspend fun get(hash: Bytes32): Bytes? /** * Store content with a given hash. * * Note: if the storage implementation already contains content for the given hash, it does not need to replace the * existing content. * * @param hash The hash for the content. * @param content The content to store. */ suspend fun put(hash: Bytes32, content: Bytes) } /** * A [MerkleStorage] implementation using [AsyncResult]'s. */ abstract class AsyncMerkleStorage : MerkleStorage { override suspend fun get(hash: Bytes32): Bytes? = getAsync(hash).await() /** * Get the stored content under the given hash. * * @param hash The hash for the content. * @return An [AsyncResult] that will complete with the stored content or {@code null} if not found. */ abstract fun getAsync(hash: Bytes32): AsyncResult override suspend fun put(hash: Bytes32, content: Bytes) = putAsync(hash, content).await() /** * Store content with a given hash. * * Note: if the storage implementation already contains content for the given hash, it does not need to replace the * existing content. * * @param hash The hash for the content. * @param content The content to store. * @return An [AsyncCompletion] that will complete when the content is stored. */ abstract fun putAsync(hash: Bytes32, content: Bytes): AsyncCompletion } cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/MerkleStorageException.kt000066400000000000000000000024301341750772100313420ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie /** * This exception is thrown when there is an issue retrieving or decoding values from [MerkleStorage]. */ class MerkleStorageException : RuntimeException { /** * Constructs a new exception with the specified detail message. * The cause is not initialized, and may subsequently be initialized by a * call to {@link #initCause}. * * @param message The detail message. */ constructor(message: String) : super(message) /** * Constructs a new exception with the specified detail message and * cause. * * @param message The detail message. * @param cause The cause. */ constructor(message: String, cause: Exception) : super(message, cause) } cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/MerkleTrie.kt000066400000000000000000000136341341750772100267720ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import net.consensys.cava.bytes.Bytes import net.consensys.cava.bytes.Bytes32 import net.consensys.cava.concurrent.AsyncCompletion import net.consensys.cava.concurrent.AsyncResult import net.consensys.cava.concurrent.coroutines.asyncCompletion import net.consensys.cava.concurrent.coroutines.asyncResult import net.consensys.cava.crypto.Hash import net.consensys.cava.rlp.RLP // Workaround for a javadoc generation issue - extracting these method bodies out of the default method and into // private funcs appears to resolve it. It would be good to remove this workaround one day. private fun getAsync(dispatcher: CoroutineDispatcher, key: K, trie: MerkleTrie): AsyncResult = GlobalScope.asyncResult(dispatcher) { trie.get(key) } private fun putAsync( dispatcher: CoroutineDispatcher, key: K, value: V?, trie: MerkleTrie ): AsyncCompletion = GlobalScope.asyncCompletion(dispatcher) { trie.put(key, value) } /** * A Merkle Trie. */ interface MerkleTrie { companion object { /** * The root hash of an empty tree. */ val EMPTY_TRIE_ROOT_HASH: Bytes32 = Hash.keccak256(RLP.encodeValue(Bytes.EMPTY)) } /** * Returns the value that corresponds to the specified key, or an empty byte array if no such value exists. * * @param key The key of the value to be returned. * @return The value that corresponds to the specified key, or {@code null} if no such value exists. * @throws MerkleStorageException If there is an error while accessing or decoding data from storage. */ suspend fun get(key: K): V? /** * Returns the value that corresponds to the specified key, or an empty byte array if no such value exists. * * @param key The key of the value to be returned. * @return A value that corresponds to the specified key, or {@code null} if no such value exists. */ fun getAsync(key: K): AsyncResult = getAsync(Dispatchers.Default, key) /** * Returns the value that corresponds to the specified key, or an empty byte array if no such value exists. * * @param key The key of the value to be returned. * @param dispatcher The co-routine dispatcher for asynchronous tasks. * @return A value that corresponds to the specified key, or {@code null} if no such value exists. */ fun getAsync(dispatcher: CoroutineDispatcher, key: K): AsyncResult = getAsync(dispatcher, key, this) /** * Updates the value that corresponds to the specified key, creating the value if one does not already exist. * * If the value is null, deletes the value that corresponds to the specified key, if such a value exists. * * @param key The key that corresponds to the value to be updated. * @param value The value to associate the key with. * @throws MerkleStorageException If there is an error while writing to storage. */ suspend fun put(key: K, value: V?) /** * Updates the value that corresponds to the specified key, creating the value if one does not already exist. * * If the value is null, deletes the value that corresponds to the specified key, if such a value exists. * * @param key The key that corresponds to the value to be updated. * @param value The value to associate the key with. * @return A completion that will complete when the value has been put into the trie. */ fun putAsync(key: K, value: V?): AsyncCompletion = putAsync(Dispatchers.Default, key, value) /** * Updates the value that corresponds to the specified key, creating the value if one does not already exist. * * If the value is null, deletes the value that corresponds to the specified key, if such a value exists. * * @param key The key that corresponds to the value to be updated. * @param value The value to associate the key with. * @param dispatcher The co-routine dispatcher for asynchronous tasks. * @return A completion that will complete when the value has been put into the trie. */ fun putAsync(dispatcher: CoroutineDispatcher, key: K, value: V?): AsyncCompletion = putAsync(dispatcher, key, value, this) /** * Deletes the value that corresponds to the specified key, if such a value exists. * * @param key The key of the value to be deleted. * @throws MerkleStorageException If there is an error while writing to storage. */ suspend fun remove(key: K) /** * Deletes the value that corresponds to the specified key, if such a value exists. * * @param key The key of the value to be deleted. * @return A completion that will complete when the value has been removed. */ fun removeAsync(key: K): AsyncCompletion = removeAsync(Dispatchers.Default, key) /** * Deletes the value that corresponds to the specified key, if such a value exists. * * @param key The key of the value to be deleted. * @param dispatcher The co-routine dispatcher for asynchronous tasks. * @return A completion that will complete when the value has been removed. */ fun removeAsync(dispatcher: CoroutineDispatcher, key: K): AsyncCompletion = GlobalScope.asyncCompletion(dispatcher) { remove(key) } /** * Returns the KECCAK256 hash of the root node of the trie. * * @return The KECCAK256 hash of the root node of the trie. */ fun rootHash(): Bytes32 } cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/Node.kt000066400000000000000000000017071341750772100256120ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import net.consensys.cava.bytes.Bytes import net.consensys.cava.bytes.Bytes32 internal interface Node { suspend fun accept(visitor: NodeVisitor, path: Bytes): Node suspend fun path(): Bytes suspend fun value(): V? fun rlp(): Bytes fun rlpRef(): Bytes fun hash(): Bytes32 suspend fun replacePath(path: Bytes): Node } cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/NodeFactory.kt000066400000000000000000000017501341750772100271400ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import net.consensys.cava.bytes.Bytes internal interface NodeFactory { suspend fun createExtension(path: Bytes, child: Node): Node suspend fun createBranch(leftIndex: Byte, left: Node, rightIndex: Byte, right: Node): Node suspend fun createBranch(newChildren: List>, value: V?): Node suspend fun createLeaf(path: Bytes, value: V): Node } cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/NodeVisitor.kt000066400000000000000000000017141341750772100271700ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import net.consensys.cava.bytes.Bytes internal interface NodeVisitor { suspend fun visit(extensionNode: ExtensionNode, path: Bytes): Node suspend fun visit(branchNode: BranchNode, path: Bytes): Node suspend fun visit(leafNode: LeafNode, path: Bytes): Node suspend fun visit(nullNode: NullNode, path: Bytes): Node } cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/NullNode.kt000066400000000000000000000027311341750772100264430ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import net.consensys.cava.bytes.Bytes import net.consensys.cava.bytes.Bytes32 import net.consensys.cava.crypto.Hash.keccak256 import net.consensys.cava.rlp.RLP internal class NullNode private constructor() : Node { companion object { private val RLP_NULL = RLP.encodeByteArray(ByteArray(0)) private val HASH = keccak256(RLP_NULL) private val instance = NullNode() @Suppress("UNCHECKED_CAST") fun instance(): NullNode = instance as NullNode } override suspend fun accept(visitor: NodeVisitor, path: Bytes): Node = visitor.visit(this, path) override suspend fun path(): Bytes = Bytes.EMPTY override suspend fun value(): V? = null override fun rlp(): Bytes = RLP_NULL override fun rlpRef(): Bytes = RLP_NULL override fun hash(): Bytes32 = HASH override suspend fun replacePath(path: Bytes): Node = this } cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/PutVisitor.kt000066400000000000000000000071101341750772100270470ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import net.consensys.cava.bytes.Bytes internal class PutVisitor( private val nodeFactory: NodeFactory, private val value: V ) : NodeVisitor { override suspend fun visit(extensionNode: ExtensionNode, path: Bytes): Node { val extensionPath = extensionNode.path() val commonPathLength = extensionPath.commonPrefixLength(path) assert(commonPathLength < path.size()) { "Visiting path doesn't end with a non-matching terminator" } if (commonPathLength == extensionPath.size()) { val child = extensionNode.child() val updatedChild = child.accept(this, path.slice(commonPathLength)) return extensionNode.replaceChild(updatedChild) } // The path diverges before the end of the extension, so create a new branch val leafIndex = path.get(commonPathLength) val leafPath = path.slice(commonPathLength + 1) val extensionIndex = extensionPath.get(commonPathLength) val updatedExtension = extensionNode.replacePath(extensionPath.slice(commonPathLength + 1)) val leaf = nodeFactory.createLeaf(leafPath, value) val branch = nodeFactory.createBranch(leafIndex, leaf, extensionIndex, updatedExtension) if (commonPathLength == 0) { return branch } return nodeFactory.createExtension(extensionPath.slice(0, commonPathLength), branch) } override suspend fun visit(branchNode: BranchNode, path: Bytes): Node { assert(path.size() > 0) { "Visiting path doesn't end with a non-matching terminator" } val childIndex = path.get(0) if (childIndex == CompactEncoding.LEAF_TERMINATOR) { return branchNode.replaceValue(value) } val updatedChild = branchNode.child(childIndex).accept(this, path.slice(1)) return branchNode.replaceChild(childIndex, updatedChild) } override suspend fun visit(leafNode: LeafNode, path: Bytes): Node { val leafPath = leafNode.path() val commonPathLength = leafPath.commonPrefixLength(path) // Check if the current leaf node should be replaced if (commonPathLength == leafPath.size() && commonPathLength == path.size()) { return nodeFactory.createLeaf(leafPath, value) } assert(commonPathLength < leafPath.size() && commonPathLength < path.size(), { "Should not have consumed non-matching terminator" }) // The current leaf path must be split to accommodate the new value. val newLeafIndex = path.get(commonPathLength) val newLeafPath = path.slice(commonPathLength + 1) val updatedLeafIndex = leafPath.get(commonPathLength) val updatedLeaf = leafNode.replacePath(leafPath.slice(commonPathLength + 1)) val leaf = nodeFactory.createLeaf(newLeafPath, value) val branch = nodeFactory.createBranch(updatedLeafIndex, updatedLeaf, newLeafIndex, leaf) if (commonPathLength == 0) { return branch } return nodeFactory.createExtension(leafPath.slice(0, commonPathLength), branch) } override suspend fun visit(nullNode: NullNode, path: Bytes): Node = nodeFactory.createLeaf(path, value) } cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/RemoveVisitor.kt000066400000000000000000000042111341750772100275330ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import net.consensys.cava.bytes.Bytes internal class RemoveVisitor : NodeVisitor { override suspend fun visit(extensionNode: ExtensionNode, path: Bytes): Node { val extensionPath = extensionNode.path() val commonPathLength = extensionPath.commonPrefixLength(path) assert(commonPathLength < path.size()) { "Visiting path doesn't end with a non-matching terminator" } if (commonPathLength == extensionPath.size()) { val child = extensionNode.child() val updatedChild = child.accept(this, path.slice(commonPathLength)) return extensionNode.replaceChild(updatedChild) } // The path diverges before the end of the extension, so it cannot match return extensionNode } override suspend fun visit(branchNode: BranchNode, path: Bytes): Node { assert(path.size() > 0) { "Visiting path doesn't end with a non-matching terminator" } val childIndex = path.get(0) if (childIndex == CompactEncoding.LEAF_TERMINATOR) { return branchNode.removeValue() } val updatedChild = branchNode.child(childIndex).accept(this, path.slice(1)) return branchNode.replaceChild(childIndex, updatedChild) } override suspend fun visit(leafNode: LeafNode, path: Bytes): Node { val leafPath = leafNode.path() val commonPathLength = leafPath.commonPrefixLength(path) if (commonPathLength == leafPath.size()) { return NullNode.instance() } return leafNode } override suspend fun visit(nullNode: NullNode, path: Bytes): Node = NullNode.instance() } cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/StoredMerklePatriciaTrie.kt000066400000000000000000000150331341750772100316230ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import net.consensys.cava.bytes.Bytes import net.consensys.cava.bytes.Bytes32 import net.consensys.cava.trie.CompactEncoding.bytesToPath import net.consensys.cava.trie.MerkleTrie.Companion.EMPTY_TRIE_ROOT_HASH import java.util.function.Function /** * A [MerkleTrie] that persists trie nodes to a [MerkleStorage] key/value store. * * @param The type of values stored by this trie. */ class StoredMerklePatriciaTrie : MerkleTrie { companion object { /** * Create a trie with value of type [Bytes]. * * @param storage The storage to use for persistence. */ @JvmStatic fun storingBytes(storage: MerkleStorage): StoredMerklePatriciaTrie = StoredMerklePatriciaTrie(storage, ::bytesIdentity, ::bytesIdentity) /** * Create a trie with keys and values of type [Bytes]. * * @param storage The storage to use for persistence. * @param rootHash The initial root has for the trie, which should be already present in `storage`. */ @JvmStatic fun storingBytes(storage: MerkleStorage, rootHash: Bytes32): StoredMerklePatriciaTrie = StoredMerklePatriciaTrie(storage, rootHash, ::bytesIdentity, ::bytesIdentity) /** * Create a trie with value of type [String]. * * Strings are stored in UTF-8 encoding. * * @param storage The storage to use for persistence. */ @JvmStatic fun storingStrings(storage: MerkleStorage): StoredMerklePatriciaTrie = StoredMerklePatriciaTrie(storage, ::stringSerializer, ::stringDeserializer) /** * Create a trie with keys and values of type [String]. * * Strings are stored in UTF-8 encoding. * * @param storage The storage to use for persistence. * @param rootHash The initial root has for the trie, which should be already present in `storage`. */ @JvmStatic fun storingStrings(storage: MerkleStorage, rootHash: Bytes32): StoredMerklePatriciaTrie = StoredMerklePatriciaTrie(storage, rootHash, ::stringSerializer, ::stringDeserializer) /** * Create a trie. * * @param storage The storage to use for persistence. * @param valueSerializer A function for serializing values to bytes. * @param valueDeserializer A function for deserializing values from bytes. * @param The serialized type. * @return A new merkle trie. */ @JvmStatic fun create( storage: MerkleStorage, valueSerializer: Function, valueDeserializer: Function ): StoredMerklePatriciaTrie { return StoredMerklePatriciaTrie(storage, valueSerializer::apply, valueDeserializer::apply) } /** * Create a trie. * * @param storage The storage to use for persistence. * @param rootHash The initial root has for the trie, which should be already present in `storage`. * @param valueSerializer A function for serializing values to bytes. * @param valueDeserializer A function for deserializing values from bytes. * @param The serialized type. * @return A new merkle trie. */ @JvmStatic fun create( storage: MerkleStorage, rootHash: Bytes32, valueSerializer: Function, valueDeserializer: Function ): StoredMerklePatriciaTrie { return StoredMerklePatriciaTrie(storage, rootHash, valueSerializer::apply, valueDeserializer::apply) } } private val getVisitor = GetVisitor() private val removeVisitor = RemoveVisitor() private val storage: MerkleStorage private val nodeFactory: StoredNodeFactory private var root: Node /** * Create a trie. * * @param storage The storage to use for persistence. * @param valueSerializer A function for serializing values to bytes. * @param valueDeserializer A function for deserializing values from bytes. */ constructor( storage: MerkleStorage, valueSerializer: (V) -> Bytes, valueDeserializer: (Bytes) -> V ) : this(storage, EMPTY_TRIE_ROOT_HASH, valueSerializer, valueDeserializer) /** * Create a trie. * * @param storage The storage to use for persistence. * @param rootHash The initial root has for the trie, which should be already present in `storage`. * @param valueSerializer A function for serializing values to bytes. * @param valueDeserializer A function for deserializing values from bytes. */ constructor( storage: MerkleStorage, rootHash: Bytes32, valueSerializer: (V) -> Bytes, valueDeserializer: (Bytes) -> V ) { this.storage = storage this.nodeFactory = StoredNodeFactory(storage, valueSerializer, valueDeserializer) this.root = if (rootHash == EMPTY_TRIE_ROOT_HASH) { NullNode.instance() } else { StoredNode(nodeFactory, rootHash) } } override suspend fun get(key: Bytes): V? = root.accept(getVisitor, bytesToPath(key)).value() override suspend fun put(key: Bytes, value: V?) { if (value == null) { return remove(key) } updateRoot(root.accept(PutVisitor(nodeFactory, value), bytesToPath(key))) } override suspend fun remove(key: Bytes) = updateRoot(root.accept(removeVisitor, bytesToPath(key))) override fun rootHash(): Bytes32 = root.hash() /** * Forces any cached trie nodes to be released, so they can be garbage collected. * * Note: nodes are already stored using [java.lang.ref.SoftReference]'s, so they will be released automatically * based on memory demands. */ fun clearCache() { val currentRoot = root if (currentRoot is StoredNode<*>) { currentRoot.unload() } } private suspend fun updateRoot(newRoot: Node) { this.root = if (newRoot is StoredNode<*>) { newRoot } else { storage.put(newRoot.hash(), newRoot.rlp()) StoredNode(nodeFactory, newRoot) } } /** * @return A string representation of the object. */ override fun toString(): String { return javaClass.simpleName + "[" + rootHash() + "]" } } cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/StoredNode.kt000066400000000000000000000064641341750772100270000ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.async import net.consensys.cava.bytes.Bytes import net.consensys.cava.bytes.Bytes32 import net.consensys.cava.rlp.RLP import java.lang.ref.SoftReference import java.util.concurrent.atomic.AtomicReference internal class StoredNode : Node { private val nodeFactory: StoredNodeFactory private val hash: Bytes32 @Volatile private var loaded: SoftReference>? = null private val loader = AtomicReference>>() constructor(nodeFactory: StoredNodeFactory, hash: Bytes32) { this.nodeFactory = nodeFactory this.hash = hash } constructor(nodeFactory: StoredNodeFactory, node: Node) { this.nodeFactory = nodeFactory this.hash = node.hash() this.loaded = SoftReference(node) } override suspend fun accept(visitor: NodeVisitor, path: Bytes): Node { val node = load() val resultNode = node.accept(visitor, path) if (node === resultNode) { return this } return resultNode } override suspend fun path(): Bytes = load().path() override suspend fun value(): V? = load().value() // Getting the rlp representation is only needed when persisting a concrete node override fun rlp(): Bytes = throw UnsupportedOperationException() override fun rlpRef(): Bytes { val loadedNode = loaded?.get() if (loadedNode != null) { return loadedNode.rlpRef() } // If this node was stored, then it must have a rlp larger than a hash return RLP.encodeValue(hash) } override fun hash(): Bytes32 = hash override suspend fun replacePath(path: Bytes): Node = load().replacePath(path) private suspend fun load(): Node { val loadedNode = loaded?.get() if (loadedNode != null) { return loadedNode } val deferred: Deferred> = GlobalScope.async(Dispatchers.IO, start = CoroutineStart.LAZY) { val node = nodeFactory.retrieve(hash) loaded = SoftReference(node) loader.set(null) node } while (!loader.compareAndSet(null, deferred)) { // already loading val prevDeferred = loader.get() if (prevDeferred != null) { return prevDeferred.await() } } // we've set the loader // check for a loaded node again, in case a loader just completed val node = loaded?.get() if (node != null) { // remove our loader, if it's still set loader.compareAndSet(deferred, null) return node } return deferred.await() } fun unload() { val deferred: Deferred>? = loader.get() deferred?.cancel() loaded = null } } cava-0.6.0/merkle-trie/src/main/kotlin/net/consensys/cava/trie/StoredNodeFactory.kt000066400000000000000000000157611341750772100303300ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import net.consensys.cava.bytes.Bytes import net.consensys.cava.bytes.Bytes32 import net.consensys.cava.rlp.RLP import net.consensys.cava.rlp.RLPException import net.consensys.cava.rlp.RLPReader import java.util.Collections internal class StoredNodeFactory( private val storage: MerkleStorage, private val valueSerializer: (V) -> Bytes, private val valueDeserializer: (Bytes) -> V ) : NodeFactory { private val nullNode: NullNode = NullNode.instance() override suspend fun createExtension(path: Bytes, child: Node): Node { return maybeStore(ExtensionNode(path, child, this)) } override suspend fun createBranch(leftIndex: Byte, left: Node, rightIndex: Byte, right: Node): Node { assert(leftIndex <= BranchNode.RADIX) assert(rightIndex <= BranchNode.RADIX) assert(leftIndex != rightIndex) val children: MutableList> = Collections.nCopies(BranchNode.RADIX, nullNode).toMutableList() return when { leftIndex.toInt() == BranchNode.RADIX -> { children[rightIndex.toInt()] = right createBranch(children, left.value()) } rightIndex.toInt() == BranchNode.RADIX -> { children[leftIndex.toInt()] = left createBranch(children, right.value()) } else -> { children[leftIndex.toInt()] = left children[rightIndex.toInt()] = right createBranch(children, null) } } } override suspend fun createBranch(newChildren: List>, value: V?): Node { return maybeStore(BranchNode(newChildren, value, this, valueSerializer)) } override suspend fun createLeaf(path: Bytes, value: V): Node { return maybeStore(LeafNode(path, value, this, valueSerializer)) } private suspend fun maybeStore(node: Node): Node { val nodeRLP = node.rlp() if (nodeRLP.size() < 32) { return node } storage.put(node.hash(), node.rlp()) return StoredNode(this, node) } internal suspend fun retrieve(hash: Bytes32): Node { val bytes = storage.get(hash) ?: throw MerkleStorageException("Missing value for hash $hash") val node = decode(bytes) { "Invalid RLP value for hash $hash" } assert(hash == node.hash()) { "Node hash ${node.hash()} not equal to expected $hash" } return node } private fun decode(rlp: Bytes, errMessage: () -> String): Node { try { return RLP.decode(rlp) { reader -> decode(reader, errMessage) } } catch (ex: RLPException) { throw MerkleStorageException(errMessage(), ex) } } private fun decode(nodeRLPs: RLPReader, errMessage: () -> String): Node { return nodeRLPs.readList { listReader -> val remaining = listReader.remaining() when (remaining) { 1 -> decodeNull(listReader, errMessage) 2 -> { val encodedPath = listReader.readValue() val path: Bytes try { path = CompactEncoding.decode(encodedPath) } catch (e: IllegalArgumentException) { throw MerkleStorageException(errMessage() + ": invalid path " + encodedPath, e) } val size = path.size() if (size > 0 && path.get(size - 1) == CompactEncoding.LEAF_TERMINATOR) { decodeLeaf(path, listReader, errMessage) } else { decodeExtension(path, listReader, errMessage) } } BranchNode.RADIX + 1 -> decodeBranch(listReader, errMessage) else -> throw MerkleStorageException(errMessage() + ": invalid list size " + remaining) } } } private fun decodeExtension(path: Bytes, valueRlp: RLPReader, errMessage: () -> String): Node { val child = if (valueRlp.nextIsList()) { decode(valueRlp, errMessage) } else { val childHash: Bytes32 try { childHash = Bytes32.wrap(valueRlp.readValue()) } catch (e: RLPException) { throw MerkleStorageException(errMessage() + ": invalid extension target") } catch (e: IllegalArgumentException) { throw MerkleStorageException(errMessage() + ": invalid extension target") } StoredNode(this, childHash) } return ExtensionNode(path, child, this) } private fun decodeBranch(nodeRLPs: RLPReader, errMessage: () -> String): BranchNode { val children = ArrayList>(BranchNode.RADIX.toInt()) for (i in 0 until BranchNode.RADIX) { val updatedChild = when { nodeRLPs.nextIsEmpty() -> { nodeRLPs.readValue() nullNode } nodeRLPs.nextIsList() -> { val child = decode(nodeRLPs, errMessage) StoredNode(this, child) } else -> { val childHash: Bytes32 try { childHash = Bytes32.wrap(nodeRLPs.readValue()) } catch (e: RLPException) { throw MerkleStorageException(errMessage() + ": invalid branch child " + i) } catch (e: IllegalArgumentException) { throw MerkleStorageException(errMessage() + ": invalid branch child " + i) } StoredNode(this, childHash) } } children.add(updatedChild) } val value = if (nodeRLPs.nextIsEmpty()) { nodeRLPs.readValue() null } else { decodeValue(nodeRLPs, errMessage) } return BranchNode(children, value, this, valueSerializer) } private fun decodeLeaf(path: Bytes, valueRlp: RLPReader, errMessage: () -> String): LeafNode { if (valueRlp.nextIsEmpty()) { throw MerkleStorageException(errMessage() + ": leaf has null value") } val value = decodeValue(valueRlp, errMessage) return LeafNode(path, value, this, valueSerializer) } private fun decodeNull(nodeRLPs: RLPReader, errMessage: () -> String): NullNode { if (!nodeRLPs.nextIsEmpty()) { throw MerkleStorageException(errMessage() + ": list size 1 but not null") } nodeRLPs.readValue() return nullNode } private fun decodeValue(valueRlp: RLPReader, errMessage: () -> String): V { val bytes: Bytes try { bytes = valueRlp.readValue() } catch (ex: RLPException) { throw MerkleStorageException(errMessage() + ": failed decoding value rlp " + valueRlp, ex) } return deserializeValue(errMessage, bytes) } private fun deserializeValue(errMessage: () -> String, bytes: Bytes): V { try { return valueDeserializer(bytes) } catch (ex: IllegalArgumentException) { throw MerkleStorageException(errMessage() + ": failed deserializing value " + bytes, ex) } } } cava-0.6.0/merkle-trie/src/test/000077500000000000000000000000001341750772100164245ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/test/java/000077500000000000000000000000001341750772100173455ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/test/java/net/000077500000000000000000000000001341750772100201335ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/test/java/net/consensys/000077500000000000000000000000001341750772100221575ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100230715ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/test/java/net/consensys/cava/trie/000077500000000000000000000000001341750772100240345ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/test/java/net/consensys/cava/trie/CompactEncodingTest.java000066400000000000000000000040041341750772100305720ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie; import static org.junit.jupiter.api.Assertions.assertEquals; import net.consensys.cava.bytes.Bytes; import org.junit.jupiter.api.Test; class CompactEncodingTest { @Test void bytesToPath() { Bytes path = CompactEncoding.bytesToPath(Bytes.of(0xab, 0xcd, 0xff)); assertEquals(Bytes.of(0xa, 0xb, 0xc, 0xd, 0xf, 0xf, 0x10), path); } @Test void encodePath() { assertEquals(Bytes.of(0x11, 0x23, 0x45), CompactEncoding.encode(Bytes.of(0x01, 0x02, 0x03, 0x04, 0x05))); assertEquals( Bytes.of(0x00, 0x01, 0x23, 0x45), CompactEncoding.encode(Bytes.of(0x00, 0x01, 0x02, 0x03, 0x04, 0x05))); assertEquals( Bytes.of(0x20, 0x0f, 0x1c, 0xb8), CompactEncoding.encode(Bytes.of(0x00, 0x0f, 0x01, 0x0c, 0x0b, 0x08, 0x10))); assertEquals(Bytes.of(0x3f, 0x1c, 0xb8), CompactEncoding.encode(Bytes.of(0x0f, 0x01, 0x0c, 0x0b, 0x08, 0x10))); } @Test void decode() { assertEquals(Bytes.of(0x01, 0x02, 0x03, 0x04, 0x05), CompactEncoding.decode(Bytes.of(0x11, 0x23, 0x45))); assertEquals( Bytes.of(0x00, 0x01, 0x02, 0x03, 0x04, 0x05), CompactEncoding.decode(Bytes.of(0x00, 0x01, 0x23, 0x45))); assertEquals( Bytes.of(0x00, 0x0f, 0x01, 0x0c, 0x0b, 0x08, 0x10), CompactEncoding.decode(Bytes.of(0x20, 0x0f, 0x1c, 0xb8))); assertEquals(Bytes.of(0x0f, 0x01, 0x0c, 0x0b, 0x08, 0x10), CompactEncoding.decode(Bytes.of(0x3f, 0x1c, 0xb8))); } } cava-0.6.0/merkle-trie/src/test/java/net/consensys/cava/trie/MerklePatriciaTrieJavaTest.java000066400000000000000000000167231341750772100320720ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNull; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import net.consensys.cava.junit.BouncyCastleExtension; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(BouncyCastleExtension.class) class MerklePatriciaTrieJavaTest { private MerkleTrie trie; @BeforeEach void setup() { trie = MerklePatriciaTrie.storingStrings(); } @Test void testEmptyTreeReturnsEmpty() throws Exception { assertNull(trie.getAsync(Bytes.EMPTY).get()); } @Test void testEmptyTreeHasKnownRootHash() { assertEquals("0x56E81F171BCC55A6FF8345E692C0F86E5B48E01B996CADC001622FB5E363B421", trie.rootHash().toString()); } @Test void testDeletesEntryUpdateWithNull() throws Exception { final Bytes key = Bytes.of(1); trie.putAsync(key, "value1").join(); trie.putAsync(key, null).join(); assertNull(trie.getAsync(key).get()); } @Test void testReplaceSingleValue() throws Exception { final Bytes key = Bytes.of(1); trie.putAsync(key, "value1").join(); assertEquals("value1", trie.getAsync(key).get()); trie.putAsync(key, "value2").join(); assertEquals("value2", trie.getAsync(key).get()); } @Test void testHashChangesWhenSingleValueReplaced() throws Exception { final Bytes key = Bytes.of(1); trie.putAsync(key, "value1").join(); final Bytes32 hash1 = trie.rootHash(); trie.putAsync(key, "value2").join(); final Bytes32 hash2 = trie.rootHash(); assertNotEquals(hash2, hash1); trie.putAsync(key, "value1").join(); assertEquals(hash1, trie.rootHash()); } @Test void testReadPastLeaf() throws Exception { final Bytes key1 = Bytes.of(1); final Bytes key2 = Bytes.of(1, 3); trie.putAsync(key1, "value").join(); assertNull(trie.getAsync(key2).get()); } @Test void testBranchValue() throws Exception { final Bytes key1 = Bytes.of(1); final Bytes key2 = Bytes.of(16); trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); assertEquals("value1", trie.getAsync(key1).get()); assertEquals("value2", trie.getAsync(key2).get()); } @Test void testReadPastBranch() throws Exception { final Bytes key1 = Bytes.of(12); final Bytes key2 = Bytes.of(12, 54); final Bytes key3 = Bytes.of(3); trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); assertNull(trie.getAsync(key3).get()); } @Test void testBranchWithValue() throws Exception { final Bytes key1 = Bytes.of(5); final Bytes key2 = Bytes.EMPTY; trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); assertEquals("value1", trie.getAsync(key1).get()); assertEquals("value2", trie.getAsync(key2).get()); } @Test void testExtendAndBranch() throws Exception { final Bytes key1 = Bytes.of(1, 5, 9); final Bytes key2 = Bytes.of(1, 5, 2); trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); assertEquals("value1", trie.getAsync(key1).get()); assertEquals("value2", trie.getAsync(key2).get()); assertNull(trie.getAsync(Bytes.of(1, 4)).get()); } @Test void testBranchFromTopOfExtend() throws Exception { final Bytes key1 = Bytes.of(0xFE, 1); final Bytes key2 = Bytes.of(0xFE, 2); final Bytes key3 = Bytes.of(0xE1, 1); trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); trie.putAsync(key3, "value3").join(); assertEquals("value1", trie.getAsync(key1).get()); assertEquals("value2", trie.getAsync(key2).get()); assertEquals("value3", trie.getAsync(key3).get()); assertNull(trie.getAsync(Bytes.of(1, 4)).get()); assertNull(trie.getAsync(Bytes.of(2, 4)).get()); assertNull(trie.getAsync(Bytes.of(3)).get()); } @Test void testSplitBranchExtension() throws Exception { final Bytes key1 = Bytes.of(1, 5, 9); final Bytes key2 = Bytes.of(1, 5, 2); final Bytes key3 = Bytes.of(1, 9, 1); trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); trie.putAsync(key3, "value3").join(); assertEquals("value1", trie.getAsync(key1).get()); assertEquals("value2", trie.getAsync(key2).get()); assertEquals("value3", trie.getAsync(key3).get()); } @Test void testReplaceBranchChild() throws Exception { final Bytes key1 = Bytes.of(0); final Bytes key2 = Bytes.of(1); trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); assertEquals("value1", trie.getAsync(key1).get()); assertEquals("value2", trie.getAsync(key2).get()); trie.putAsync(key1, "value3").join(); assertEquals("value3", trie.getAsync(key1).get()); assertEquals("value2", trie.getAsync(key2).get()); } @Test void testInlineBranchInBranch() throws Exception { final Bytes key1 = Bytes.of(0); final Bytes key2 = Bytes.of(1); final Bytes key3 = Bytes.of(2); final Bytes key4 = Bytes.of(0, 0); final Bytes key5 = Bytes.of(0, 1); trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); trie.putAsync(key3, "value3").join(); trie.putAsync(key4, "value4").join(); trie.putAsync(key5, "value5").join(); trie.removeAsync(key2).join(); trie.removeAsync(key3).join(); assertEquals("value1", trie.getAsync(key1).get()); assertNull(trie.getAsync(key2).get()); assertNull(trie.getAsync(key3).get()); assertEquals("value4", trie.getAsync(key4).get()); assertEquals("value5", trie.getAsync(key5).get()); } @Test void testRemoveNodeInBranchExtensionHasNoEffect() throws Exception { final Bytes key1 = Bytes.of(1, 5, 9); final Bytes key2 = Bytes.of(1, 5, 2); trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); final Bytes hash = trie.rootHash(); trie.removeAsync(Bytes.of(1, 4)).join(); assertEquals(hash, trie.rootHash()); } @Test void testHashChangesWhenValueChanged() throws Exception { final Bytes key1 = Bytes.of(1, 5, 8, 9); final Bytes key2 = Bytes.of(1, 6, 1, 2); final Bytes key3 = Bytes.of(1, 6, 1, 3); trie.putAsync(key1, "value1").join(); final Bytes32 hash1 = trie.rootHash(); trie.putAsync(key2, "value2").join(); trie.putAsync(key3, "value3").join(); final Bytes32 hash2 = trie.rootHash(); assertNotEquals(hash2, hash1); trie.putAsync(key1, "value4").join(); final Bytes32 hash3 = trie.rootHash(); assertNotEquals(hash3, hash1); assertNotEquals(hash3, hash2); trie.putAsync(key1, "value1").join(); assertEquals(hash2, trie.rootHash()); trie.removeAsync(key2).join(); trie.removeAsync(key3).join(); assertEquals(hash1, trie.rootHash()); } } cava-0.6.0/merkle-trie/src/test/java/net/consensys/cava/trie/MerklePatriciaTriePerformanceTest.java000066400000000000000000000073701341750772100334500ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import net.consensys.cava.concurrent.AsyncCompletion; import net.consensys.cava.concurrent.AsyncResult; import net.consensys.cava.concurrent.CompletableAsyncCompletion; import net.consensys.cava.junit.BouncyCastleExtension; import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(BouncyCastleExtension.class) class MerklePatriciaTriePerformanceTest { private static final SecureRandom secureRandom = new SecureRandom(); private Bytes createRandomBytes() { Bytes bytes = Bytes.wrap(new byte[32]); secureRandom.nextBytes(bytes.toArrayUnsafe()); return bytes; } @Test @Disabled("Expensive test worth running on a developer machine") void insertOneMillionRecords() throws Exception { ExecutorService threadPool = Executors.newFixedThreadPool(16); Map storage = new ConcurrentHashMap<>(); AsyncMerkleStorage merkleStorage = new AsyncMerkleStorage() { @Override public @NotNull AsyncResult getAsync(@NotNull Bytes32 hash) { return AsyncResult.completed(storage.get(hash)); } @Override public @NotNull AsyncCompletion putAsync(@NotNull Bytes32 hash, @NotNull Bytes content) { CompletableAsyncCompletion completion = AsyncCompletion.incomplete(); threadPool.submit(() -> { storage.put(hash, content); completion.complete(); }); return completion; } }; StoredMerklePatriciaTrie trie = StoredMerklePatriciaTrie.storingStrings(merkleStorage); List allKeys = new ArrayList<>(); long beforeInsertion = System.nanoTime(); AsyncCompletion.allOf(IntStream.range(0, 1000000).mapToObj(i -> { Bytes key = createRandomBytes(); allKeys.add(key); AsyncCompletion completion = trie.putAsync(key, UUID.randomUUID().toString()); if (i % 1000 == 0) { return completion.thenRun( () -> System.out.println( String.format("%020d", (System.nanoTime() - beforeInsertion) / 1000) + " ms Record #" + i + " ingested")); } else { return completion; } })).join(2, TimeUnit.MINUTES); long afterInsertion = System.nanoTime(); System.out.println("Insertion of records done in " + (afterInsertion - beforeInsertion) + " ns"); long recordsRead = System.nanoTime(); for (int i = 0; i < allKeys.size(); i += 10) { trie.getAsync(allKeys.get(i)).get(1, TimeUnit.SECONDS); if (i % 100 == 0) { System.out.println("Read 100 records in " + (System.nanoTime() - recordsRead) + " ns"); recordsRead = System.nanoTime(); } } } } cava-0.6.0/merkle-trie/src/test/java/net/consensys/cava/trie/StoredMerklePatriciaTrieJavaTest.java000066400000000000000000000226161341750772100332510ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie; import static org.junit.jupiter.api.Assertions.*; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import net.consensys.cava.concurrent.AsyncCompletion; import net.consensys.cava.concurrent.AsyncResult; import net.consensys.cava.junit.BouncyCastleExtension; import java.util.HashMap; import java.util.Map; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(BouncyCastleExtension.class) class StoredMerklePatriciaTrieJavaTest { private MerkleStorage merkleStorage; private StoredMerklePatriciaTrie trie; @BeforeEach void setup() { Map storage = new HashMap<>(); merkleStorage = new AsyncMerkleStorage() { @Override public @NotNull AsyncResult getAsync(@NotNull Bytes32 hash) { return AsyncResult.completed(storage.get(hash)); } @Override public @NotNull AsyncCompletion putAsync(@NotNull Bytes32 hash, @NotNull Bytes content) { storage.put(hash, content); return AsyncCompletion.completed(); } }; trie = StoredMerklePatriciaTrie.storingStrings(merkleStorage); } @Test void testEmptyTreeReturnsEmpty() throws Exception { assertNull(trie.getAsync(Bytes.EMPTY).get()); } @Test void testEmptyTreeHasKnownRootHash() { assertEquals("0x56E81F171BCC55A6FF8345E692C0F86E5B48E01B996CADC001622FB5E363B421", trie.rootHash().toString()); } @Test void testDeletesEntryUpdateWithNull() throws Exception { final Bytes key = Bytes.of(1); trie.putAsync(key, "value1").join(); trie.putAsync(key, null).join(); assertNull(trie.getAsync(key).get()); } @Test void testReplaceSingleValue() throws Exception { final Bytes key = Bytes.of(1); trie.putAsync(key, "value1").join(); assertEquals("value1", trie.getAsync(key).get()); trie.putAsync(key, "value2").join(); assertEquals("value2", trie.getAsync(key).get()); } @Test void testHashChangesWhenSingleValueReplaced() throws Exception { final Bytes key = Bytes.of(1); trie.putAsync(key, "value1").join(); final Bytes32 hash1 = trie.rootHash(); trie.putAsync(key, "value2").join(); final Bytes32 hash2 = trie.rootHash(); assertNotEquals(hash2, hash1); trie.putAsync(key, "value1").join(); assertEquals(hash1, trie.rootHash()); } @Test void testReadPastLeaf() throws Exception { final Bytes key1 = Bytes.of(1); final Bytes key2 = Bytes.of(1, 3); trie.putAsync(key1, "value").join(); assertNull(trie.getAsync(key2).get()); } @Test void testBranchValue() throws Exception { final Bytes key1 = Bytes.of(1); final Bytes key2 = Bytes.of(16); trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); assertEquals("value1", trie.getAsync(key1).get()); assertEquals("value2", trie.getAsync(key2).get()); } @Test void testReadPastBranch() throws Exception { final Bytes key1 = Bytes.of(12); final Bytes key2 = Bytes.of(12, 54); final Bytes key3 = Bytes.of(3); trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); assertNull(trie.getAsync(key3).get()); } @Test void testBranchWithValue() throws Exception { final Bytes key1 = Bytes.of(5); final Bytes key2 = Bytes.EMPTY; trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); assertEquals("value1", trie.getAsync(key1).get()); assertEquals("value2", trie.getAsync(key2).get()); } @Test void testExtendAndBranch() throws Exception { final Bytes key1 = Bytes.of(1, 5, 9); final Bytes key2 = Bytes.of(1, 5, 2); trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); assertEquals("value1", trie.getAsync(key1).get()); assertEquals("value2", trie.getAsync(key2).get()); assertNull(trie.getAsync(Bytes.of(1, 4)).get()); } @Test void testBranchFromTopOfExtend() throws Exception { final Bytes key1 = Bytes.of(0xFE, 1); final Bytes key2 = Bytes.of(0xFE, 2); final Bytes key3 = Bytes.of(0xE1, 1); trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); trie.putAsync(key3, "value3").join(); assertEquals("value1", trie.getAsync(key1).get()); assertEquals("value2", trie.getAsync(key2).get()); assertEquals("value3", trie.getAsync(key3).get()); assertNull(trie.getAsync(Bytes.of(1, 4)).get()); assertNull(trie.getAsync(Bytes.of(2, 4)).get()); assertNull(trie.getAsync(Bytes.of(3)).get()); } @Test void testSplitBranchExtension() throws Exception { final Bytes key1 = Bytes.of(1, 5, 9); final Bytes key2 = Bytes.of(1, 5, 2); final Bytes key3 = Bytes.of(1, 9, 1); trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); trie.putAsync(key3, "value3").join(); assertEquals("value1", trie.getAsync(key1).get()); assertEquals("value2", trie.getAsync(key2).get()); assertEquals("value3", trie.getAsync(key3).get()); } @Test void testReplaceBranchChild() throws Exception { final Bytes key1 = Bytes.of(0); final Bytes key2 = Bytes.of(1); trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); assertEquals("value1", trie.getAsync(key1).get()); assertEquals("value2", trie.getAsync(key2).get()); trie.putAsync(key1, "value3").join(); assertEquals("value3", trie.getAsync(key1).get()); assertEquals("value2", trie.getAsync(key2).get()); } @Test void testInlineBranchInBranch() throws Exception { final Bytes key1 = Bytes.of(0); final Bytes key2 = Bytes.of(1); final Bytes key3 = Bytes.of(2); final Bytes key4 = Bytes.of(0, 0); final Bytes key5 = Bytes.of(0, 1); trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); trie.putAsync(key3, "value3").join(); trie.putAsync(key4, "value4").join(); trie.putAsync(key5, "value5").join(); trie.removeAsync(key2).join(); trie.removeAsync(key3).join(); assertEquals("value1", trie.getAsync(key1).get()); assertNull(trie.getAsync(key2).get()); assertNull(trie.getAsync(key3).get()); assertEquals("value4", trie.getAsync(key4).get()); assertEquals("value5", trie.getAsync(key5).get()); } @Test void testRemoveNodeInBranchExtensionHasNoEffect() throws Exception { final Bytes key1 = Bytes.of(1, 5, 9); final Bytes key2 = Bytes.of(1, 5, 2); trie.putAsync(key1, "value1").join(); trie.putAsync(key2, "value2").join(); final Bytes hash = trie.rootHash(); trie.removeAsync(Bytes.of(1, 4)).join(); assertEquals(hash, trie.rootHash()); } @Test void testHashChangesWhenValueChanged() throws Exception { final Bytes key1 = Bytes.of(1, 5, 8, 9); final Bytes key2 = Bytes.of(1, 6, 1, 2); final Bytes key3 = Bytes.of(1, 6, 1, 3); trie.putAsync(key1, "value1").join(); final Bytes32 hash1 = trie.rootHash(); trie.putAsync(key2, "value2").join(); trie.putAsync(key3, "value3").join(); final Bytes32 hash2 = trie.rootHash(); assertNotEquals(hash2, hash1); trie.putAsync(key1, "value4").join(); final Bytes32 hash3 = trie.rootHash(); assertNotEquals(hash3, hash1); assertNotEquals(hash3, hash2); trie.clearCache(); trie.putAsync(key1, "value1").join(); assertEquals(hash2, trie.rootHash()); trie.removeAsync(key2).join(); trie.removeAsync(key3).join(); assertEquals(hash1, trie.rootHash()); } @Test void testCanReloadTrieFromHash() throws Exception { final Bytes key1 = Bytes.of(1, 5, 8, 9); final Bytes key2 = Bytes.of(1, 6, 1, 2); final Bytes key3 = Bytes.of(1, 6, 1, 3); trie.putAsync(key1, "value1").join(); final Bytes32 hash1 = trie.rootHash(); trie.putAsync(key2, "value2").join(); trie.putAsync(key3, "value3").join(); final Bytes32 hash2 = trie.rootHash(); assertNotEquals(hash2, hash1); trie.putAsync(key1, "value4").join(); final Bytes32 hash3 = trie.rootHash(); assertNotEquals(hash3, hash1); assertNotEquals(hash3, hash2); assertEquals("value4", trie.getAsync(key1).get()); trie = StoredMerklePatriciaTrie.storingStrings(merkleStorage, hash1); assertEquals("value1", trie.getAsync(key1).get()); assertNull(trie.getAsync(key2).get()); assertNull(trie.getAsync(key3).get()); trie = StoredMerklePatriciaTrie.storingStrings(merkleStorage, hash2); assertEquals("value1", trie.getAsync(key1).get()); assertEquals("value2", trie.getAsync(key2).get()); assertEquals("value3", trie.getAsync(key3).get()); trie = StoredMerklePatriciaTrie.storingStrings(merkleStorage, hash3); assertEquals("value4", trie.getAsync(key1).get()); assertEquals("value2", trie.getAsync(key2).get()); assertEquals("value3", trie.getAsync(key3).get()); } } cava-0.6.0/merkle-trie/src/test/kotlin/000077500000000000000000000000001341750772100177245ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/test/kotlin/net/000077500000000000000000000000001341750772100205125ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/test/kotlin/net/consensys/000077500000000000000000000000001341750772100225365ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/test/kotlin/net/consensys/cava/000077500000000000000000000000001341750772100234505ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/test/kotlin/net/consensys/cava/trie/000077500000000000000000000000001341750772100244135ustar00rootroot00000000000000cava-0.6.0/merkle-trie/src/test/kotlin/net/consensys/cava/trie/MerklePatriciaTrieKotlinTest.kt000066400000000000000000000150031341750772100325130ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import kotlinx.coroutines.runBlocking import net.consensys.cava.bytes.Bytes import net.consensys.cava.junit.BouncyCastleExtension import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @ExtendWith(BouncyCastleExtension::class) internal class MerklePatriciaTrieKotlinTest { private lateinit var trie: MerkleTrie @BeforeEach fun setup() { trie = MerklePatriciaTrie.storingStrings() } @Test fun testEmptyTreeReturnsEmpty() { runBlocking { assertNull(trie.get(Bytes.EMPTY)) } } @Test fun testEmptyTreeHasKnownRootHash() { assertEquals("0x56E81F171BCC55A6FF8345E692C0F86E5B48E01B996CADC001622FB5E363B421", trie.rootHash().toString()) } @Test fun testDeletesEntryUpdateWithNull() { val key = Bytes.of(1) runBlocking { trie.put(key, "value1") trie.put(key, null) assertNull(trie.get(key)) } } @Test fun testReplaceSingleValue() { val key = Bytes.of(1) val value1 = "value1" val value2 = "value2" runBlocking { trie.put(key, value1) assertEquals(value1, trie.get(key)) trie.put(key, value2) assertEquals(value2, trie.get(key)) } } @Test fun testHashChangesWhenSingleValueReplaced() { val key = Bytes.of(1) runBlocking { trie.put(key, "value1") val hash1 = trie.rootHash() trie.put(key, "value2") val hash2 = trie.rootHash() assertNotEquals(hash2, hash1) trie.put(key, "value1") assertEquals(hash1, trie.rootHash()) } } @Test fun testReadPastLeaf() { val key1 = Bytes.of(1) val key2 = Bytes.of(1, 3) runBlocking { trie.put(key1, "value") assertNull(trie.get(key2)) } } @Test fun testBranchValue() { val key1 = Bytes.of(1) val key2 = Bytes.of(16) runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") assertEquals("value1", trie.get(key1)) assertEquals("value2", trie.get(key2)) } } @Test fun testReadPastBranch() { val key1 = Bytes.of(12) val key2 = Bytes.of(12, 54) val key3 = Bytes.of(3) runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") assertNull(trie.get(key3)) } } @Test fun testBranchWithValue() { val key1 = Bytes.of(5) val key2 = Bytes.EMPTY runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") assertEquals("value1", trie.get(key1)) assertEquals("value2", trie.get(key2)) } } @Test fun testExtendAndBranch() { val key1 = Bytes.of(1, 5, 9) val key2 = Bytes.of(1, 5, 2) val key3 = Bytes.of(1, 4) runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") assertEquals("value1", trie.get(key1)) assertEquals("value2", trie.get(key2)) assertNull(trie.get(key3)) } } @Test fun testBranchFromTopOfExtend() { val key1 = Bytes.of(0xFE, 1) val key2 = Bytes.of(0xFE, 2) val key3 = Bytes.of(0xE1, 1) runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") trie.put(key3, "value3") assertEquals("value1", trie.get(key1)) assertEquals("value2", trie.get(key2)) assertEquals("value3", trie.get(key3)) assertNull(trie.get(Bytes.of(1, 4))) assertNull(trie.get(Bytes.of(2, 4))) assertNull(trie.get(Bytes.of(3))) } } @Test fun testSplitBranchExtension() { val key1 = Bytes.of(1, 5, 9) val key2 = Bytes.of(1, 5, 2) val key3 = Bytes.of(1, 9, 1) runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") trie.put(key3, "value3") assertEquals("value1", trie.get(key1)) assertEquals("value2", trie.get(key2)) assertEquals("value3", trie.get(key3)) } } @Test fun testReplaceBranchChild() { val key1 = Bytes.of(0) val key2 = Bytes.of(1) runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") assertEquals("value1", trie.get(key1)) assertEquals("value2", trie.get(key2)) trie.put(key1, "value3") assertEquals("value3", trie.get(key1)) assertEquals("value2", trie.get(key2)) } } @Test fun testInlineBranchInBranch() { val key1 = Bytes.of(0) val key2 = Bytes.of(1) val key3 = Bytes.of(2) val key4 = Bytes.of(0, 0) val key5 = Bytes.of(0, 1) runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") trie.put(key3, "value3") trie.put(key4, "value4") trie.put(key5, "value5") trie.remove(key2) trie.remove(key3) assertEquals("value1", trie.get(key1)) assertNull(trie.get(key2)) assertNull(trie.get(key3)) assertEquals("value4", trie.get(key4)) assertEquals("value5", trie.get(key5)) } } @Test fun testRemoveNodeInBranchExtensionHasNoEffect() { val key1 = Bytes.of(1, 5, 9) val key2 = Bytes.of(1, 5, 2) runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") val hash = trie.rootHash() trie.remove(Bytes.of(1, 4)) assertEquals(hash, trie.rootHash()) } } @Test fun testHashChangesWhenValueChanged() { val key1 = Bytes.of(1, 5, 8, 9) val key2 = Bytes.of(1, 6, 1, 2) val key3 = Bytes.of(1, 6, 1, 3) runBlocking { trie.put(key1, "value1") val hash1 = trie.rootHash() trie.put(key2, "value2") trie.put(key3, "value3") val hash2 = trie.rootHash() assertNotEquals(hash2, hash1) trie.put(key1, "value4") val hash3 = trie.rootHash() assertNotEquals(hash3, hash1) assertNotEquals(hash3, hash2) trie.put(key1, "value1") assertEquals(hash2, trie.rootHash()) trie.remove(key2) trie.remove(key3) assertEquals(hash1, trie.rootHash()) } } } cava-0.6.0/merkle-trie/src/test/kotlin/net/consensys/cava/trie/StoredMerklePatriciaTrieKotlinTest.kt000066400000000000000000000202571341750772100337030ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.trie import kotlinx.coroutines.runBlocking import net.consensys.cava.bytes.Bytes import net.consensys.cava.bytes.Bytes32 import net.consensys.cava.junit.BouncyCastleExtension import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @ExtendWith(BouncyCastleExtension::class) internal class StoredMerklePatriciaTrieKotlinTest { private lateinit var storage: MutableMap private val merkleStorage = object : MerkleStorage { override suspend fun get(hash: Bytes32): Bytes? = storage[hash] override suspend fun put(hash: Bytes32, content: Bytes) { storage[hash] = content } } private lateinit var trie: StoredMerklePatriciaTrie @BeforeEach fun setup() { storage = mutableMapOf() trie = StoredMerklePatriciaTrie.storingStrings(merkleStorage) } @Test fun testEmptyTreeReturnsEmpty() { runBlocking { assertNull(trie.get(Bytes.EMPTY)) } } @Test fun testEmptyTreeHasKnownRootHash() { assertEquals("0x56E81F171BCC55A6FF8345E692C0F86E5B48E01B996CADC001622FB5E363B421", trie.rootHash().toString()) } @Test fun testDeletesEntryUpdateWithNull() { val key = Bytes.of(1) runBlocking { trie.put(key, "value1") trie.put(key, null) assertNull(trie.get(key)) } } @Test fun testReplaceSingleValue() { val key = Bytes.of(1) val value1 = "value1" val value2 = "value2" runBlocking { trie.put(key, value1) assertEquals(value1, trie.get(key)) trie.put(key, value2) assertEquals(value2, trie.get(key)) } } @Test fun testHashChangesWhenSingleValueReplaced() { val key = Bytes.of(1) runBlocking { trie.put(key, "value1") val hash1 = trie.rootHash() trie.put(key, "value2") val hash2 = trie.rootHash() assertNotEquals(hash2, hash1) trie.put(key, "value1") assertEquals(hash1, trie.rootHash()) } } @Test fun testReadPastLeaf() { val key1 = Bytes.of(1) val key2 = Bytes.of(1, 3) runBlocking { trie.put(key1, "value") assertNull(trie.get(key2)) } } @Test fun testBranchValue() { val key1 = Bytes.of(1) val key2 = Bytes.of(16) runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") assertEquals("value1", trie.get(key1)) assertEquals("value2", trie.get(key2)) } } @Test fun testReadPastBranch() { val key1 = Bytes.of(12) val key2 = Bytes.of(12, 54) val key3 = Bytes.of(3) runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") assertNull(trie.get(key3)) } } @Test fun testBranchWithValue() { val key1 = Bytes.of(5) val key2 = Bytes.EMPTY runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") assertEquals("value1", trie.get(key1)) assertEquals("value2", trie.get(key2)) } } @Test fun testExtendAndBranch() { val key1 = Bytes.of(1, 5, 9) val key2 = Bytes.of(1, 5, 2) val key3 = Bytes.of(1, 4) runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") assertEquals("value1", trie.get(key1)) assertEquals("value2", trie.get(key2)) assertNull(trie.get(key3)) } } @Test fun testBranchFromTopOfExtend() { val key1 = Bytes.of(0xFE, 1) val key2 = Bytes.of(0xFE, 2) val key3 = Bytes.of(0xE1, 1) runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") trie.put(key3, "value3") assertEquals("value1", trie.get(key1)) assertEquals("value2", trie.get(key2)) assertEquals("value3", trie.get(key3)) assertNull(trie.get(Bytes.of(1, 4))) assertNull(trie.get(Bytes.of(2, 4))) assertNull(trie.get(Bytes.of(3))) } } @Test fun testSplitBranchExtension() { val key1 = Bytes.of(1, 5, 9) val key2 = Bytes.of(1, 5, 2) val key3 = Bytes.of(1, 9, 1) runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") trie.put(key3, "value3") assertEquals("value1", trie.get(key1)) assertEquals("value2", trie.get(key2)) assertEquals("value3", trie.get(key3)) } } @Test fun testReplaceBranchChild() { val key1 = Bytes.of(0) val key2 = Bytes.of(1) runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") assertEquals("value1", trie.get(key1)) assertEquals("value2", trie.get(key2)) trie.put(key1, "value3") assertEquals("value3", trie.get(key1)) assertEquals("value2", trie.get(key2)) } } @Test fun testInlineBranchInBranch() { val key1 = Bytes.of(0) val key2 = Bytes.of(1) val key3 = Bytes.of(2) val key4 = Bytes.of(0, 0) val key5 = Bytes.of(0, 1) runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") trie.put(key3, "value3") trie.put(key4, "value4") trie.put(key5, "value5") trie.remove(key2) trie.remove(key3) assertEquals("value1", trie.get(key1)) assertNull(trie.get(key2)) assertNull(trie.get(key3)) assertEquals("value4", trie.get(key4)) assertEquals("value5", trie.get(key5)) } } @Test fun testRemoveNodeInBranchExtensionHasNoEffect() { val key1 = Bytes.of(1, 5, 9) val key2 = Bytes.of(1, 5, 2) runBlocking { trie.put(key1, "value1") trie.put(key2, "value2") val hash = trie.rootHash() trie.remove(Bytes.of(1, 4)) assertEquals(hash, trie.rootHash()) } } @Test fun testHashChangesWhenValueChanged() { val key1 = Bytes.of(1, 5, 8, 9) val key2 = Bytes.of(1, 6, 1, 2) val key3 = Bytes.of(1, 6, 1, 3) runBlocking { trie.put(key1, "value1") val hash1 = trie.rootHash() trie.put(key2, "value2") trie.put(key3, "value3") val hash2 = trie.rootHash() assertNotEquals(hash2, hash1) trie.put(key1, "value4") val hash3 = trie.rootHash() assertNotEquals(hash3, hash1) assertNotEquals(hash3, hash2) trie.clearCache() trie.put(key1, "value1") assertEquals(hash2, trie.rootHash()) trie.remove(key2) trie.remove(key3) assertEquals(hash1, trie.rootHash()) } } @Test fun testCanReloadTrieFromHash() { val key1 = Bytes.of(1, 5, 8, 9) val key2 = Bytes.of(1, 6, 1, 2) val key3 = Bytes.of(1, 6, 1, 3) runBlocking { trie.put(key1, "value1") } val hash1 = trie.rootHash() runBlocking { trie.put(key2, "value2") trie.put(key3, "value3") } val hash2 = trie.rootHash() assertNotEquals(hash2, hash1) runBlocking { trie.put(key1, "value4") assertEquals("value4", trie.get(key1)) } val hash3 = trie.rootHash() assertNotEquals(hash3, hash1) assertNotEquals(hash3, hash2) val trie1 = StoredMerklePatriciaTrie.storingStrings(merkleStorage, hash1) runBlocking { assertEquals("value1", trie1.get(key1)) assertNull(trie1.get(key2)) assertNull(trie1.get(key3)) } val trie2 = StoredMerklePatriciaTrie.storingStrings(merkleStorage, hash2) runBlocking { assertEquals("value1", trie2.get(key1)) assertEquals("value2", trie2.get(key2)) assertEquals("value3", trie2.get(key3)) } val trie3 = StoredMerklePatriciaTrie.storingStrings(merkleStorage, hash3) runBlocking { assertEquals("value4", trie3.get(key1)) assertEquals("value2", trie3.get(key2)) assertEquals("value3", trie3.get(key3)) } } } cava-0.6.0/net-coroutines/000077500000000000000000000000001341750772100154145ustar00rootroot00000000000000cava-0.6.0/net-coroutines/build.gradle000066400000000000000000000006721341750772100177000ustar00rootroot00000000000000description = 'Classes and utilities for coroutine based networking.' dependencies { compile 'com.google.guava:guava' compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core' compile 'org.logl:logl-api' testCompile project(':junit') testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testCompile 'org.logl:logl-logl' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/net-coroutines/src/000077500000000000000000000000001341750772100162035ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/main/000077500000000000000000000000001341750772100171275ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/main/kotlin/000077500000000000000000000000001341750772100204275ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/main/kotlin/net/000077500000000000000000000000001341750772100212155ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/main/kotlin/net/consensys/000077500000000000000000000000001341750772100232415ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/main/kotlin/net/consensys/cava/000077500000000000000000000000001341750772100241535ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/main/kotlin/net/consensys/cava/net/000077500000000000000000000000001341750772100247415ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/main/kotlin/net/consensys/cava/net/coroutines/000077500000000000000000000000001341750772100271335ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/main/kotlin/net/consensys/cava/net/coroutines/CoroutineByteChannel.kt000066400000000000000000000344341341750772100335670ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.coroutines import java.io.IOException import java.nio.ByteBuffer import java.nio.channels.AsynchronousCloseException import java.nio.channels.ClosedByInterruptException import java.nio.channels.ClosedChannelException import java.nio.channels.GatheringByteChannel import java.nio.channels.NonReadableChannelException import java.nio.channels.NonWritableChannelException import java.nio.channels.ReadableByteChannel import java.nio.channels.ScatteringByteChannel import java.nio.channels.SelectableChannel import java.nio.channels.SelectionKey import java.nio.channels.WritableByteChannel /** * A co-routine channel that can read bytes. * * @author Chris Leishman - https://cleishm.github.io/ */ interface ReadableCoroutineByteChannel { /** * Reads a sequence of bytes from this channel into the given buffer. * * An attempt is made to read up to r bytes from the channel, where r is the number of bytes remaining in the buffer, * that is, dst.remaining(), at the moment this method is invoked. If no bytes are available, then this method * suspends until at least some bytes can be read. * * @param dst The buffer into which bytes are to be transferred. * @return The number of bytes read, possibly zero, or `-1` if the channel has reached end-of-stream. * @throws NonReadableChannelException If this channel was not opened for reading. * @throws ClosedChannelException If the channel is closed. * @throws AsynchronousCloseException If another thread closes this channel while the read operation is in progress. * @throws ClosedByInterruptException If another thread interrupts the current thread while the read operation is * in progress, thereby closing the channel and setting the current thread's interrupt status. * @throws IOException If some other I/O error occurs. */ suspend fun read(dst: ByteBuffer): Int /** * Reads a sequence of bytes from this channel into the given buffer, if any bytes are immediately available. * * An attempt is made to read up to r bytes from the channel, where r is the number of bytes remaining in the buffer, * that is, dst.remaining(), at the moment this method is invoked. * * @param dst The buffer into which bytes are to be transferred. * @return The number of bytes read, possibly zero, or `-1` if the channel has reached end-of-stream. * @throws NonReadableChannelException If this channel was not opened for reading. * @throws ClosedChannelException If the channel is closed. * @throws AsynchronousCloseException If another thread closes this channel while the read operation is in progress. * @throws ClosedByInterruptException If another thread interrupts the current thread while the read operation is * in progress, thereby closing the channel and setting the current thread's interrupt status. * @throws IOException If some other I/O error occurs. */ fun tryRead(dst: ByteBuffer): Int } internal class ReadableCoroutineByteChannelMixin( private val channel: T, private val group: CoroutineChannelGroup ) : ReadableCoroutineByteChannel where T : SelectableChannel, T : ReadableByteChannel { override suspend fun read(dst: ByteBuffer): Int { while (true) { val n = channel.read(dst) if (n != 0 || dst.remaining() == 0) { return n } // slow path group.select(channel, SelectionKey.OP_READ) } } override fun tryRead(dst: ByteBuffer): Int = channel.read(dst) } /** * A co-routine channel that can write bytes. * * @author Chris Leishman - https://cleishm.github.io/ */ interface WritableCoroutineByteChannel { /** * Writes a sequence of bytes to this channel from the given buffer. * * This method will suspend until some bytes can be written to the channel, or an error occurs. * * @param src The buffer from which bytes are to be retrieved. * @return The number of bytes written. * @throws NonWritableChannelException If this channel was not opened for writing. * @throws ClosedChannelException If the channel is closed. * @throws AsynchronousCloseException If another thread closes this channel while the write operation is in progress. * @throws ClosedByInterruptException If another thread interrupts the current thread while the write operation is * in progress, thereby closing the channel and setting the current thread's interrupt status. * @throws IOException If some other I/O error occurs. */ suspend fun write(src: ByteBuffer): Int /** * Writes a sequence of bytes to this channel from the given buffer, if the channel is ready for writing. * * @param src The buffer from which bytes are to be retrieved. * @return The number of bytes written, possibly zero. * @throws NonWritableChannelException If this channel was not opened for writing. * @throws ClosedChannelException If the channel is closed. * @throws AsynchronousCloseException If another thread closes this channel while the write operation is in progress. * @throws ClosedByInterruptException If another thread interrupts the current thread while the write operation is * in progress, thereby closing the channel and setting the current thread's interrupt status. * @throws IOException If some other I/O error occurs. */ fun tryWrite(src: ByteBuffer): Int } internal class WritableCoroutineByteChannelMixin( private val channel: T, private val group: CoroutineChannelGroup ) : WritableCoroutineByteChannel where T : SelectableChannel, T : WritableByteChannel { override suspend fun write(src: ByteBuffer): Int { while (true) { val n = channel.write(src) if (n != 0 || src.remaining() == 0) { return n } // slow path group.select(channel, SelectionKey.OP_WRITE) } } override fun tryWrite(src: ByteBuffer): Int = channel.write(src) } /** * A co-routine channel that can read and write bytes. * * @author Chris Leishman - https://cleishm.github.io/ */ interface CoroutineByteChannel : ReadableCoroutineByteChannel, WritableCoroutineByteChannel internal class CoroutineByteChannelMixin( private val channel: T, private val group: CoroutineChannelGroup ) : CoroutineByteChannel, ReadableCoroutineByteChannel by ReadableCoroutineByteChannelMixin(channel, group), WritableCoroutineByteChannel by WritableCoroutineByteChannelMixin(channel, group) where T : SelectableChannel, T : ReadableByteChannel, T : WritableByteChannel /** * A channel that can read bytes into a sequence of buffers. * * @author Chris Leishman - https://cleishm.github.io/ */ interface ScatteringCoroutineByteChannel : ReadableCoroutineByteChannel { /** * Reads a sequence of bytes from this channel into a subsequence of the given buffers. * * @param dsts The buffers into which bytes are to be transferred. * @param offset The offset within the buffer array of the first buffer into which bytes are to be transferred; * must be non-negative and no larger than `dsts.length`. * @param length The maximum number of buffers to be accessed; must be non-negative and no larger than * `dsts.length - offset`. * @return The number of bytes read, possibly zero, or `-1` if the channel has reached end-of-stream. * @throws IndexOutOfBoundsException If the preconditions on the offset and length parameters do not hold. * @throws NonReadableChannelException If this channel was not opened for reading. * @throws ClosedChannelException If the channel is closed. * @throws AsynchronousCloseException If another thread closes this channel while the read operation is in progress. * @throws ClosedByInterruptException If another thread interrupts the current thread while the read operation is * in progress, thereby closing the channel and setting the current thread's interrupt status. * @throws IOException If some other I/O error occurs. */ suspend fun read(dsts: Array, offset: Int = 0, length: Int = dsts.size): Long /** * Reads a sequence of bytes from this channel into a subsequence of the given buffers, if any are available. * * @param dsts The buffers into which bytes are to be transferred. * @param offset The offset within the buffer array of the first buffer into which bytes are to be transferred; * must be non-negative and no larger than `dsts.length`. * @param length The maximum number of buffers to be accessed; must be non-negative and no larger than * `dsts.length - offset`. * @return The number of bytes read, possibly zero, or `-1` if the channel has reached end-of-stream. * @throws IndexOutOfBoundsException If the preconditions on the offset and length parameters do not hold. * @throws NonReadableChannelException If this channel was not opened for reading. * @throws ClosedChannelException If the channel is closed. * @throws AsynchronousCloseException If another thread closes this channel while the read operation is in progress. * @throws ClosedByInterruptException If another thread interrupts the current thread while the read operation is * in progress, thereby closing the channel and setting the current thread's interrupt status. * @throws IOException If some other I/O error occurs. */ fun tryRead(dsts: Array, offset: Int = 0, length: Int = dsts.size): Long } internal class ScatteringCoroutineByteChannelMixin( private val channel: T, private val group: CoroutineChannelGroup ) : ScatteringCoroutineByteChannel, ReadableCoroutineByteChannel by ReadableCoroutineByteChannelMixin(channel, group) where T : SelectableChannel, T : ScatteringByteChannel { override suspend fun read(dsts: Array, offset: Int, length: Int): Long { while (true) { val n = channel.read(dsts, offset, length) if (n != 0L || buffersAreEmpty(dsts, offset, length)) { return n } // slow path group.select(channel, SelectionKey.OP_READ) } } override fun tryRead(dsts: Array, offset: Int, length: Int): Long = channel.read(dsts, offset, length) } /** * A channel that can write bytes from a sequence of buffers. * * @author Chris Leishman - https://cleishm.github.io/ */ interface GatheringCoroutineByteChannel : WritableCoroutineByteChannel { /** * Writes a sequence of bytes to this channel from a subsequence of the given buffers. * * This method will suspend until some bytes can be written to the channel, or an error occurs. * * @param srcs The buffers from which bytes are to be retrieved. * @param offset The offset within the buffer array of the first buffer from which bytes are to be retrieved; * must be non-negative and no larger than `srcs.length`. * @param length The maximum number of buffers to be accessed; must be non-negative and no larger than * `srcs.length - offset`. * @return The number of bytes written. * @throws IndexOutOfBoundsException If the preconditions on the offset and length parameters do not hold. * @throws NonWritableChannelException If this channel was not opened for writing. * @throws ClosedChannelException If the channel is closed. * @throws AsynchronousCloseException If another thread closes this channel while the write operation is in progress. * @throws ClosedByInterruptException If another thread interrupts the current thread while the write operation is * in progress, thereby closing the channel and setting the current thread's interrupt status. * @throws IOException If some other I/O error occurs. */ suspend fun write(srcs: Array, offset: Int = 0, length: Int = srcs.size): Long /** * Writes a sequence of bytes to this channel from a subsequence of the given buffers, if the channel is ready for writing. * * @param srcs The buffers from which bytes are to be retrieved. * @param offset The offset within the buffer array of the first buffer from which bytes are to be retrieved; * must be non-negative and no larger than `srcs.length`. * @param length The maximum number of buffers to be accessed; must be non-negative and no larger than * `srcs.length - offset`. * @return The number of bytes written, possibly zero. * @throws IndexOutOfBoundsException If the preconditions on the offset and length parameters do not hold. * @throws NonWritableChannelException If this channel was not opened for writing. * @throws ClosedChannelException If the channel is closed. * @throws AsynchronousCloseException If another thread closes this channel while the write operation is in progress. * @throws ClosedByInterruptException If another thread interrupts the current thread while the write operation is * in progress, thereby closing the channel and setting the current thread's interrupt status. * @throws IOException If some other I/O error occurs. */ fun tryWrite(srcs: Array, offset: Int = 0, length: Int = srcs.size): Long } internal class GatheringCoroutineByteChannelMixin( private val channel: T, private val group: CoroutineChannelGroup ) : GatheringCoroutineByteChannel, WritableCoroutineByteChannel by WritableCoroutineByteChannelMixin(channel, group) where T : SelectableChannel, T : GatheringByteChannel { override suspend fun write(srcs: Array, offset: Int, length: Int): Long { while (true) { val n = channel.write(srcs, offset, length) if (n != 0L || buffersAreEmpty(srcs, offset, length)) { return n } // slow path group.select(channel, SelectionKey.OP_WRITE) } } override fun tryWrite(srcs: Array, offset: Int, length: Int): Long = channel.write(srcs, offset, length) } private fun buffersAreEmpty(buffers: Array, offset: Int, length: Int): Boolean { while (offset < length) { if (buffers[offset].remaining() != 0) { return false } } return true } cava-0.6.0/net-coroutines/src/main/kotlin/net/consensys/cava/net/coroutines/CoroutineChannelGroup.kt000066400000000000000000000146701341750772100337600ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.coroutines import org.logl.LoggerProvider import java.nio.channels.Channel import java.nio.channels.SelectableChannel import java.nio.channels.ShutdownChannelGroupException import java.util.Collections import java.util.WeakHashMap import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.Executor import java.util.concurrent.Executors import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine /** * A common co-routine channel group. * * This group is created with a selector per available processors. */ val CommonCoroutineGroup: CoroutineChannelGroup = CoroutineChannelGroup.open() /** * A grouping of co-routine channels for the purpose of resource sharing. * * A co-routine channel group encapsulates the mechanics required to handle completion of suspended I/O operations * initiated on channels bound to the group. * * @author Chris Leishman - https://cleishm.github.io/ */ sealed class CoroutineChannelGroup { companion object { /** * Create a co-routine channel group. * * @param nSelectors The number of selectors that should be active in the group. Defaults to one per available * system processor. * @param executor The thread pool for running selectors. Defaults to a fixed size thread pool, with one thread * per selector. * @param loggerProvider A provider for logger instances. * @param selectTimeout The maximum time the selection operation will wait before checking for closed channels. * @param idleTimeout The minimum idle time before the selection loop of a selector exits. * @return A co-routine channel group. */ fun open( nSelectors: Int = Runtime.getRuntime().availableProcessors(), executor: Executor = Executors.newFixedThreadPool(nSelectors, CoroutineSelector.DEFAULT_THREAD_FACTORY), loggerProvider: LoggerProvider = LoggerProvider.nullProvider(), selectTimeout: Long = 1000, idleTimeout: Long = 10000 ): CoroutineChannelGroup { require(nSelectors > 0) { "nSelectors must be larger than zero" } return CoroutineSelectorChannelGroup(nSelectors, executor, loggerProvider, selectTimeout, idleTimeout) } } internal abstract fun register(channel: Channel): Boolean internal abstract fun deRegister(channel: Channel): Boolean internal abstract fun selectorFor(channel: SelectableChannel): CoroutineSelector internal suspend fun select(channel: SelectableChannel, ops: Int) { selectorFor(channel).select(channel, ops) } /** * Check if the group has been shutdown. * * @return `true` if the group is shutdown. */ abstract val isShutdown: Boolean /** * Check if the group has terminated. * * @return `true` if the group has terminated. */ abstract val isTerminated: Boolean /** * Shuts down the group. */ abstract fun shutdown() /** * Shuts down the group and closes all open channels in the group. */ abstract fun shutdownNow() /** * Suspend until the group has terminated. */ abstract suspend fun awaitTermination() } internal class CoroutineSelectorChannelGroup( nSelectors: Int, private val executor: Executor, loggerProvider: LoggerProvider, selectTimeout: Long, idleTimeout: Long ) : CoroutineChannelGroup() { private val logger = loggerProvider.getLogger(CoroutineChannelGroup::class.java) private var selectors: Array? = Array(nSelectors) { CoroutineSelector.open(executor, loggerProvider, selectTimeout, idleTimeout) } private val channels = Collections.synchronizedSet(Collections.newSetFromMap(WeakHashMap())) @Volatile override var isShutdown: Boolean = false @Volatile private var wasTerminated: Boolean = false private val pendingTermination = ConcurrentLinkedQueue>() override fun register(channel: Channel): Boolean { if (isShutdown) { throw ShutdownChannelGroupException() } val added = channels.add(channel) if (added) { logger.debug("Added channel {} to group {}", System.identityHashCode(channel), System.identityHashCode(this)) } return added } override fun deRegister(channel: Channel): Boolean { val removed = channels.remove(channel) if (removed) { logger.debug("Removed channel {} from group {}", System.identityHashCode(channel), System.identityHashCode(this)) if (isShutdown && channels.isEmpty()) { terminate() } } return removed } override fun selectorFor(channel: SelectableChannel): CoroutineSelector { val selectors = this.selectors ?: throw IllegalStateException( "Access to terminated ChannelGroup by unregistered channel") return selectors[System.identityHashCode(channel).rem(selectors.size)] } override val isTerminated: Boolean get() { if (isShutdown && !wasTerminated && channels.isEmpty()) { terminate() return true } return wasTerminated } override fun shutdown() { isShutdown = true logger.debug("Shutdown channel group {}", System.identityHashCode(this)) if (!wasTerminated && channels.isEmpty()) { terminate() } } override fun shutdownNow() { shutdown() channels.forEach { it.close() } } override suspend fun awaitTermination() { if (wasTerminated) { return } suspendCoroutine { cont -> pendingTermination.add(cont) if (isShutdown && channels.isEmpty()) { terminate() } } } private fun terminate() { check(isShutdown) wasTerminated = true logger.debug("Terminated channel group {}", System.identityHashCode(this)) while (true) { (pendingTermination.poll() ?: break).resume(Unit) } val selectors = this.selectors this.selectors = null selectors?.let { it.map { selector -> selector.close() } } } } CoroutineDatagramChannel.kt000066400000000000000000000157661341750772100343340ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/main/kotlin/net/consensys/cava/net/coroutines/* * Copyright 2018 ConsenSys AG. * * 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.net.coroutines import java.io.IOException import java.net.SocketAddress import java.nio.ByteBuffer import java.nio.channels.AsynchronousCloseException import java.nio.channels.ClosedByInterruptException import java.nio.channels.ClosedChannelException import java.nio.channels.DatagramChannel import java.nio.channels.SelectionKey /** * A co-routine based datagram-oriented network channel. * * @author Chris Leishman - https://cleishm.github.io/ */ class CoroutineDatagramChannel private constructor( private val channel: DatagramChannel, private val group: CoroutineChannelGroup ) : CoroutineByteChannel, ScatteringCoroutineByteChannel by ScatteringCoroutineByteChannelMixin(channel, group), GatheringCoroutineByteChannel by GatheringCoroutineByteChannelMixin(channel, group), CoroutineNetworkChannel by CoroutineNetworkChannelMixin(channel) { companion object { /** * Opens a datagram channel. * * @param selector The selector to use with this channel (defaults to `CommonCoroutineSelector`). * @return A new channel. * @throws IOException If an I/O error occurs. */ fun open(group: CoroutineChannelGroup = CommonCoroutineGroup): CoroutineDatagramChannel { val channel = DatagramChannel.open() channel.configureBlocking(false) return CoroutineDatagramChannel(channel, group) } } init { group.register(this) } override fun close() { group.deRegister(this) channel.close() } /** * Indicates whether this channel is connected. */ val isConnected: Boolean get() = channel.isConnected /** * Get the remote address to which this channel is connected. */ val remoteAddress: SocketAddress get() = channel.remoteAddress override fun bind(local: SocketAddress?): CoroutineDatagramChannel { channel.bind(local) return this } /** * Connect this channel. * * The channel will be configured so that it only receives datagrams from, and sends datagrams to, the given * remote peer address. Once connected, datagrams may not be received from or sent to any other address. A datagram * channel remains connected until it is disconnected or closed. * * @param remote The remote address to which this channel is to be connected. * @return This channel. * @throws ClosedChannelException If the channel is closed. * @throws AsynchronousCloseException If another thread closes this channel while the connect operation is in * progress. * @throws ClosedByInterruptException If another thread interrupts the current thread while the connect operation is * in progress, thereby closing the channel and setting the current thread's interrupt status. * @throws IOException If some other I/O error occurs. */ fun connect(remote: SocketAddress): CoroutineDatagramChannel { channel.connect(remote) return this } /** * Disconnects this channel. */ fun disconnect(): CoroutineDatagramChannel { channel.disconnect() return this } /** * Receives a datagram via this channel. * * @param dst The buffer into which the datagram is to be transferred. * @return The datagram's source address. * @throws ClosedChannelException If the channel is closed. * @throws AsynchronousCloseException If another thread closes this channel while the receive operation is in * progress. * @throws ClosedByInterruptException If another thread interrupts the current thread while the receive operation is * in progress, thereby closing the channel and setting the current thread's interrupt status. * @throws IOException If some other I/O error occurs. */ suspend fun receive(dst: ByteBuffer): SocketAddress { while (true) { val address = channel.receive(dst) if (address != null) { return address } // slow path group.select(channel, SelectionKey.OP_READ) } } /** * Receives a datagram via this channel, if one is immediately available. * * @param dst The buffer into which the datagram is to be transferred. * @return The datagram's source address, or `null` if no datagram was available to be received. * @throws ClosedChannelException If the channel is closed. * @throws AsynchronousCloseException If another thread closes this channel while the receive operation is in * progress. * @throws ClosedByInterruptException If another thread interrupts the current thread while the receive operation is * in progress, thereby closing the channel and setting the current thread's interrupt status. * @throws IOException If some other I/O error occurs. */ fun tryReceive(dst: ByteBuffer): SocketAddress? = channel.receive(dst) /** * Sends a datagram via this channel. * * @param src The buffer containing the datagram to be sent. * @param target The address to which the datagram is to be sent. * @return The number of bytes sent. * @throws ClosedChannelException If the channel is closed. * @throws AsynchronousCloseException If another thread closes this channel while the send operation is in progress. * @throws ClosedByInterruptException If another thread interrupts the current thread while the send operation is * in progress, thereby closing the channel and setting the current thread's interrupt status. * @throws IOException If some other I/O error occurs. */ suspend fun send(src: ByteBuffer, target: SocketAddress): Int { while (true) { val n = channel.send(src, target) if (n != 0 || src.remaining() == 0) { return n } // slow path group.select(channel, SelectionKey.OP_WRITE) } } /** * Sends a datagram via this channel, if it can be sent immediately. * * @param src The buffer containing the datagram to be sent. * @param target The address to which the datagram is to be sent. * @return The number of bytes sent, which will be zero if the channel was not ready to send. * @throws ClosedChannelException If the channel is closed. * @throws AsynchronousCloseException If another thread closes this channel while the send operation is in progress. * @throws ClosedByInterruptException If another thread interrupts the current thread while the send operation is * in progress, thereby closing the channel and setting the current thread's interrupt status. * @throws IOException If some other I/O error occurs. */ fun trySend(src: ByteBuffer, target: SocketAddress): Int = channel.send(src, target) } CoroutineNetworkChannel.kt000066400000000000000000000135651341750772100342400ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/main/kotlin/net/consensys/cava/net/coroutines/* * Copyright 2018 ConsenSys AG. * * 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.net.coroutines import java.io.IOException import java.net.InetAddress import java.net.InetSocketAddress import java.net.SocketAddress import java.net.SocketOption import java.nio.channels.AlreadyBoundException import java.nio.channels.ClosedChannelException import java.nio.channels.NetworkChannel import java.nio.channels.UnsupportedAddressTypeException /** * A co-routine based network channel. * * @author Chris Leishman - https://cleishm.github.io/ */ interface CoroutineNetworkChannel : NetworkChannel { /** * Indicates if this channel is open. * * @return `true` if this channel is open. */ override fun isOpen(): Boolean /** * Closes this channel. * * After a channel is closed, any further attempt to invoke I/O operations upon it will cause a * [ClosedChannelException] to be thrown. * * @throws IOException If an I/O error occurs. */ override fun close() /** * Binds the channel's socket to a local address. * * This method is used to establish an association between the socket and a local address. Once an association is * established then the socket remains bound until the channel is closed. If the `local` parameter has the value * `null` then the socket will be bound to an address that is assigned automatically. * * @param local The address to bind the socket, or `null` to bind the socket to an automatically assigned socket * address. * @return This channel. * @throws AlreadyBoundException If the socket is already bound. * @throws UnsupportedAddressTypeException If the type of the given address is not supported. * @throws ClosedChannelException If the channel is closed. * @throws IOException If some other I/O error occurs. * @throws SecurityException If a security manager is installed and it denies an unspecified permission. An * implementation of this interface should specify any required permissions. */ override fun bind(local: SocketAddress?): CoroutineNetworkChannel /** * Returns the socket address that this channel's socket is bound to. * * @return The socket address that the socket is bound to, or `null` if the channel's socket is not bound. * @throws ClosedChannelException If the channel is closed. * @throws IOException If an I/O error occurs. */ override fun getLocalAddress(): SocketAddress? /** * Returns the InetAddress corresponding to the interface this channel's socket is bound to. * * @return The InetAddress that the socket is bound to, or `null` if the channel's socket is not bound. * @throws IllegalStateException If the channel is not bound to an inet address * @throws ClosedChannelException If the channel is closed. * @throws IOException If an I/O error occurs. */ fun getAdvertisableAddress(): InetAddress? { val localAddress = localAddress ?: return null val localInetAddress = (localAddress as? InetSocketAddress)?.address ?: throw IllegalStateException("Channel bound to non-inet interface") if (!localInetAddress.isAnyLocalAddress) { return localInetAddress } // This will typically work ok on hosts with only a single interface return InetAddress.getLocalHost() } /** * The port number on the local host to which this socket is bound. * * The port number on the local host to which this socket is bound, -1 if the socket is closed, or 0 if it is not * bound yet. */ val localPort: Int /** * Sets the value of a socket option. * * @param The type of the socket option value. * @param name The socket option. * @param value The value of the socket option. A value of `null` may be a valid value for some socket options. * @return This channel * @throws UnsupportedOperationException If the socket option is not supported by this channel. * @throws IllegalArgumentException If the value is not a valid value for this socket option. * @throws ClosedChannelException If this channel is closed. * @throws IOException If an I/O error occurs. * @see java.net.StandardSocketOptions */ override fun setOption(name: SocketOption, value: T?): NetworkChannel /** * Returns the value of a socket option. * * @param The type of the socket option value. * @param name The socket option. * @return The value of the socket option. A value of `null` may be a valid value for some socket options. * @throws UnsupportedOperationException If the socket option is not supported by this channel. * @throws ClosedChannelException If this channel is closed. * @throws IOException If an I/O error occurs. * @see java.net.StandardSocketOptions */ override fun getOption(name: SocketOption): T? /** * Returns a set of the socket options supported by this channel. * * @return A set of the socket options supported by this channel. */ override fun supportedOptions(): Set> } internal class CoroutineNetworkChannelMixin( private val channel: NetworkChannel ) : CoroutineNetworkChannel, NetworkChannel by channel { override val localPort: Int get() { if (!isOpen) { return -1 } return (localAddress as? InetSocketAddress)?.port ?: 0 } override fun bind(local: SocketAddress?): CoroutineNetworkChannel { channel.bind(local) return this } } cava-0.6.0/net-coroutines/src/main/kotlin/net/consensys/cava/net/coroutines/CoroutineSelector.kt000066400000000000000000000362761341750772100331610ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.coroutines import com.google.common.util.concurrent.ThreadFactoryBuilder import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.suspendCancellableCoroutine import org.logl.LogMessage import org.logl.LoggerProvider import java.nio.channels.CancelledKeyException import java.nio.channels.ClosedChannelException import java.nio.channels.ClosedSelectorException import java.nio.channels.SelectableChannel import java.nio.channels.SelectionKey import java.nio.channels.Selector import java.util.concurrent.Executor import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicInteger import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.resumeWithException /** * A selector for co-routine based channel IO. * * @author Chris Leishman - https://cleishm.github.io/ */ sealed class CoroutineSelector { companion object { internal val DEFAULT_THREAD_FACTORY = ThreadFactoryBuilder().setNameFormat("selection-dispatch-%d").setDaemon(true).build() /** * Open a co-routine selector. * * @param executor An executor for obtaining a thread to run the selection loop. * @param loggerProvider A provider for logger instances. * @param selectTimeout The maximum time the selection operation will wait before checking for closed channels. * @param idleTimeout The minimum idle time before the selection loop of the selector exits. * @return A co-routine selector. */ fun open( executor: Executor = Executors.newSingleThreadExecutor(DEFAULT_THREAD_FACTORY), loggerProvider: LoggerProvider = LoggerProvider.nullProvider(), selectTimeout: Long = 1000, idleTimeout: Long = 10000 ): CoroutineSelector { require(selectTimeout > 0) { "selectTimeout must be larger than zero" } require(idleTimeout >= 0) { "idleTimeout must be positive" } val idleTasks = idleTimeout / selectTimeout require(idleTasks <= Integer.MAX_VALUE) { "idleTimeout is too large" } return SingleThreadCoroutineSelector(executor, Selector.open(), loggerProvider, selectTimeout, idleTasks.toInt()) } } /** * Indicates whether the selector is open or not. * * @return `true` if the selector is open. */ abstract fun isOpen(): Boolean /** * Wait for a channel to become ready for any of the specified operations. * * @param channel The channel. * @param ops The interest set, as a combination of [SelectionKey.OP_ACCEPT], [SelectionKey.OP_CONNECT], * [SelectionKey.OP_READ] and/or [SelectionKey.OP_WRITE]. * @throws ClosedSelectorException If the co-routine selector has been closed. */ abstract suspend fun select(channel: SelectableChannel, ops: Int) /** * Cancel any suspended calls to [select] for the specified channel. * * @param channel The channel. * @param cause An optional cause for the cancellation. * @return `true` if any suspensions were cancelled. * @throws ClosedSelectorException If the co-routine selector has been closed. */ abstract suspend fun cancelSelections(channel: SelectableChannel, cause: Throwable? = null): Boolean /** * Force the selection loop, if running, to wake up and process any closed channels. * * @throws ClosedSelectorException If the co-routine selector has been closed. */ abstract fun wakeup() /** * Close the co-routine selector. */ abstract fun close() /** * Close the co-routine selector and wait for all suspensions to be cancelled. */ abstract suspend fun closeNow() } internal class SingleThreadCoroutineSelector( private val executor: Executor, private val selector: Selector, loggerProvider: LoggerProvider, private val selectTimeout: Long, private val idleTasks: Int ) : CoroutineSelector() { private val logger = loggerProvider.getLogger(CoroutineSelector::class.java) private val pendingInterests = Channel(capacity = Channel.UNLIMITED) private val pendingCancellations = Channel(capacity = Channel.UNLIMITED) private val pendingCloses = Channel>(capacity = Channel.UNLIMITED) private val outstandingTasks = AtomicInteger(0) private val registeredKeys = HashSet() init { require(selector.isOpen) { "Selector is closed" } require(selector.keys().isEmpty()) { "Selector already has selection keys" } } override fun isOpen(): Boolean = selector.isOpen override suspend fun select(channel: SelectableChannel, ops: Int) { require(!channel.isBlocking) { "AsyncChannel must be set to non blocking" } require(ops != 0) { "ops must not be zero" } require(ops and channel.validOps().inv() == 0) { "Invalid operations for channel" } if (!selector.isOpen) { throw ClosedSelectorException() } suspendCancellableCoroutine { cont: CancellableContinuation -> try { // increment tasks first to keep selection loop running while we add a new pending interest val isRunning = incrementTasks() pendingInterests.offer(SelectionInterest(cont, channel, ops)) wakeup(isRunning) } catch (e: Throwable) { cont.resumeWithException(e) } } } override suspend fun cancelSelections(channel: SelectableChannel, cause: Throwable?): Boolean { if (!selector.isOpen) { throw ClosedSelectorException() } check(selector.isOpen) { "Selector is closed" } return suspendCancellableCoroutine { cont: CancellableContinuation -> try { // increment tasks first to keep selection loop running while we add a new pending cancellation val isRunning = incrementTasks() pendingCancellations.offer(SelectionCancellation(channel, cause, cont)) wakeup(isRunning) } catch (e: Throwable) { cont.resumeWithException(e) } } } override fun wakeup() { if (!selector.isOpen) { throw ClosedSelectorException() } selector.wakeup() } override fun close() { selector.close() } override suspend fun closeNow() { selector.close() suspendCoroutine { cont: Continuation -> try { pendingCloses.offer(cont) if (outstandingTasks.get() == 0) { processPendingCloses() } } catch (e: Throwable) { cont.resumeWithException(e) throw e } } } private fun incrementTasks(): Boolean = outstandingTasks.getAndIncrement() != 0 private fun wakeup(isRunning: Boolean) { if (isRunning) { logger.debug("Selector {}: Interrupting selection loop", System.identityHashCode(selector)) selector.wakeup() } else { executor.execute(this::selectionLoop) } } private fun selectionLoop() { logger.debug("Selector {}: Starting selection loop", System.identityHashCode(selector)) try { // allow the selector to cleanup any outstanding cancelled keys before starting the loop selector.selectNow() outstandingTasks.addAndGet(idleTasks) var idleCount = 0 while (true) { // add pending selections before awakening selected, which allows for newly added // selections to be awoken immediately and avoids trying to register to already canceled keys if (processTasks(this::registerPendingSelections)) { break } if (processTasks(this::processPendingCancellations)) { break } if (processTasks(this::awakenSelected)) { break } if (selector.keys().isEmpty()) { if (outstandingTasks.decrementAndGet() == 0) { break } idleCount++ } else { outstandingTasks.addAndGet(idleCount) idleCount = 0 } selector.selectedKeys().clear() // use a timeout on select, as keys cancelled via channel close wont wakeup the selector selector.select(selectTimeout) if (!selector.isOpen) { cancelAll(ClosedSelectorException()) break } if (processTasks(this::cancelMissingRegistrations)) { break } } logger.debug("Selector {}: Exiting selection loop", System.identityHashCode(selector)) processPendingCloses() } catch (e: Throwable) { selector.close() logger.error(LogMessage.patternFormat("Selector {}: An unexpected exception occurred in selection loop", System.identityHashCode(selector)), e) cancelAll(e) processPendingCloses(e) } } private fun processTasks(block: () -> Int): Boolean { val processed = block() val remaining = outstandingTasks.addAndGet(-processed) check(remaining >= 0) { "More tasks processed than were outstanding" } return remaining == 0 } private fun registerPendingSelections(): Int { var processed = 0 while (true) { val interest = pendingInterests.poll() ?: break try { val key = interest.channel.keyFor(selector) val registered = if (key == null) { registerInterest(interest) } else { mergeInterest(key, interest) } if (!registered) { processed++ } } catch (e: Throwable) { interest.cont.resumeWithException(e) throw e } } return processed } private fun registerInterest(interest: SelectionInterest): Boolean { val key: SelectionKey try { key = interest.channel.register(selector, interest.ops, arrayListOf(interest)) } catch (e: ClosedChannelException) { interest.cont.resumeWithException(e) return false } registeredKeys.add(key) logger.debug("Selector {}: Registered {}@{} for interests {}", System.identityHashCode(selector), interest.channel, System.identityHashCode(interest.channel), interest.ops) return true } private fun mergeInterest(key: SelectionKey, interest: SelectionInterest): Boolean { val mergedInterests: Int try { mergedInterests = key.interestOps() or interest.ops key.interestOps(mergedInterests) } catch (e: CancelledKeyException) { // key must have been cancelled via closing the channel val exception = ClosedChannelException() exception.addSuppressed(e) interest.cont.resumeWithException(exception) return false } @Suppress("UNCHECKED_CAST") val interests = key.attachment() as ArrayList interests.add(interest) logger.debug("Selector {}: Updated registration for channel {} to interests {}", System.identityHashCode(selector), System.identityHashCode(interest.channel), mergedInterests) return true } private fun processPendingCancellations(): Int { var processed = 0 while (true) { val cancellation = pendingCancellations.poll() ?: break processed++ val key = cancellation.channel.keyFor(selector) if (key != null) { logger.debug("Selector {}: Cancelling registration for channel {}", System.identityHashCode(selector), System.identityHashCode(cancellation.channel)) @Suppress("UNCHECKED_CAST") val interests = key.attachment() as ArrayList for (interest in interests) { interest.cont.cancel(cancellation.cause) processed++ } interests.clear() key.cancel() registeredKeys.remove(key) cancellation.cont.resume(true) } else { cancellation.cont.resume(false) } } return processed } private fun processPendingCloses() { while (true) { (pendingCloses.poll() ?: break).resume(Unit) } } private fun processPendingCloses(e: Throwable) { while (true) { (pendingCloses.poll() ?: break).resumeWithException(e) } } private fun awakenSelected(): Int { var awoken = 0 val selectedKeys = selector.selectedKeys() for (key in selectedKeys) { @Suppress("UNCHECKED_CAST") val interests = key.attachment() as ArrayList if (!key.isValid) { // channel must have been closed val cause = ClosedChannelException() interests.forEach { it.cont.resumeWithException(cause) } interests.clear() continue } val readyOps = key.readyOps() logger.debug("Selector {}: Channel {} selected for interests {}", System.identityHashCode(selector), System.identityHashCode(key.channel()), readyOps) var remainingOps = 0 val it = interests.iterator() while (it.hasNext()) { val interest = it.next() // if any of the interests are set, then resume the continuation if ((interest.ops and readyOps) != 0) { interest.cont.resume(Unit) it.remove() awoken++ } else { remainingOps = remainingOps or interest.ops } } key.interestOps(remainingOps) if (interests.isEmpty()) { registeredKeys.remove(key) key.cancel() } } return awoken } private fun cancelMissingRegistrations(): Int { val selectorKeys = selector.keys() if (selectorKeys.size == registeredKeys.size) { // assume sets of the same size contain the same members return 0 } // There should only be less keys in the selector, as keys will vanish after cancellation via closing their channel check(selectorKeys.size < registeredKeys.size) { "More registered keys than are outstanding" } var processed = 0 registeredKeys.removeIf { key -> if (selectorKeys.contains(key)) { false } else { val cause = ClosedChannelException() @Suppress("UNCHECKED_CAST") val interests = key.attachment() as ArrayList for (interest in interests) { interest.cont.resumeWithException(cause) processed++ } interests.clear() key.cancel() true } } return processed } private fun cancelAll(e: Throwable) { while (true) { val it = pendingInterests.poll() ?: break it.cont.resumeWithException(e) } registeredKeys.forEach { key -> @Suppress("UNCHECKED_CAST") (key.attachment() as ArrayList).forEach { it.cont.resumeWithException(e) } } registeredKeys.clear() while (true) { val it = pendingCancellations.poll() ?: break it.cont.resumeWithException(e) } outstandingTasks.set(0) } private data class SelectionInterest( val cont: CancellableContinuation, val channel: SelectableChannel, val ops: Int ) private data class SelectionCancellation( val channel: SelectableChannel, val cause: Throwable?, val cont: CancellableContinuation ) } CoroutineServerSocketChannel.kt000066400000000000000000000104521341750772100352160ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/main/kotlin/net/consensys/cava/net/coroutines/* * Copyright 2018 ConsenSys AG. * * 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.net.coroutines import java.io.IOException import java.net.SocketAddress import java.nio.channels.AlreadyBoundException import java.nio.channels.AsynchronousCloseException import java.nio.channels.ClosedByInterruptException import java.nio.channels.ClosedChannelException import java.nio.channels.NotYetBoundException import java.nio.channels.SelectionKey import java.nio.channels.ServerSocketChannel import java.nio.channels.UnsupportedAddressTypeException /** * A co-routine based network channel for stream-oriented connection listening. * * @author Chris Leishman - https://cleishm.github.io/ */ class CoroutineServerSocketChannel private constructor( private val channel: ServerSocketChannel, private val group: CoroutineChannelGroup ) : CoroutineNetworkChannel by CoroutineNetworkChannelMixin(channel) { companion object { /** * Opens a server-socket channel. * * @param selector The selector to use with this channel (defaults to `CommonCoroutineSelector`). * @return A new channel. * @throws IOException If an I/O error occurs. */ fun open(group: CoroutineChannelGroup = CommonCoroutineGroup): CoroutineServerSocketChannel { val channel = ServerSocketChannel.open() channel.configureBlocking(false) return CoroutineServerSocketChannel(channel, group) } } init { group.register(this) } override fun close() { group.deRegister(this) channel.close() } /** * Binds the channel's socket to a local address and configures the socket to listen for connections. * * @param local The local address to bind the socket, or null to bind to an automatically assigned socket address. * @return This channel * @throws AlreadyBoundException If the socket is already bound. * @throws UnsupportedAddressTypeException If the type of the given address is not supported. * @throws ClosedChannelException If the channel is closed. * @throws IOException If an I/O error occurs. */ override fun bind(local: SocketAddress?): CoroutineServerSocketChannel = bind(local, 0) /** * Binds the channel's socket to a local address and configures the socket to listen for connections. * * @param local The local address to bind the socket, or null to bind to an automatically assigned socket address. * @param backlog The maximum number of pending connections. * @return This channel * @throws AlreadyBoundException If the socket is already bound. * @throws UnsupportedAddressTypeException If the type of the given address is not supported. * @throws ClosedChannelException If the channel is closed. * @throws IOException If an I/O error occurs. */ fun bind(local: SocketAddress?, backlog: Int): CoroutineServerSocketChannel { channel.bind(local, backlog) return this } /** * Accepts a connection made to this channel's socket. * * @return The socket channel for the new connection. * @throws ClosedChannelException If the channel is closed. * @throws AsynchronousCloseException If another thread closes this channel while the accept operation is in * progress. * @throws ClosedByInterruptException If another thread interrupts the current thread while the accept operation is * in progress, thereby closing the channel and setting the current thread's interrupt status. * @throws NotYetBoundException If this channel's socket has not yet been bound. * @throws IOException If an I/O error occurs. */ suspend fun accept(): CoroutineSocketChannel { while (true) { val s = channel.accept() if (s != null) { s.configureBlocking(false) return CoroutineSocketChannel(s, group) } // slow path group.select(channel, SelectionKey.OP_ACCEPT) } } } CoroutineSocketChannel.kt000066400000000000000000000101141341750772100340220ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/main/kotlin/net/consensys/cava/net/coroutines/* * Copyright 2018 ConsenSys AG. * * 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.net.coroutines import java.io.IOException import java.net.SocketAddress import java.nio.channels.AsynchronousCloseException import java.nio.channels.ClosedByInterruptException import java.nio.channels.ClosedChannelException import java.nio.channels.NotYetConnectedException import java.nio.channels.SelectionKey import java.nio.channels.SocketChannel /** * A co-routine based stream-oriented network channel. * * @author Chris Leishman - https://cleishm.github.io/ */ class CoroutineSocketChannel internal constructor( private val channel: SocketChannel, private val group: CoroutineChannelGroup ) : CoroutineByteChannel, ScatteringCoroutineByteChannel by ScatteringCoroutineByteChannelMixin(channel, group), GatheringCoroutineByteChannel by GatheringCoroutineByteChannelMixin(channel, group), CoroutineNetworkChannel by CoroutineNetworkChannelMixin(channel) { companion object { /** * Opens a socket channel. * * @param selector The selector to use with this channel (defaults to `CommonCoroutineSelector`). * @return A new channel. * @throws IOException If an I/O error occurs. */ fun open(group: CoroutineChannelGroup = CommonCoroutineGroup): CoroutineSocketChannel { val channel = SocketChannel.open() channel.configureBlocking(false) return CoroutineSocketChannel(channel, group) } } init { group.register(this) } override fun close() { group.deRegister(this) channel.close() } /** * Indicates whether this channel is connected. */ val isConnected: Boolean get() = channel.isConnected /** * Get the remote address to which this channel is connected. */ val remoteAddress: SocketAddress get() = channel.remoteAddress override fun bind(local: SocketAddress?): CoroutineSocketChannel { channel.bind(local) return this } /** * Connect this channel. * * @param remote The remote address to which this channel is to be connected. * @return This channel. * @throws ClosedChannelException If the channel is closed. * @throws AsynchronousCloseException If another thread closes this channel while the connect operation is in * progress. * @throws ClosedByInterruptException If another thread interrupts the current thread while the connect operation is * in progress, thereby closing the channel and setting the current thread's interrupt status. * @throws IOException If some other I/O error occurs. */ suspend fun connect(remote: SocketAddress): CoroutineSocketChannel { if (!channel.connect(remote)) { // slow path do { group.select(channel, SelectionKey.OP_CONNECT) } while (!channel.finishConnect()) } return this } /** * Shutdown the connection for reading without closing the channel. * * @return This channel. * @throws NotYetConnectedException If this channel is not yet connected. * @throws ClosedChannelException If the channel is closed. * @throws IOException If some other I/O error occurs. */ fun shutdownInput(): CoroutineSocketChannel { channel.shutdownInput() return this } /** * Shutdown the connection for writing without closing the channel. * * @return This channel. * @throws NotYetConnectedException If this channel is not yet connected. * @throws ClosedChannelException If the channel is closed. * @throws IOException If some other I/O error occurs. */ fun shutdownOutput(): CoroutineSocketChannel { channel.shutdownOutput() return this } } cava-0.6.0/net-coroutines/src/test/000077500000000000000000000000001341750772100171625ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/test/java/000077500000000000000000000000001341750772100201035ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/test/java/net/000077500000000000000000000000001341750772100206715ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/test/java/net/consensys/000077500000000000000000000000001341750772100227155ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100236275ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/test/java/net/consensys/cava/net/000077500000000000000000000000001341750772100244155ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/test/java/net/consensys/cava/net/coroutines/000077500000000000000000000000001341750772100266075ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/test/java/net/consensys/cava/net/coroutines/SelectorTest.java000066400000000000000000000112531341750772100320740ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.coroutines; import static java.nio.channels.SelectionKey.OP_READ; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.channels.Pipe; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; class SelectorTest { private static ExecutorService executor; @BeforeAll static void setup() { executor = Executors.newCachedThreadPool(); } @Test void selectorRemovesKeysOnChannelCloseWhenSelecting() throws Exception { Pipe pipe = Pipe.open(); Selector selector = Selector.open(); SelectableChannel source = pipe.source(); source.configureBlocking(false); SelectionKey key = source.register(selector, OP_READ); assertTrue(selector.keys().contains(key)); source.close(); assertTrue(selector.keys().contains(key)); selector.selectNow(); assertFalse(selector.keys().contains(key)); } @Test void selectorRemovesKeysOnChannelCloseWhileSelecting() throws Exception { Pipe pipe = Pipe.open(); Selector selector = Selector.open(); SelectableChannel source = pipe.source(); source.configureBlocking(false); SelectionKey key = source.register(selector, OP_READ); assertTrue(selector.keys().contains(key)); CountDownLatch latch = new CountDownLatch(1); Future job = executor.submit(() -> { latch.countDown(); try { selector.select(); } catch (IOException e) { throw new UncheckedIOException(e); } }); latch.await(); Thread.sleep(100); source.close(); selector.wakeup(); job.get(); assertFalse(selector.keys().contains(key)); } @Test void selectorRemovesKeysOnCancelWhenSelecting() throws Exception { Pipe pipe = Pipe.open(); Selector selector = Selector.open(); SelectableChannel source = pipe.source(); source.configureBlocking(false); SelectionKey key = source.register(selector, OP_READ); assertTrue(selector.keys().contains(key)); key.cancel(); assertTrue(selector.keys().contains(key)); assertSame(key, source.keyFor(selector)); selector.selectNow(); assertFalse(selector.keys().contains(key)); assertNull(source.keyFor(selector)); } @Test void selectorRemovesKeysOnCancelWhileSelecting() throws Exception { Pipe pipe = Pipe.open(); Selector selector = Selector.open(); SelectableChannel source = pipe.source(); source.configureBlocking(false); SelectionKey key = source.register(selector, OP_READ); assertTrue(selector.keys().contains(key)); CountDownLatch latch = new CountDownLatch(1); Future job = executor.submit(() -> { latch.countDown(); try { selector.select(); } catch (IOException e) { throw new UncheckedIOException(e); } }); latch.await(); Thread.sleep(100); key.cancel(); assertTrue(selector.keys().contains(key)); assertSame(key, source.keyFor(selector)); selector.wakeup(); job.get(); assertFalse(selector.keys().contains(key)); assertNull(source.keyFor(selector)); } @Test void cancelledKeyRemovedFromChannel() throws Exception { Pipe pipe = Pipe.open(); Selector selector = Selector.open(); SelectableChannel source = pipe.source(); source.configureBlocking(false); for (int i = 0; i < 1000; ++i) { assertNull(source.keyFor(selector)); SelectionKey key = source.register(selector, OP_READ); selector.selectedKeys().clear(); selector.selectNow(); key.cancel(); selector.wakeup(); selector.selectedKeys().clear(); selector.selectNow(); } } } cava-0.6.0/net-coroutines/src/test/kotlin/000077500000000000000000000000001341750772100204625ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/test/kotlin/net/000077500000000000000000000000001341750772100212505ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/test/kotlin/net/consensys/000077500000000000000000000000001341750772100232745ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/test/kotlin/net/consensys/cava/000077500000000000000000000000001341750772100242065ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/test/kotlin/net/consensys/cava/net/000077500000000000000000000000001341750772100247745ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/test/kotlin/net/consensys/cava/net/coroutines/000077500000000000000000000000001341750772100271665ustar00rootroot00000000000000CoroutineChannelGroupTest.kt000066400000000000000000000061061341750772100345670ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/test/kotlin/net/consensys/cava/net/coroutines/* * Copyright 2018 ConsenSys AG. * * 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.net.coroutines import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import java.nio.channels.ShutdownChannelGroupException internal class CoroutineChannelGroupTest { @Test fun shouldImmediatelyTerminateEmptyGroup() { val group = CoroutineChannelGroup.open() group.shutdown() assertTrue(group.isShutdown) assertTrue(group.isTerminated) } @Test fun shouldTerminateGroupWhenAllChannelsClosed() { val group = CoroutineChannelGroup.open() val channel = CoroutineServerSocketChannel.open(group) group.shutdown() assertTrue(group.isShutdown) assertFalse(group.isTerminated) channel.close() assertTrue(group.isTerminated) } @Test fun shouldNotAllowNewChannelsAfterShutdown() { val group = CoroutineChannelGroup.open() CoroutineServerSocketChannel.open(group) group.shutdown() assertThrows { CoroutineServerSocketChannel.open(group) } } @Test fun shouldTerminateWhenAllChannelAreClosed() = runBlocking { val group = CoroutineChannelGroup.open() val channel = CoroutineServerSocketChannel.open(group) var didBlock = false val task = async { group.awaitTermination() assertTrue(didBlock) } group.shutdown() Thread.sleep(100) assertFalse(group.isTerminated) didBlock = true channel.close() task.await() assertTrue(group.isTerminated) } @Test fun shutdownNowShouldCloseChannels() { val group = CoroutineChannelGroup.open() val channel = CoroutineServerSocketChannel.open(group) assertTrue(channel.isOpen) group.shutdownNow() assertFalse(channel.isOpen) assertTrue(group.isTerminated) } @Test fun shutdownNowShouldResumeCoroutinesAwaitingTermination() = runBlocking { val group = CoroutineChannelGroup.open() val channel = CoroutineServerSocketChannel.open(group) var didBlock = false val task = async { group.awaitTermination() assertTrue(didBlock) } Thread.sleep(100) didBlock = true group.shutdownNow() assertTrue(group.isTerminated) task.await() assertFalse(channel.isOpen) } @Test fun awaitTerminationShouldReturnImmediatelyForTerminatedGroup() { val group = CoroutineChannelGroup.open() group.shutdown() runBlocking { group.awaitTermination() } } } CoroutineDatagramChannelTest.kt000066400000000000000000000055641341750772100352220ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/test/kotlin/net/consensys/cava/net/coroutines/* * Copyright 2018 ConsenSys AG. * * 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.net.coroutines import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import java.net.DatagramPacket import java.net.DatagramSocket import java.net.InetAddress import java.net.InetSocketAddress import java.nio.ByteBuffer internal class CoroutineDatagramChannelTest { @Test fun shouldSuspendDatagramChannelWhileReading() = runBlocking { val channel = CoroutineDatagramChannel.open() assertNull(channel.localAddress) assertEquals(0, channel.localPort) channel.bind(null) assertNotNull(channel.localAddress) assertTrue(channel.localPort > 0) var didBlock = false val dst = ByteBuffer.allocate(10) val job = async { channel.receive(dst) assertTrue(didBlock) } Thread.sleep(100) didBlock = true val socket = DatagramSocket() socket.connect(InetAddress.getLocalHost(), (channel.localAddress as InetSocketAddress).port) val testData = byteArrayOf(1, 2, 3, 4, 5) socket.send(DatagramPacket(testData, 5)) job.await() dst.flip() assertEquals(5, dst.remaining()) assertEquals(testData[0], dst.get(0)) assertEquals(testData[1], dst.get(1)) assertEquals(testData[2], dst.get(2)) assertEquals(testData[3], dst.get(3)) assertEquals(testData[4], dst.get(4)) } @Test fun shouldSuspendDatagramChannelWhileWriting() = runBlocking { val socket = DatagramSocket() val channel = CoroutineDatagramChannel.open() val address = InetSocketAddress(InetAddress.getLocalHost(), socket.localPort) val testData = byteArrayOf(1, 2, 3, 4, 5) val src = ByteBuffer.wrap(testData) val job = async { channel.send(src, address) } val resultData = ByteArray(5) val packet = DatagramPacket(resultData, resultData.size) socket.receive(packet) assertEquals(5, packet.length) assertEquals(testData[0], resultData[0]) assertEquals(testData[1], resultData[1]) assertEquals(testData[2], resultData[2]) assertEquals(testData[3], resultData[3]) assertEquals(testData[4], resultData[4]) job.await() } } cava-0.6.0/net-coroutines/src/test/kotlin/net/consensys/cava/net/coroutines/CoroutineSelectorTest.kt000066400000000000000000000154151341750772100340440ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.coroutines import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.fail import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.logl.logl.SimpleLogger import java.lang.IllegalArgumentException import java.net.InetSocketAddress import java.nio.ByteBuffer import java.nio.channels.ClosedChannelException import java.nio.channels.ClosedSelectorException import java.nio.channels.Pipe import java.nio.channels.SelectionKey import java.nio.channels.ServerSocketChannel import java.nio.channels.SocketChannel internal class CoroutineSelectorTest { @Test fun shouldRequireNonBlockingChannel() { val pipe = Pipe.open() val selector = CoroutineSelector.open() assertThrows { runBlocking { selector.select(pipe.source(), SelectionKey.OP_READ) } } } @Test fun shouldThrowWhenAccessingClosedSelector() { val pipe = Pipe.open() pipe.source().configureBlocking(false) val selector = CoroutineSelector.open() selector.close() assertThrows { runBlocking { selector.select(pipe.source(), SelectionKey.OP_READ) } } assertThrows { runBlocking { selector.cancelSelections(pipe.source()) } } assertThrows { selector.wakeup() } } @Test fun closeNowOnEmptySelectorShouldReturnImmediately() { val selector = CoroutineSelector.open() runBlocking { selector.closeNow() } } @Test fun shouldSuspendUntilReady() = runBlocking { val pipe1 = Pipe.open() pipe1.source().configureBlocking(false) val pipe2 = Pipe.open() pipe2.source().configureBlocking(false) val selector = CoroutineSelector.open() var ok1 = false var ok2 = false val job1 = async { selector.select(pipe1.source(), SelectionKey.OP_READ) assertTrue(ok1, "failed to suspend") assertFalse(ok2) } Thread.sleep(100) val job2 = async { selector.select(pipe2.source(), SelectionKey.OP_READ) assertTrue(ok2, "failed to suspend") } ok1 = true pipe1.sink().write(ByteBuffer.wrap(byteArrayOf(1))) job1.await() ok2 = true pipe2.sink().write(ByteBuffer.wrap(byteArrayOf(1))) job2.await() } @Test fun shouldAwakenMultiple() = runBlocking { val server = ServerSocketChannel.open() server.bind(InetSocketAddress(0)) val client = SocketChannel.open() client.connect(server.localAddress) client.configureBlocking(false) val selector = CoroutineSelector.open() val job1 = async { selector.select(client, SelectionKey.OP_READ) } val job2 = async { selector.select(client, SelectionKey.OP_WRITE) } Thread.sleep(100) server.accept().write(ByteBuffer.wrap(byteArrayOf(1))) job2.await() job1.await() } @UseExperimental(ExperimentalCoroutinesApi::class) @Test fun shouldCancelOutstanding() = runBlocking { val server = ServerSocketChannel.open() server.bind(InetSocketAddress(0)) val client = SocketChannel.open() client.connect(server.localAddress) server.accept() client.configureBlocking(false) server.configureBlocking(false) val selector = CoroutineSelector.open(loggerProvider = SimpleLogger.toOutputStream(System.err)) assertFalse(selector.cancelSelections(client)) val job1 = async(start = CoroutineStart.UNDISPATCHED) { selector.select(client, SelectionKey.OP_READ) } val job2 = async(start = CoroutineStart.UNDISPATCHED) { selector.select(client, SelectionKey.OP_WRITE) } val job3 = async(start = CoroutineStart.UNDISPATCHED) { selector.select(server, SelectionKey.OP_ACCEPT) } job2.await() Thread.sleep(100) selector.cancelSelections(client) assertThrows { runBlocking { job1.await() } } assertFalse(job3.isCompleted) SocketChannel.open().connect(server.localAddress) job3.await() } @Test fun shouldThrowWhenSelectingClosedChannel() { val pipe = Pipe.open() pipe.source().configureBlocking(false) val selector = CoroutineSelector.open(loggerProvider = SimpleLogger.toOutputStream(System.err)) pipe.source().close() assertThrows { runBlocking { selector.select(pipe.source(), SelectionKey.OP_READ) } } } @Test fun shouldAwakenOnChannelClose() = runBlocking { val pipe1 = Pipe.open() pipe1.source().configureBlocking(false) val pipe2 = Pipe.open() pipe2.source().configureBlocking(false) val selector = CoroutineSelector.open(loggerProvider = SimpleLogger.toOutputStream(System.err)) val job1 = async { selector.select(pipe1.source(), SelectionKey.OP_READ) fail("should not be reached") } val job2 = async { selector.select(pipe2.source(), SelectionKey.OP_READ) fail("should not be reached") } Thread.sleep(100) pipe1.source().close() assertThrows { runBlocking { job1.await() } } Thread.sleep(100) pipe2.source().close() assertThrows { runBlocking { job2.await() } } } @Test fun shouldAwakenOnSelectorClose() = runBlocking { val pipe1 = Pipe.open() pipe1.source().configureBlocking(false) val pipe2 = Pipe.open() pipe2.source().configureBlocking(false) val selector = CoroutineSelector.open(loggerProvider = SimpleLogger.toOutputStream(System.err)) val job1 = async { selector.select(pipe1.source(), SelectionKey.OP_READ) fail("should not be reached") } val job2 = async { selector.select(pipe2.source(), SelectionKey.OP_READ) fail("should not be reached") } Thread.sleep(100) selector.close() assertThrows { runBlocking { job1.await() } } assertThrows { runBlocking { job2.await() } } } } CoroutineSocketChannelTest.kt000066400000000000000000000102041341750772100347150ustar00rootroot00000000000000cava-0.6.0/net-coroutines/src/test/kotlin/net/consensys/cava/net/coroutines/* * Copyright 2018 ConsenSys AG. * * 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.net.coroutines import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import java.net.InetAddress import java.net.InetSocketAddress import java.nio.ByteBuffer import java.nio.charset.StandardCharsets.UTF_8 internal class CoroutineSocketChannelTest { @Test fun shouldSuspendServerSocketChannelWhileAccepting() = runBlocking { val listenChannel = CoroutineServerSocketChannel.open() Assertions.assertNull(listenChannel.localAddress) assertEquals(0, listenChannel.localPort) listenChannel.bind(null) assertNotNull(listenChannel.localAddress) assertTrue(listenChannel.localPort > 0) val addr = InetSocketAddress(InetAddress.getLocalHost(), listenChannel.localPort) var didBlock = false val job = async { val serverChannel = listenChannel.accept() assertNotNull(serverChannel) assertTrue(didBlock) } Thread.sleep(100) didBlock = true val clientChannel = CoroutineSocketChannel.open() clientChannel.connect(addr) job.await() } @Test fun shouldBlockSocketChannelWhileReading() = runBlocking { val listenChannel = CoroutineServerSocketChannel.open() listenChannel.bind(null) val addr = InetSocketAddress(InetAddress.getLocalHost(), (listenChannel.localAddress as InetSocketAddress).port) val serverJob = async { val serverChannel = listenChannel.accept() assertNotNull(serverChannel) assertTrue(serverChannel.isConnected) val dst = ByteBuffer.allocate(1024) serverChannel.read(dst) dst.flip() val chars = ByteArray(dst.limit()) dst.get(chars, 0, dst.limit()) assertEquals("testing123456", String(chars, UTF_8)) serverChannel.write(ByteBuffer.wrap("654321abcdefg".toByteArray(UTF_8))) serverChannel.close() } val clientJob = async { val clientChannel = CoroutineSocketChannel.open() clientChannel.connect(addr) clientChannel.write(ByteBuffer.wrap("testing123456".toByteArray(UTF_8))) val dst = ByteBuffer.allocate(1024) clientChannel.read(dst) dst.flip() val chars = ByteArray(dst.limit()) dst.get(chars, 0, dst.limit()) assertEquals("654321abcdefg", String(chars, UTF_8)) clientChannel.close() } serverJob.await() clientJob.await() } @Test fun shouldCloseSocketChannelWhenRemoteClosed() = runBlocking { val listenChannel = CoroutineServerSocketChannel.open() listenChannel.bind(null) val addr = InetSocketAddress(InetAddress.getLocalHost(), (listenChannel.localAddress as InetSocketAddress).port) val serverJob = async { val serverChannel = listenChannel.accept() assertNotNull(serverChannel) assertTrue(serverChannel.isConnected) val dst = ByteBuffer.allocate(1024) serverChannel.read(dst) dst.flip() val chars = ByteArray(dst.limit()) dst.get(chars, 0, dst.limit()) assertEquals("testing123456", String(chars, UTF_8)) serverChannel.close() } val clientJob = async { val clientChannel = CoroutineSocketChannel.open() clientChannel.connect(addr) clientChannel.write(ByteBuffer.wrap("testing123456".toByteArray(UTF_8))) val dst = ByteBuffer.allocate(1024) assertTrue(clientChannel.read(dst) < 0) clientChannel.close() } serverJob.await() clientJob.await() } } cava-0.6.0/net/000077500000000000000000000000001341750772100132245ustar00rootroot00000000000000cava-0.6.0/net/build.gradle000066400000000000000000000013021341750772100154770ustar00rootroot00000000000000description = 'Classes and utilities for working with networking.' dependencies { compile project(':bytes') compile project(':crypto') compile project(':io') compile 'com.google.guava:guava' compileOnly 'io.vertx:vertx-core' compileOnly 'org.bouncycastle:bcprov-jdk15on' compileOnly 'org.bouncycastle:bcpkix-jdk15on' testCompile project(':junit') testCompile 'com.squareup.okhttp3:okhttp' testCompile 'io.vertx:vertx-core' testCompile 'org.bouncycastle:bcprov-jdk15on' testCompile 'org.bouncycastle:bcpkix-jdk15on' testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/net/src/000077500000000000000000000000001341750772100140135ustar00rootroot00000000000000cava-0.6.0/net/src/main/000077500000000000000000000000001341750772100147375ustar00rootroot00000000000000cava-0.6.0/net/src/main/java/000077500000000000000000000000001341750772100156605ustar00rootroot00000000000000cava-0.6.0/net/src/main/java/net/000077500000000000000000000000001341750772100164465ustar00rootroot00000000000000cava-0.6.0/net/src/main/java/net/consensys/000077500000000000000000000000001341750772100204725ustar00rootroot00000000000000cava-0.6.0/net/src/main/java/net/consensys/cava/000077500000000000000000000000001341750772100214045ustar00rootroot00000000000000cava-0.6.0/net/src/main/java/net/consensys/cava/net/000077500000000000000000000000001341750772100221725ustar00rootroot00000000000000cava-0.6.0/net/src/main/java/net/consensys/cava/net/package-info.java000066400000000000000000000004111341750772100253550ustar00rootroot00000000000000/** * 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; cava-0.6.0/net/src/main/java/net/consensys/cava/net/tls/000077500000000000000000000000001341750772100227745ustar00rootroot00000000000000cava-0.6.0/net/src/main/java/net/consensys/cava/net/tls/ClientFingerprintTrustManager.java000066400000000000000000000112421341750772100316220ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static java.lang.String.format; import static net.consensys.cava.net.tls.TLS.certificateFingerprint; import net.consensys.cava.bytes.Bytes; import java.net.Socket; import java.nio.file.Path; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.SSLEngine; import javax.net.ssl.X509ExtendedTrustManager; import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.asn1.x500.style.IETFUtils; import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; final class ClientFingerprintTrustManager extends X509ExtendedTrustManager { private static final X509Certificate[] EMPTY_X509_CERTIFICATES = new X509Certificate[0]; static ClientFingerprintTrustManager record(Path repository) { return new ClientFingerprintTrustManager(repository, true, true); } static ClientFingerprintTrustManager tofa(Path repository) { return new ClientFingerprintTrustManager(repository, true, false); } static ClientFingerprintTrustManager whitelist(Path repository) { return new ClientFingerprintTrustManager(repository, false, false); } private final FingerprintRepository repository; private final boolean acceptNewFingerprints; private final boolean updateFingerprints; private ClientFingerprintTrustManager(Path repository, boolean acceptNewFingerprints, boolean updateFingerprints) { this.repository = new FingerprintRepository(repository); this.acceptNewFingerprints = acceptNewFingerprints; this.updateFingerprints = updateFingerprints; } @Override public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { X509Certificate cert = chain[0]; X500Name x500name = new JcaX509CertificateHolder(cert).getSubject(); RDN cn = x500name.getRDNs(BCStyle.CN)[0]; String hostname = IETFUtils.valueToString(cn.getFirst().getValue()); checkTrusted(chain, hostname); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) { throw new UnsupportedOperationException(); } @Override public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { X509Certificate cert = chain[0]; X500Name x500name = new JcaX509CertificateHolder(cert).getSubject(); RDN cn = x500name.getRDNs(BCStyle.CN)[0]; String hostname = IETFUtils.valueToString(cn.getFirst().getValue()); checkTrusted(chain, hostname); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) { throw new UnsupportedOperationException(); } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { throw new UnsupportedOperationException(); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { throw new UnsupportedOperationException(); } private void checkTrusted(X509Certificate[] chain, String host) throws CertificateException { X509Certificate cert = chain[0]; Bytes fingerprint = Bytes.wrap(certificateFingerprint(cert)); if (repository.contains(host, fingerprint)) { return; } if (repository.contains(host)) { if (!updateFingerprints) { throw new CertificateException( format( "Client identification has changed!!" + " Certificate for %s (%s) has fingerprint %s", host, cert.getSubjectDN(), fingerprint.toHexString().substring(2).toLowerCase())); } } else if (!acceptNewFingerprints) { throw new CertificateException( format( "Certificate for %s (%s) has unknown fingerprint %s", host, cert.getSubjectDN(), fingerprint.toHexString().substring(2).toLowerCase())); } repository.addFingerprint(host, fingerprint); } @Override public X509Certificate[] getAcceptedIssuers() { return EMPTY_X509_CERTIFICATES; } } cava-0.6.0/net/src/main/java/net/consensys/cava/net/tls/DelegatingTrustManagerFactory.java000066400000000000000000000166651341750772100316050ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static java.util.Objects.requireNonNull; import java.net.Socket; import java.security.KeyStore; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.ManagerFactoryParameters; import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedTrustManager; import javax.net.ssl.X509TrustManager; import io.netty.handler.ssl.util.SimpleTrustManagerFactory; final class DelegatingTrustManagerFactory extends SimpleTrustManagerFactory { private static final X509Certificate[] EMPTY_X509_CERTIFICATES = new X509Certificate[0]; private final TrustManagerFactory delegate; private final X509TrustManager fallback; private final TrustManager[] trustManagers; DelegatingTrustManagerFactory(TrustManagerFactory delegate, X509TrustManager fallback) { requireNonNull(delegate); requireNonNull(fallback); this.delegate = delegate; this.fallback = fallback; this.trustManagers = new TrustManager[] {new DelegatingTrustManager()}; } @Override protected void engineInit(KeyStore keyStore) throws Exception { delegate.init(keyStore); } @Override protected void engineInit(ManagerFactoryParameters managerFactoryParameters) throws Exception { delegate.init(managerFactoryParameters); } @Override protected TrustManager[] engineGetTrustManagers() { return trustManagers; } private class DelegatingTrustManager extends X509ExtendedTrustManager { @Override public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { CertificateException caException = null; try { for (TrustManager trustManager : delegate.getTrustManagers()) { if (trustManager instanceof X509ExtendedTrustManager) { ((X509ExtendedTrustManager) trustManager).checkClientTrusted(chain, authType, socket); return; } } } catch (CertificateException e) { caException = e; } try { if (fallback instanceof X509ExtendedTrustManager) { ((X509ExtendedTrustManager) fallback).checkClientTrusted(chain, authType, socket); } else { fallback.checkClientTrusted(chain, authType); } } catch (CertificateException e) { if (caException != null) { e.addSuppressed(caException); } throw e; } } @Override public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { CertificateException caException = null; try { for (TrustManager trustManager : delegate.getTrustManagers()) { if (trustManager instanceof X509ExtendedTrustManager) { ((X509ExtendedTrustManager) trustManager).checkServerTrusted(chain, authType, socket); return; } } } catch (CertificateException e) { caException = e; } try { if (fallback instanceof X509ExtendedTrustManager) { ((X509ExtendedTrustManager) fallback).checkServerTrusted(chain, authType, socket); } else { fallback.checkServerTrusted(chain, authType); } } catch (CertificateException e) { if (caException != null) { e.addSuppressed(caException); } throw e; } } @Override public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { CertificateException caException = null; try { for (TrustManager trustManager : delegate.getTrustManagers()) { if (trustManager instanceof X509ExtendedTrustManager) { ((X509ExtendedTrustManager) trustManager).checkClientTrusted(chain, authType, engine); return; } } } catch (CertificateException e) { caException = e; } try { if (fallback instanceof X509ExtendedTrustManager) { ((X509ExtendedTrustManager) fallback).checkClientTrusted(chain, authType, engine); } else { fallback.checkClientTrusted(chain, authType); } } catch (CertificateException e) { if (caException != null) { e.addSuppressed(caException); } throw e; } } @Override public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { CertificateException caException = null; try { for (TrustManager trustManager : delegate.getTrustManagers()) { if (trustManager instanceof X509ExtendedTrustManager) { ((X509ExtendedTrustManager) trustManager).checkServerTrusted(chain, authType, engine); return; } } } catch (CertificateException e) { caException = e; } try { if (fallback instanceof X509ExtendedTrustManager) { ((X509ExtendedTrustManager) fallback).checkServerTrusted(chain, authType, engine); } else { fallback.checkServerTrusted(chain, authType); } } catch (CertificateException e) { if (caException != null) { e.addSuppressed(caException); } throw e; } } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { CertificateException caException = null; try { for (TrustManager trustManager : delegate.getTrustManagers()) { if (trustManager instanceof X509TrustManager) { ((X509TrustManager) trustManager).checkClientTrusted(chain, authType); return; } } } catch (CertificateException e) { caException = e; } try { fallback.checkClientTrusted(chain, authType); } catch (CertificateException e) { if (caException != null) { e.addSuppressed(caException); } throw e; } } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { CertificateException caException = null; try { for (TrustManager trustManager : delegate.getTrustManagers()) { if (trustManager instanceof X509TrustManager) { ((X509TrustManager) trustManager).checkServerTrusted(chain, authType); return; } } } catch (CertificateException e) { caException = e; } try { fallback.checkServerTrusted(chain, authType); } catch (CertificateException e) { if (caException != null) { e.addSuppressed(caException); } throw e; } } @Override public X509Certificate[] getAcceptedIssuers() { return EMPTY_X509_CERTIFICATES; } } } cava-0.6.0/net/src/main/java/net/consensys/cava/net/tls/FingerprintRepository.java000066400000000000000000000136501341750772100302330ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static java.nio.file.Files.createDirectories; import static net.consensys.cava.io.file.Files.atomicReplace; import static net.consensys.cava.io.file.Files.createFileIfMissing; import net.consensys.cava.bytes.Bytes; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.AbstractMap.SimpleImmutableEntry; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; final class FingerprintRepository { private final Path fingerprintFile; private volatile Map fingerprints; FingerprintRepository(Path fingerprintFile) { try { createDirectories(fingerprintFile.getParent()); createFileIfMissing(fingerprintFile); } catch (IOException e) { throw new TLSEnvironmentException("Cannot create fingerprint file " + fingerprintFile, e); } this.fingerprintFile = fingerprintFile; this.fingerprints = parseFingerprintFile(fingerprintFile); } boolean contains(String identifier) { return fingerprints.containsKey(identifier); } boolean contains(String identifier, Bytes fingerprint) { return fingerprint.equals(fingerprints.get(identifier)); } void addFingerprint(String identifier, Bytes fingerprint) { if (!contains(identifier, fingerprint)) { synchronized (this) { if (!contains(identifier, fingerprint)) { // put into a copy first, then atomically replace HashMap fingerprintsCopy = new HashMap<>(fingerprints); fingerprintsCopy.put(identifier, fingerprint); fingerprints = writeFingerprintFile(fingerprintFile, fingerprintsCopy); } } } } private static Map parseFingerprintFile(Path fingerprintFile) { List lines; try { lines = Files.readAllLines(fingerprintFile); } catch (IOException e) { throw new TLSEnvironmentException("Cannot read fingerprint file " + fingerprintFile, e); } Map fingerprints = new HashMap<>(); for (int i = 0; i < lines.size(); ++i) { String line = lines.get(i).trim(); if (line.isEmpty() || line.startsWith("#")) { continue; } Entry entry; try { entry = parseLine(line); } catch (IOException e) { throw new TLSEnvironmentException(e.getMessage() + " in " + fingerprintFile + " (line " + (i + 1) + ")"); } fingerprints.put(entry.getKey(), entry.getValue()); } return Collections.unmodifiableMap(fingerprints); } private static Map writeFingerprintFile(Path fingerprintFile, Map updatedFingerprints) { List lines; try { lines = Files.readAllLines(fingerprintFile); } catch (IOException e) { throw new TLSEnvironmentException("Cannot read fingerprint file " + fingerprintFile, e); } Map fingerprints = new HashMap<>(); HashSet updatedIdentifiers = new HashSet<>(updatedFingerprints.keySet()); try { atomicReplace(fingerprintFile, writer -> { // copy lines, replacing any updated fingerprints for (int i = 0; i < lines.size(); ++i) { String line = lines.get(i).trim(); if (line.isEmpty() || line.startsWith("#")) { writer.write(lines.get(i)); writer.write(System.lineSeparator()); continue; } Entry entry; try { entry = parseLine(line); } catch (IOException e) { throw new TLSEnvironmentException(e.getMessage() + " in " + fingerprintFile + " (line " + (i + 1) + ")"); } String identifier = entry.getKey(); Bytes fingerprint = updatedFingerprints.getOrDefault(identifier, entry.getValue()); fingerprints.put(identifier, fingerprint); updatedIdentifiers.remove(identifier); writer.write(identifier); writer.write(' '); writer.write(fingerprint.toHexString().substring(2).toLowerCase()); writer.write(System.lineSeparator()); } // write any new fingerprints at the end for (String identifier : updatedIdentifiers) { Bytes fingerprint = updatedFingerprints.get(identifier); fingerprints.put(identifier, fingerprint); writer.write(identifier); writer.write(' '); writer.write(fingerprint.toHexString().substring(2).toLowerCase()); writer.write(System.lineSeparator()); } }); return Collections.unmodifiableMap(fingerprints); } catch (IOException e) { throw new TLSEnvironmentException("Cannot write fingerprint file " + fingerprintFile, e); } } private static Entry parseLine(String line) throws IOException { String[] segments = line.split("\\s+", 2); if (segments.length != 2) { throw new IOException("Invalid line"); } String identifier = segments[0].toLowerCase(); String fingerprintString = segments[1].trim().replace(":", ""); Bytes fingerprint; try { fingerprint = Bytes.fromHexString(fingerprintString); } catch (IllegalArgumentException e) { throw new IOException("Invalid fingerprint", e); } return new SimpleImmutableEntry<>(identifier, fingerprint); } } cava-0.6.0/net/src/main/java/net/consensys/cava/net/tls/ServerFingerprintTrustManager.java000066400000000000000000000107671341750772100316650ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static java.lang.String.format; import static net.consensys.cava.net.tls.TLS.certificateFingerprint; import net.consensys.cava.bytes.Bytes; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.file.Path; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.SSLEngine; import javax.net.ssl.X509ExtendedTrustManager; final class ServerFingerprintTrustManager extends X509ExtendedTrustManager { private static final X509Certificate[] EMPTY_X509_CERTIFICATES = new X509Certificate[0]; static ServerFingerprintTrustManager record(Path repository) { return new ServerFingerprintTrustManager(repository, true, true); } static ServerFingerprintTrustManager tofu(Path repository) { return new ServerFingerprintTrustManager(repository, true, false); } static ServerFingerprintTrustManager whitelist(Path repository) { return new ServerFingerprintTrustManager(repository, false, false); } private final FingerprintRepository repository; private final boolean acceptNewFingerprints; private final boolean updateFingerprints; private ServerFingerprintTrustManager(Path repository, boolean acceptNewFingerprints, boolean updateFingerprints) { this.repository = new FingerprintRepository(repository); this.acceptNewFingerprints = acceptNewFingerprints; this.updateFingerprints = updateFingerprints; } @Override public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { throw new UnsupportedOperationException(); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { InetSocketAddress socketAddress = (InetSocketAddress) socket.getRemoteSocketAddress(); checkTrusted(chain, socketAddress.getHostName(), socketAddress.getPort()); } @Override public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { throw new UnsupportedOperationException(); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { checkTrusted(chain, engine.getPeerHost(), engine.getPeerPort()); } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { throw new UnsupportedOperationException(); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { throw new UnsupportedOperationException(); } private void checkTrusted(X509Certificate[] chain, String host, int port) throws CertificateException { X509Certificate cert = chain[0]; String identifier = hostIdentifier(host, port); Bytes fingerprint = Bytes.wrap(certificateFingerprint(cert)); if (repository.contains(identifier, fingerprint)) { return; } if (repository.contains(identifier)) { if (!updateFingerprints) { throw new CertificateException( format( "Remote host identification has changed!!" + " Certificate for %s (%s) has fingerprint %s", identifier, cert.getSubjectDN(), fingerprint.toHexString().substring(2).toLowerCase())); } } else if (!acceptNewFingerprints) { throw new CertificateException( format( "Certificate for %s (%s) has unknown fingerprint %s", identifier, cert.getSubjectDN(), fingerprint.toHexString().substring(2).toLowerCase())); } repository.addFingerprint(identifier, fingerprint); } @Override public X509Certificate[] getAcceptedIssuers() { return EMPTY_X509_CERTIFICATES; } private String hostIdentifier(String host, int port) { return host.trim().toLowerCase() + ":" + port; } } cava-0.6.0/net/src/main/java/net/consensys/cava/net/tls/SingleTrustManagerFactory.java000066400000000000000000000024201341750772100307430ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import java.security.KeyStore; import javax.net.ssl.ManagerFactoryParameters; import javax.net.ssl.TrustManager; import io.netty.handler.ssl.util.SimpleTrustManagerFactory; final class SingleTrustManagerFactory extends SimpleTrustManagerFactory { private final TrustManager[] trustManagers; SingleTrustManagerFactory(TrustManager trustManager) { this.trustManagers = new TrustManager[] {trustManager}; } @Override protected void engineInit(KeyStore keyStore) {} @Override protected void engineInit(ManagerFactoryParameters managerFactoryParameters) {} @Override protected TrustManager[] engineGetTrustManagers() { return trustManagers; } } cava-0.6.0/net/src/main/java/net/consensys/cava/net/tls/TLS.java000066400000000000000000000165061341750772100243110ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.file.Files.createDirectories; import static net.consensys.cava.crypto.Hash.sha2_256; import net.consensys.cava.bytes.Bytes; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.math.BigInteger; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Calendar; import java.util.Date; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemReader; import org.bouncycastle.util.io.pem.PemWriter; /** * Common utilities for TLS. * *

* This class depends upon the BouncyCastle library being available and added as a {@link java.security.Provider}. See * https://www.bouncycastle.org/wiki/display/JA1/Provider+Installation. * *

* BouncyCastle can be included using the gradle dependencies org.bouncycastle:bcprov-jdk15on and * org.bouncycastle:bcpkix-jdk15on. */ public final class TLS { private TLS() {} /** * Create a self-signed certificate, if it is not already present. * *

* If both the key or the certificate file are missing, they will be re-created as a self-signed certificate. * * @param key The key path. * @param certificate The certificate path. * @return {@code true} if a self-signed certificate was created. * @throws IOException If an IO error occurs creating the certificate. */ public static boolean createSelfSignedCertificateIfMissing(Path key, Path certificate) throws IOException { if (Files.exists(certificate) || Files.exists(key)) { return false; } createDirectories(certificate.getParent()); createDirectories(key.getParent()); Path keyFile = Files.createTempFile(key.getParent(), "client-key", ".tmp"); Path certFile = Files.createTempFile(certificate.getParent(), "client-cert", ".tmp"); try { createSelfSignedCertificate(new Date(), keyFile, certFile); } catch (CertificateException | NoSuchAlgorithmException | OperatorCreationException e) { throw new TLSEnvironmentException("Could not generate certificate: " + e.getMessage(), e); } Files.move(keyFile, key, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); Files.move(certFile, certificate, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); return true; } private static void createSelfSignedCertificate(Date now, Path key, Path certificate) throws NoSuchAlgorithmException, IOException, OperatorCreationException, CertificateException { KeyPairGenerator rsa = KeyPairGenerator.getInstance("RSA"); rsa.initialize(2048, new SecureRandom()); KeyPair keyPair = rsa.generateKeyPair(); Calendar cal = Calendar.getInstance(); cal.setTime(now); cal.add(Calendar.YEAR, 1); Date yearFromNow = cal.getTime(); X500Name dn = new X500Name("CN=example.com"); X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder( dn, new BigInteger(64, new SecureRandom()), now, yearFromNow, dn, keyPair.getPublic()); ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider("BC").build(keyPair.getPrivate()); X509Certificate x509Certificate = new JcaX509CertificateConverter().setProvider("BC").getCertificate(builder.build(signer)); try (BufferedWriter writer = Files.newBufferedWriter(key, UTF_8); PemWriter pemWriter = new PemWriter(writer)) { pemWriter.writeObject(new PemObject("PRIVATE KEY", keyPair.getPrivate().getEncoded())); } try (BufferedWriter writer = Files.newBufferedWriter(certificate, UTF_8); PemWriter pemWriter = new PemWriter(writer)) { pemWriter.writeObject(new PemObject("CERTIFICATE", x509Certificate.getEncoded())); } } /** * Read a PEM-encoded file. * * @param certificate The path to a PEM-encoded file. * @return The bytes for the PEM content. * @throws IOException If an IO error occurs. */ public static byte[] readPemFile(Path certificate) throws IOException { try (BufferedReader reader = Files.newBufferedReader(certificate, UTF_8); PemReader pemReader = new PemReader(reader)) { PemObject pemObject = pemReader.readPemObject(); return pemObject.getContent(); } } /** * Calculate the fingerprint for a PEM-encoded certificate. * * @param certificate The path to a PEM-encoded certificate. * @return The fingerprint bytes for the certificate. * @throws IOException If an IO error occurs. */ public static byte[] certificateFingerprint(Path certificate) throws IOException { return sha2_256(readPemFile(certificate)); } /** * Calculate the fingerprint for a PEM-encoded certificate. * * @param certificate The path to a PEM-encoded certificate. * @return The fingerprint hex-string for the certificate. * @throws IOException If an IO error occurs. */ public static String certificateHexFingerprint(Path certificate) throws IOException { return Bytes.wrap(certificateFingerprint(certificate)).toHexString().substring(2).toLowerCase(); } /** * Calculate the fingerprint for certificate. * * @param certificate The certificate. * @return The fingerprint bytes for the certificate. * @throws CertificateEncodingException If the certificate cannot be encoded. */ public static byte[] certificateFingerprint(Certificate certificate) throws CertificateEncodingException { return sha2_256(certificate.getEncoded()); } /** * Calculate the fingerprint for certificate. * * @param certificate The certificate. * @return The fingerprint hex-string for the certificate. * @throws CertificateEncodingException If the certificate cannot be encoded. */ public static String certificateHexFingerprint(Certificate certificate) throws CertificateEncodingException { return Bytes.wrap(certificateFingerprint(certificate)).toHexString().substring(2).toLowerCase(); } } cava-0.6.0/net/src/main/java/net/consensys/cava/net/tls/TLSEnvironmentException.java000066400000000000000000000015201341750772100304030ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; final class TLSEnvironmentException extends RuntimeException { TLSEnvironmentException(String message) { super(message); } TLSEnvironmentException(String message, Throwable cause) { super(message, cause); } } cava-0.6.0/net/src/main/java/net/consensys/cava/net/tls/TrustManagerFactories.java000066400000000000000000000416071341750772100301230ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static java.util.Objects.requireNonNull; import java.nio.file.Path; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import javax.annotation.Nullable; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; /** * Trust manager factories for fingerprinting clients and servers. */ public final class TrustManagerFactories { private TrustManagerFactories() {} /** * Accept all server certificates, recording certificate fingerprints for those that are not CA-signed. * *

* Excepting when a server presents a CA-signed certificate, the server host+port and the certificate fingerprint will * be written to {@code knownServersFile}. * *

* Important: this provides no security as it is vulnerable to man-in-the-middle attacks. * * @param knownServersFile The path to a file in which to record fingerprints by host. * @return A trust manager factory. */ public static TrustManagerFactory recordServerFingerprints(Path knownServersFile) { requireNonNull(knownServersFile); return recordServerFingerprints(knownServersFile, true); } /** * Accept all server certificates, recording certificate fingerprints. * *

* For all connections, the server host+port and the fingerprint of the presented certificate will be written to * {@code knownServersFile}. * *

* Important: this provides no security as it is vulnerable to man-in-the-middle attacks. * * @param knownServersFile The path to a file in which to record fingerprints by host. * @param skipCASigned If {@code true}, CA-signed certificates are not recorded. * @return A trust manager factory. */ public static TrustManagerFactory recordServerFingerprints(Path knownServersFile, boolean skipCASigned) { requireNonNull(knownServersFile); return wrap(ServerFingerprintTrustManager.record(knownServersFile), skipCASigned); } /** * Accept all server certificates, recording certificate fingerprints for those that are not CA-signed. * *

* Excepting when a server presents a CA-signed certificate, the server host+port and the certificate fingerprint will * be written to {@code knownServersFile}. * *

* Important: this provides no security as it is vulnerable to man-in-the-middle attacks. * * @param knownServersFile The path to a file in which to record fingerprints by host. * @param tmf A {@link TrustManagerFactory} for checking server certificates against a CA. * @return A trust manager factory. */ public static TrustManagerFactory recordServerFingerprints(Path knownServersFile, TrustManagerFactory tmf) { requireNonNull(knownServersFile); requireNonNull(tmf); return wrap(ServerFingerprintTrustManager.record(knownServersFile), tmf); } /** * Accept CA-signed certificates, and otherwise trust server certificates on first use. * *

* Except when a server presents a CA-signed certificate, on first connection to a server (identified by host+port) * the fingerprint of the presented certificate will be recorded in {@code knownServersFile}. On subsequent * connections, the presented certificate will be matched to the stored fingerprint to ensure it has not changed. * * @param knownServersFile The path to the file containing fingerprints by host. * @return A trust manager factory. */ public static TrustManagerFactory trustServerOnFirstUse(Path knownServersFile) { requireNonNull(knownServersFile); return trustServerOnFirstUse(knownServersFile, true); } /** * Trust server certificates on first use. * *

* On first connection to a server (identified by host+port) the fingerprint of the presented certificate will be * recorded in {@code knownServersFile}. On subsequent connections, the presented certificate will be matched to the * stored fingerprint to ensure it has not changed. * * @param knownServersFile The path to the file containing fingerprints by host. * @param acceptCASigned If {@code true}, CA-signed certificates will always be accepted (and the fingerprint will not * be recorded). * @return A trust manager factory. */ public static TrustManagerFactory trustServerOnFirstUse(Path knownServersFile, boolean acceptCASigned) { requireNonNull(knownServersFile); return wrap(ServerFingerprintTrustManager.tofu(knownServersFile), acceptCASigned); } /** * Accept CA-signed certificates, and otherwise trust server certificates on first use. * *

* Except when a server presents a CA-signed certificate, on first connection to a server (identified by host+port) * the fingerprint of the presented certificate will be recorded in {@code knownServersFile}. On subsequent * connections, the presented certificate will be matched to the stored fingerprint to ensure it has not changed. * * @param knownServersFile The path to the file containing fingerprints by host. * @param tmf A {@link TrustManagerFactory} for checking server certificates against a CA. * @return A trust manager factory. */ public static TrustManagerFactory trustServerOnFirstUse(Path knownServersFile, TrustManagerFactory tmf) { requireNonNull(knownServersFile); requireNonNull(tmf); return wrap(ServerFingerprintTrustManager.tofu(knownServersFile), tmf); } /** * Require servers to present known certificates, or CA-signed certificates. * *

* If a certificate is not CA-signed, then its fingerprint must be present in the {@code knownServersFile}, associated * with the server (identified by host+port). * * @param knownServersFile The path to the file containing fingerprints by host. * @return A trust manager factory. */ public static TrustManagerFactory whitelistServers(Path knownServersFile) { requireNonNull(knownServersFile); return whitelistServers(knownServersFile, true); } /** * Require servers to present known certificates. * *

* The fingerprint for a server certificate must be present in the {@code knownServersFile}, associated with the * server (identified by host+port). * * @param knownServersFile The path to the file containing fingerprints by host. * @param acceptCASigned If {@code true}, CA-signed certificates will always be accepted. * @return A trust manager factory. */ public static TrustManagerFactory whitelistServers(Path knownServersFile, boolean acceptCASigned) { requireNonNull(knownServersFile); return wrap(ServerFingerprintTrustManager.whitelist(knownServersFile), acceptCASigned); } /** * Require servers to present known certificates, or CA-signed certificates. * *

* If a certificate is not CA-signed, then its fingerprint must be present in the {@code knownServersFile}, associated * with the server (identified by host+port). * * @param knownServersFile The path to the file containing fingerprints by host. * @param tmf A {@link TrustManagerFactory} for checking server certificates against a CA. * @return A trust manager factory. */ public static TrustManagerFactory whitelistServers(Path knownServersFile, TrustManagerFactory tmf) { requireNonNull(knownServersFile); requireNonNull(tmf); return wrap(ServerFingerprintTrustManager.whitelist(knownServersFile), tmf); } /** * Accept all client certificates, recording certificate fingerprints for those that are not CA-signed. * *

* Excepting when a client presents a CA-signed certificate, the certificate fingerprint will be written to * {@code knownClientsFile}. * *

* Important: this provides no security as it is vulnerable to man-in-the-middle attacks. * * @param knownClientsFile The path to a file in which to record fingerprints. * @return A trust manager factory. */ public static TrustManagerFactory recordClientFingerprints(Path knownClientsFile) { requireNonNull(knownClientsFile); return recordClientFingerprints(knownClientsFile, true); } /** * Accept all client certificates, recording certificate fingerprints. * *

* For all connections, the fingerprint of the presented certificate will be written to {@code knownClientsFile}. * *

* Important: this provides no security as it is vulnerable to man-in-the-middle attacks. * * @param knownClientsFile The path to a file in which to record fingerprints. * @param skipCASigned If {@code true}, CA-signed certificates are not recorded. * @return A trust manager factory. */ public static TrustManagerFactory recordClientFingerprints(Path knownClientsFile, boolean skipCASigned) { requireNonNull(knownClientsFile); return wrap(ClientFingerprintTrustManager.record(knownClientsFile), skipCASigned); } /** * Accept all client certificates, recording certificate fingerprints for those that are not CA-signed. * *

* Excepting when a client presents a CA-signed certificate, the certificate fingerprint will be written to * {@code knownClientsFile}. * *

* Important: this provides no security as it is vulnerable to man-in-the-middle attacks. * * @param knownClientsFile The path to a file in which to record fingerprints. * @param tmf A {@link TrustManagerFactory} for checking server certificates against a CA. * @return A trust manager factory. */ public static TrustManagerFactory recordClientFingerprints(Path knownClientsFile, TrustManagerFactory tmf) { requireNonNull(knownClientsFile); requireNonNull(tmf); return wrap(ClientFingerprintTrustManager.record(knownClientsFile), tmf); } /** * Accept CA-signed client certificates, and otherwise trust client certificates on first access. * *

* Except when a client presents a CA-signed certificate, on first connection to this server the common name and * fingerprint of the presented certificate will be recorded. On subsequent connections, the client will be rejected * if the fingerprint has changed. * *

* Note: unlike the seemingly equivalent {@link #trustServerOnFirstUse(Path)} method for authenticating servers, * this method for authenticating clients is insecure and provides zero confidence in client identity. * Unlike the server version, which bases the identity on the hostname and port the connection is being established * to, the client version only uses the common name of the certificate that the connecting client presents. Therefore, * clients can circumvent access control by using a different common name from any previously recorded client. * * @param knownClientsFile The path to the file containing fingerprints. * @return A trust manager factory. */ public static TrustManagerFactory trustClientOnFirstAccess(Path knownClientsFile) { requireNonNull(knownClientsFile); return trustClientOnFirstAccess(knownClientsFile, true); } /** * Trust client certificates on first access. * *

* on first connection to this server the common name and fingerprint of the presented certificate will be recorded. * On subsequent connections, the client will be rejected if the fingerprint has changed. * *

* Note: unlike the seemingly equivalent {@link #trustServerOnFirstUse(Path)} method for authenticating servers, * this method for authenticating clients is insecure and provides zero confidence in client identity. * Unlike the server version, which bases the identity on the hostname and port the connection is being established * to, the client version only uses the common name of the certificate that the connecting client presents. Therefore, * clients can circumvent access control by using a different common name from any previously recorded client. * * @param knownClientsFile The path to the file containing fingerprints. * @param acceptCASigned If {@code true}, CA-signed certificates will always be accepted. * @return A trust manager factory. */ public static TrustManagerFactory trustClientOnFirstAccess(Path knownClientsFile, boolean acceptCASigned) { requireNonNull(knownClientsFile); return wrap(ClientFingerprintTrustManager.tofa(knownClientsFile), acceptCASigned); } /** * Accept CA-signed certificates, and otherwise trust client certificates on first access. * *

* Except when a client presents a CA-signed certificate, on first connection to this server the common name and * fingerprint of the presented certificate will be recorded. On subsequent connections, the client will be rejected * if the fingerprint has changed. * *

* Note: unlike the seemingly equivalent {@link #trustServerOnFirstUse(Path)} method for authenticating servers, * this method for authenticating clients is insecure and provides zero confidence in client identity. * Unlike the server version, which bases the identity on the hostname and port the connection is being established * to, the client version only uses the common name of the certificate that the connecting client presents. Therefore, * clients can circumvent access control by using a different common name from any previously recorded client. * * @param knownClientsFile The path to the file containing fingerprints. * @param tmf A {@link TrustManagerFactory} for checking server certificates against a CA. * @return A trust manager factory. */ public static TrustManagerFactory trustClientOnFirstAccess(Path knownClientsFile, TrustManagerFactory tmf) { requireNonNull(knownClientsFile); requireNonNull(tmf); return wrap(ClientFingerprintTrustManager.tofa(knownClientsFile), tmf); } /** * Require servers to present known certificates, or CA-signed certificates. * *

* If a certificate is not CA-signed, then its fingerprint must be present in the {@code knownClientsFile}. * * @param knownClientsFile The path to the file containing fingerprints. * @return A trust manager factory. */ public static TrustManagerFactory whitelistClients(Path knownClientsFile) { requireNonNull(knownClientsFile); return whitelistClients(knownClientsFile, true); } /** * Require clients to present known certificates. * *

* The fingerprint for a client certificate must be present in {@code knownClientsFile}. * * @param knownClientsFile The path to the file containing fingerprints. * @param acceptCASigned If {@code true}, CA-signed certificates will always be accepted. * @return A trust manager factory. */ public static TrustManagerFactory whitelistClients(Path knownClientsFile, boolean acceptCASigned) { requireNonNull(knownClientsFile); return wrap(ClientFingerprintTrustManager.whitelist(knownClientsFile), acceptCASigned); } /** * Require servers to present known certificates, or CA-signed certificates. * *

* If a certificate is not CA-signed, then its fingerprint must be present in the {@code knownClientsFile}. * * @param knownClientsFile The path to the file containing fingerprints. * @param tmf A {@link TrustManagerFactory} for checking server certificates against a CA. * @return A trust manager factory. */ public static TrustManagerFactory whitelistClients(Path knownClientsFile, TrustManagerFactory tmf) { requireNonNull(knownClientsFile); requireNonNull(tmf); return wrap(ClientFingerprintTrustManager.whitelist(knownClientsFile), tmf); } private static TrustManagerFactory wrap(X509TrustManager trustManager, boolean acceptCASigned) { return wrap(trustManager, acceptCASigned ? defaultTrustManagerFactory() : null); } private static TrustManagerFactory wrap(X509TrustManager trustManager, @Nullable TrustManagerFactory delegate) { if (delegate != null) { return new DelegatingTrustManagerFactory(delegate, trustManager); } else { return new SingleTrustManagerFactory(trustManager); } } private static TrustManagerFactory defaultTrustManagerFactory() { TrustManagerFactory delegate; try { delegate = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); } catch (NoSuchAlgorithmException e) { // not reachable throw new RuntimeException(e); } try { delegate.init((KeyStore) null); } catch (KeyStoreException e) { // not reachable throw new RuntimeException(e); } return delegate; } } cava-0.6.0/net/src/main/java/net/consensys/cava/net/tls/TrustManagerFactoryWrapper.java000066400000000000000000000031311341750772100311420ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import javax.net.ssl.TrustManagerFactory; import io.vertx.core.Vertx; import io.vertx.core.net.TrustOptions; final class TrustManagerFactoryWrapper implements TrustOptions { private final TrustManagerFactory trustManagerFactory; TrustManagerFactoryWrapper(TrustManagerFactory trustManagerFactory) { this.trustManagerFactory = trustManagerFactory; } @Override public TrustOptions clone() { return new TrustManagerFactoryWrapper(trustManagerFactory); } @Override public TrustManagerFactory getTrustManagerFactory(Vertx vertx) { return trustManagerFactory; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof TrustManagerFactoryWrapper)) { return false; } TrustManagerFactoryWrapper other = (TrustManagerFactoryWrapper) obj; return trustManagerFactory.equals(other.trustManagerFactory); } @Override public int hashCode() { return trustManagerFactory.hashCode(); } } cava-0.6.0/net/src/main/java/net/consensys/cava/net/tls/VertxTrustOptions.java000066400000000000000000000377251341750772100274030ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import java.nio.file.Path; import javax.net.ssl.TrustManagerFactory; import io.vertx.core.net.TrustOptions; /** * Vert.x {@link TrustOptions} for fingerprinting clients and servers. * *

* This class depends upon the Vert.X library being available on the classpath, along with its dependencies. See * https://vertx.io/download/. Vert.X can be included using the gradle dependency 'io.vertx:vertx-core'. */ public final class VertxTrustOptions { private VertxTrustOptions() {} /** * Accept all server certificates, recording certificate fingerprints for those that are not CA-signed. * *

* Excepting when a server presents a CA-signed certificate, the server host+port and the certificate fingerprint will * be written to {@code knownServersFile}. * *

* Important: this provides no security as it is vulnerable to man-in-the-middle attacks. * * @param knownServersFile The path to a file in which to record fingerprints by host. * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions recordServerFingerprints(Path knownServersFile) { return new TrustManagerFactoryWrapper(TrustManagerFactories.recordServerFingerprints(knownServersFile)); } /** * Accept all server certificates, recording certificate fingerprints. * *

* For all connections, the server host+port and the fingerprint of the presented certificate will be written to * {@code knownServersFile}. * *

* Important: this provides no security as it is vulnerable to man-in-the-middle attacks. * * @param knownServersFile The path to a file in which to record fingerprints by host. * @param skipCASigned If {@code true}, CA-signed certificates are not recorded. * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions recordServerFingerprints(Path knownServersFile, boolean skipCASigned) { return new TrustManagerFactoryWrapper( TrustManagerFactories.recordServerFingerprints(knownServersFile, skipCASigned)); } /** * Accept all server certificates, recording certificate fingerprints for those that are not CA-signed. * *

* Excepting when a server presents a CA-signed certificate, the server host+port and the certificate fingerprint will * be written to {@code knownServersFile}. * *

* Important: this provides no security as it is vulnerable to man-in-the-middle attacks. * * @param knownServersFile The path to a file in which to record fingerprints by host. * @param tmf A {@link TrustManagerFactory} for checking server certificates against a CA. * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions recordServerFingerprints(Path knownServersFile, TrustManagerFactory tmf) { return new TrustManagerFactoryWrapper(TrustManagerFactories.recordServerFingerprints(knownServersFile, tmf)); } /** * Accept CA-signed certificates, and otherwise trust server certificates on first use. * *

* Except when a server presents a CA-signed certificate, on first connection to a server (identified by host+port) * the fingerprint of the presented certificate will be recorded. On subsequent connections, the presented certificate * will be matched to the stored fingerprint to ensure it has not changed. * * @param knownServersFile The path to the file containing fingerprints by host. * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions trustServerOnFirstUse(Path knownServersFile) { return new TrustManagerFactoryWrapper(TrustManagerFactories.trustServerOnFirstUse(knownServersFile)); } /** * Trust server certificates on first use. * *

* On first connection to a server (identified by host+port) the fingerprint of the presented certificate will be * recorded. On subsequent connections, the presented certificate will be matched to the stored fingerprint to ensure * it has not changed. * * @param knownServersFile The path to the file containing fingerprints by host. * @param acceptCASigned If {@code true}, CA-signed certificates will always be accepted (and the fingerprint will not * be recorded). * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions trustServerOnFirstUse(Path knownServersFile, boolean acceptCASigned) { return new TrustManagerFactoryWrapper( TrustManagerFactories.trustServerOnFirstUse(knownServersFile, acceptCASigned)); } /** * Accept CA-signed certificates, and otherwise trust server certificates on first use. * *

* Except when a server presents a CA-signed certificate, on first connection to a server (identified by host+port) * the fingerprint of the presented certificate will be recorded. On subsequent connections, the presented certificate * will be matched to the stored fingerprint to ensure it has not changed. * * @param knownServersFile The path to the file containing fingerprints by host. * @param tmf A {@link TrustManagerFactory} for checking server certificates against a CA. * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions trustServerOnFirstUse(Path knownServersFile, TrustManagerFactory tmf) { return new TrustManagerFactoryWrapper(TrustManagerFactories.trustServerOnFirstUse(knownServersFile, tmf)); } /** * Require servers to present known certificates, or CA-signed certificates. * *

* If a certificate is not CA-signed, then its fingerprint must be present in the known servers file, associated with * the server (identified by host+port). * * @param knownServersFile The path to the file containing fingerprints by host. * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions whitelistServers(Path knownServersFile) { return new TrustManagerFactoryWrapper(TrustManagerFactories.whitelistServers(knownServersFile)); } /** * Require servers to present known certificates. * *

* The fingerprint for a server certificate must be present in the known servers file, associated with the server * (identified by host+port). * * @param knownServersFile The path to the file containing fingerprints by host. * @param acceptCASigned If {@code true}, CA-signed certificates will always be accepted. * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions whitelistServers(Path knownServersFile, boolean acceptCASigned) { return new TrustManagerFactoryWrapper(TrustManagerFactories.whitelistServers(knownServersFile, acceptCASigned)); } /** * Require servers to present known certificates, or CA-signed certificates. * *

* If a certificate is not CA-signed, then its fingerprint must be present in the known servers file, associated with * the server (identified by host+port). * * @param knownServersFile The path to the file containing fingerprints by host. * @param tmf A {@link TrustManagerFactory} for checking server certificates against a CA. * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions whitelistServers(Path knownServersFile, TrustManagerFactory tmf) { return new TrustManagerFactoryWrapper(TrustManagerFactories.whitelistServers(knownServersFile, tmf)); } /** * Accept all client certificates, recording certificate fingerprints for those that are not CA-signed. * *

* Excepting when a client presents a CA-signed certificate, the certificate common name and fingerprint will be * written to {@code knownClientsFile}. * *

* Important: this provides no security as it is vulnerable to man-in-the-middle attacks. * * @param knownClientsFile The path to a file in which to record fingerprints by common name. * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions recordClientFingerprints(Path knownClientsFile) { return new TrustManagerFactoryWrapper(TrustManagerFactories.recordClientFingerprints(knownClientsFile)); } /** * Accept all client certificates, recording certificate fingerprints. * *

* For all connections, the common name and fingerprint of the presented certificate will be written to * {@code knownClientsFile}. * *

* Important: this provides no security as it is vulnerable to man-in-the-middle attacks. * * @param knownClientsFile The path to a file in which to record fingerprints by common name. * @param skipCASigned If {@code true}, CA-signed certificates are not recorded. * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions recordClientFingerprints(Path knownClientsFile, boolean skipCASigned) { return new TrustManagerFactoryWrapper( TrustManagerFactories.recordClientFingerprints(knownClientsFile, skipCASigned)); } /** * Accept all client certificates, recording certificate fingerprints for those that are not CA-signed. * *

* Excepting when a client presents a CA-signed certificate, the certificate common name and fingerprint will be * written to {@code knownClientsFile}. * *

* Important: this provides no security as it is vulnerable to man-in-the-middle attacks. * * @param knownClientsFile The path to a file in which to record fingerprints by common name. * @param tmf A {@link TrustManagerFactory} for checking client certificates against a CA. * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions recordClientFingerprints(Path knownClientsFile, TrustManagerFactory tmf) { return new TrustManagerFactoryWrapper(TrustManagerFactories.recordClientFingerprints(knownClientsFile, tmf)); } /** * Accept CA-signed client certificates, and otherwise trust client certificates on first access. * *

* Except when a client presents a CA-signed certificate, on first connection to this server the common name and * fingerprint of the presented certificate will be recorded. On subsequent connections, the client will be rejected * if the fingerprint has changed. * *

* Note: unlike the seemingly equivalent {@link #trustServerOnFirstUse(Path)} method for authenticating servers, * this method for authenticating clients is insecure and provides zero confidence in client identity. * Unlike the server version, which bases the identity on the hostname and port the connection is being established * to, the client version only uses the common name of the certificate that the connecting client presents. Therefore, * clients can circumvent access control by using a different common name from any previously recorded client. * * @param knownClientsFile The path to the file containing fingerprints by common name. * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions trustClientOnFirstAccess(Path knownClientsFile) { return new TrustManagerFactoryWrapper(TrustManagerFactories.trustClientOnFirstAccess(knownClientsFile)); } /** * Trust client certificates on first access. * *

* On first connection to this server the common name and fingerprint of the presented certificate will be recorded. * On subsequent connections, the client will be rejected if the fingerprint has changed. * *

* Note: unlike the seemingly equivalent {@link #trustServerOnFirstUse(Path)} method for authenticating servers, * this method for authenticating clients is insecure and provides zero confidence in client identity. * Unlike the server version, which bases the identity on the hostname and port the connection is being established * to, the client version only uses the common name of the certificate that the connecting client presents. Therefore, * clients can circumvent access control by using a different common name from any previously recorded client. * * @param knownClientsFile The path to the file containing fingerprints by common name. * @param acceptCASigned If {@code true}, CA-signed certificates will always be accepted. * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions trustClientOnFirstAccess(Path knownClientsFile, boolean acceptCASigned) { return new TrustManagerFactoryWrapper( TrustManagerFactories.trustClientOnFirstAccess(knownClientsFile, acceptCASigned)); } /** * Accept CA-signed certificates, and otherwise trust client certificates on first access. * *

* Except when a client presents a CA-signed certificate, on first connection to this server the common name and * fingerprint of the presented certificate will be recorded. On subsequent connections, the client will be rejected * if the fingerprint has changed. * *

* Note: unlike the seemingly equivalent {@link #trustServerOnFirstUse(Path)} method for authenticating servers, * this method for authenticating clients is insecure and provides zero confidence in client identity. * Unlike the server version, which bases the identity on the hostname and port the connection is being established * to, the client version only uses the common name of the certificate that the connecting client presents. Therefore, * clients can circumvent access control by using a different common name from any previously recorded client. * * @param knownClientsFile The path to the file containing fingerprints by common name. * @param tmf A {@link TrustManagerFactory} for checking server certificates against a CA. * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions trustClientOnFirstAccess(Path knownClientsFile, TrustManagerFactory tmf) { return new TrustManagerFactoryWrapper(TrustManagerFactories.trustClientOnFirstAccess(knownClientsFile, tmf)); } /** * Require clients to present known certificates, or CA-signed certificates. * *

* If a certificate is not CA-signed, then its common name and fingerprint must be present in the * {@code knownClientsFile}. * * @param knownClientsFile The path to the file containing fingerprints by common name. * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions whitelistClients(Path knownClientsFile) { return new TrustManagerFactoryWrapper(TrustManagerFactories.whitelistClients(knownClientsFile)); } /** * Require clients to present known certificates. * *

* The common name and fingerprint for a client certificate must be present in {@code knownClientsFile}. * * @param knownClientsFile The path to the file containing fingerprints by common name. * @param acceptCASigned If {@code true}, CA-signed certificates will always be accepted. * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions whitelistClients(Path knownClientsFile, boolean acceptCASigned) { return new TrustManagerFactoryWrapper(TrustManagerFactories.whitelistClients(knownClientsFile, acceptCASigned)); } /** * Require clients to present known certificates, or CA-signed certificates. * *

* If a certificate is not CA-signed, then its common name and fingerprint must be present in the * {@code knownClientsFile}. * * @param knownClientsFile The path to the file containing fingerprints by common name. * @param tmf A {@link TrustManagerFactory} for checking client certificates against a CA. * @return A Vert.x {@link TrustOptions}. */ public static TrustOptions whitelistClients(Path knownClientsFile, TrustManagerFactory tmf) { return new TrustManagerFactoryWrapper(TrustManagerFactories.whitelistClients(knownClientsFile, tmf)); } } cava-0.6.0/net/src/main/java/net/consensys/cava/net/tls/package-info.java000066400000000000000000000003061341750772100261620ustar00rootroot00000000000000/** * Utilities for doing fingerprint based TLS certificate checking. */ @ParametersAreNonnullByDefault package net.consensys.cava.net.tls; import javax.annotation.ParametersAreNonnullByDefault; cava-0.6.0/net/src/test/000077500000000000000000000000001341750772100147725ustar00rootroot00000000000000cava-0.6.0/net/src/test/java/000077500000000000000000000000001341750772100157135ustar00rootroot00000000000000cava-0.6.0/net/src/test/java/net/000077500000000000000000000000001341750772100165015ustar00rootroot00000000000000cava-0.6.0/net/src/test/java/net/consensys/000077500000000000000000000000001341750772100205255ustar00rootroot00000000000000cava-0.6.0/net/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100214375ustar00rootroot00000000000000cava-0.6.0/net/src/test/java/net/consensys/cava/net/000077500000000000000000000000001341750772100222255ustar00rootroot00000000000000cava-0.6.0/net/src/test/java/net/consensys/cava/net/tls/000077500000000000000000000000001341750772100230275ustar00rootroot00000000000000cava-0.6.0/net/src/test/java/net/consensys/cava/net/tls/ClientCaOrRecordTest.java000066400000000000000000000235121341750772100276570ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static net.consensys.cava.net.tls.SecurityTestUtils.DUMMY_FINGERPRINT; import static net.consensys.cava.net.tls.SecurityTestUtils.startServer; import static net.consensys.cava.net.tls.TLS.certificateHexFingerprint; import static org.junit.jupiter.api.Assertions.assertEquals; import net.consensys.cava.junit.TempDirectory; import net.consensys.cava.junit.TempDirectoryExtension; import net.consensys.cava.junit.VertxExtension; import net.consensys.cava.junit.VertxInstance; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import io.vertx.core.Vertx; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.net.SelfSignedCertificate; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) @ExtendWith(VertxExtension.class) class ClientCaOrRecordTest { private static String caValidFingerprint; private static HttpServer caValidServer; private static String fooFingerprint; private static HttpServer fooServer; private static String barFingerprint; private static HttpServer barServer; private static String foobarFingerprint; private static HttpServer foobarServer; private Path knownServersFile; private HttpClient client; @BeforeAll static void startServers(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { SelfSignedCertificate caSignedCert = SelfSignedCertificate.create("localhost"); SecurityTestUtils.configureJDKTrustStore(tempDir, caSignedCert); caValidFingerprint = certificateHexFingerprint(Paths.get(caSignedCert.keyCertOptions().getCertPath())); caValidServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(caSignedCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(caValidServer); SelfSignedCertificate fooCert = SelfSignedCertificate.create("foo.com"); fooFingerprint = certificateHexFingerprint(Paths.get(fooCert.keyCertOptions().getCertPath())); fooServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(fooCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(fooServer); SelfSignedCertificate barCert = SelfSignedCertificate.create("bar.com"); barFingerprint = certificateHexFingerprint(Paths.get(barCert.keyCertOptions().getCertPath())); barServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(barCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(barServer); SelfSignedCertificate foobarCert = SelfSignedCertificate.create("foobar.com"); foobarFingerprint = certificateHexFingerprint(Paths.get(foobarCert.keyCertOptions().getCertPath())); foobarServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(foobarCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(foobarServer); } @BeforeEach void setupClient(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { knownServersFile = tempDir.resolve("known-hosts.txt"); Files.write( knownServersFile, Arrays.asList("#First line", "localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT)); HttpClientOptions options = new HttpClientOptions(); options .setSsl(true) .setTrustOptions(VertxTrustOptions.recordServerFingerprints(knownServersFile)) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); client = vertx.createHttpClient(options); } @AfterEach void cleanupClient() { client.close(); } @AfterAll static void stopServers() { caValidServer.close(); fooServer.close(); barServer.close(); foobarServer.close(); System.clearProperty("javax.net.ssl.trustStore"); System.clearProperty("javax.net.ssl.trustStorePassword"); } @Test void shouldValidateUsingCertificate() throws Exception { CompletableFuture statusCode = new CompletableFuture<>(); client .post( caValidServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); assertEquals((Integer) 200, statusCode.join()); List knownServers = Files.readAllLines(knownServersFile); assertEquals(2, knownServers.size(), "CA verified host should not have been recorded"); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT, knownServers.get(1)); } @Test void shouldRecordMultipleHosts() throws Exception { CompletableFuture statusCode = new CompletableFuture<>(); client .post(fooServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); assertEquals((Integer) 200, statusCode.join()); List knownServers = Files.readAllLines(knownServersFile); assertEquals(3, knownServers.size(), String.join("\n", knownServers)); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT, knownServers.get(1)); assertEquals("localhost:" + fooServer.actualPort() + " " + fooFingerprint, knownServers.get(2)); CompletableFuture secondStatusCode = new CompletableFuture<>(); client .post( barServer.actualPort(), "localhost", "/sample", response -> secondStatusCode.complete(response.statusCode())) .exceptionHandler(secondStatusCode::completeExceptionally) .end(); assertEquals((Integer) 200, secondStatusCode.join()); knownServers = Files.readAllLines(knownServersFile); assertEquals(4, knownServers.size(), String.join("\n", knownServers)); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT, knownServers.get(1)); assertEquals("localhost:" + fooServer.actualPort() + " " + fooFingerprint, knownServers.get(2)); assertEquals("localhost:" + barServer.actualPort() + " " + barFingerprint, knownServers.get(3)); } @Test void shouldFallbackToRecordingForInvalidName() throws Exception { CompletableFuture statusCode = new CompletableFuture<>(); client .post( caValidServer.actualPort(), "127.0.0.1", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); assertEquals((Integer) 200, statusCode.join()); List knownServers = Files.readAllLines(knownServersFile); assertEquals(3, knownServers.size(), String.join("\n", knownServers)); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT, knownServers.get(1)); assertEquals("127.0.0.1:" + caValidServer.actualPort() + " " + caValidFingerprint, knownServers.get(2)); } @Test void shouldReplaceFingerprint() throws Exception { CompletableFuture statusCode = new CompletableFuture<>(); client .post(fooServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); assertEquals((Integer) 200, statusCode.join()); List knownServers = Files.readAllLines(knownServersFile); assertEquals(3, knownServers.size(), String.join("\n", knownServers)); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT, knownServers.get(1)); assertEquals("localhost:" + fooServer.actualPort() + " " + fooFingerprint, knownServers.get(2)); CompletableFuture secondStatusCode = new CompletableFuture<>(); client .post( foobarServer.actualPort(), "localhost", "/sample", response -> secondStatusCode.complete(response.statusCode())) .exceptionHandler(secondStatusCode::completeExceptionally) .end(); assertEquals((Integer) 200, secondStatusCode.join()); knownServers = Files.readAllLines(knownServersFile); assertEquals(3, knownServers.size(), String.join("\n", knownServers)); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + foobarServer.actualPort() + " " + foobarFingerprint, knownServers.get(1)); assertEquals("localhost:" + fooServer.actualPort() + " " + fooFingerprint, knownServers.get(2)); } } cava-0.6.0/net/src/test/java/net/consensys/cava/net/tls/ClientCaOrTofuTest.java000066400000000000000000000175111341750772100273600ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static net.consensys.cava.net.tls.SecurityTestUtils.DUMMY_FINGERPRINT; import static net.consensys.cava.net.tls.SecurityTestUtils.startServer; import static net.consensys.cava.net.tls.TLS.certificateHexFingerprint; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.junit.TempDirectory; import net.consensys.cava.junit.TempDirectoryExtension; import net.consensys.cava.junit.VertxExtension; import net.consensys.cava.junit.VertxInstance; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.cert.CertificateException; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import javax.net.ssl.SSLException; import io.vertx.core.Vertx; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.net.SelfSignedCertificate; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) @ExtendWith(VertxExtension.class) class ClientCaOrTofuTest { private static String caValidFingerprint; private static HttpServer caValidServer; private static String fooFingerprint; private static HttpServer fooServer; private static String foobarFingerprint; private static HttpServer foobarServer; private Path knownServersFile; private HttpClient client; @BeforeAll static void startServers(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { SelfSignedCertificate caSignedCert = SelfSignedCertificate.create("localhost"); SecurityTestUtils.configureJDKTrustStore(tempDir, caSignedCert); caValidFingerprint = certificateHexFingerprint(Paths.get(caSignedCert.keyCertOptions().getCertPath())); caValidServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(caSignedCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(caValidServer); SelfSignedCertificate fooCert = SelfSignedCertificate.create("foo.com"); fooFingerprint = certificateHexFingerprint(Paths.get(fooCert.keyCertOptions().getCertPath())); fooServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(fooCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(fooServer); SelfSignedCertificate foobarCert = SelfSignedCertificate.create("foobar.com"); foobarFingerprint = certificateHexFingerprint(Paths.get(foobarCert.keyCertOptions().getCertPath())); foobarServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(foobarCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(foobarServer); } @BeforeEach void setupClient(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { knownServersFile = tempDir.resolve("known-hosts.txt"); Files.write( knownServersFile, Arrays.asList("#First line", "localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT)); HttpClientOptions options = new HttpClientOptions(); options .setSsl(true) .setTrustOptions(VertxTrustOptions.trustServerOnFirstUse(knownServersFile)) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); client = vertx.createHttpClient(options); } @AfterEach void cleanupClient() { client.close(); } @AfterAll static void stopServers() { caValidServer.close(); fooServer.close(); foobarServer.close(); System.clearProperty("javax.net.ssl.trustStore"); System.clearProperty("javax.net.ssl.trustStorePassword"); } @Test void shouldValidateUsingCertificate() throws Exception { CompletableFuture statusCode = new CompletableFuture<>(); client .post( caValidServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); assertEquals((Integer) 200, statusCode.join()); List knownServers = Files.readAllLines(knownServersFile); assertEquals(2, knownServers.size(), "Host was verified via TOFU and not CA"); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT, knownServers.get(1)); } @Test void shouldFallbackToTOFUForInvalidName() throws Exception { CompletableFuture statusCode = new CompletableFuture<>(); client .post( caValidServer.actualPort(), "127.0.0.1", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); assertEquals((Integer) 200, statusCode.join()); List knownServers = Files.readAllLines(knownServersFile); assertEquals(3, knownServers.size()); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT, knownServers.get(1)); assertEquals("127.0.0.1:" + caValidServer.actualPort() + " " + caValidFingerprint, knownServers.get(2)); } @Test void shouldValidateOnFirstUse() throws Exception { CompletableFuture statusCode = new CompletableFuture<>(); client .post(fooServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); assertEquals((Integer) 200, statusCode.join()); List knownServers = Files.readAllLines(knownServersFile); assertEquals(3, knownServers.size()); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT, knownServers.get(1)); assertEquals("localhost:" + fooServer.actualPort() + " " + fooFingerprint, knownServers.get(2)); } @Test void shouldRejectDifferentCertificate() { CompletableFuture statusCode = new CompletableFuture<>(); client .post(foobarServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); Throwable e = assertThrows(CompletionException.class, statusCode::join); e = e.getCause(); while (!(e instanceof CertificateException)) { assertTrue(e instanceof SSLException, "Expected SSLException, but got " + e.getClass()); e = e.getCause(); } assertTrue(e.getMessage().contains("Remote host identification has changed!!"), e.getMessage()); assertTrue(e.getMessage().contains("has fingerprint " + foobarFingerprint)); } } cava-0.6.0/net/src/test/java/net/consensys/cava/net/tls/ClientCaOrWhitelistTest.java000066400000000000000000000144011341750772100304120ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static net.consensys.cava.net.tls.SecurityTestUtils.startServer; import static net.consensys.cava.net.tls.TLS.certificateHexFingerprint; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.junit.TempDirectory; import net.consensys.cava.junit.TempDirectoryExtension; import net.consensys.cava.junit.VertxExtension; import net.consensys.cava.junit.VertxInstance; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.cert.CertificateException; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import javax.net.ssl.SSLException; import io.vertx.core.Vertx; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.net.SelfSignedCertificate; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) @ExtendWith(VertxExtension.class) class ClientCaOrWhitelistTest { private static HttpServer caValidServer; private static String fooFingerprint; private static HttpServer fooServer; private static String barFingerprint; private static HttpServer barServer; private Path knownServersFile; private HttpClient client; @BeforeAll static void startServers(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { SelfSignedCertificate caSignedCert = SelfSignedCertificate.create("localhost"); SecurityTestUtils.configureJDKTrustStore(tempDir, caSignedCert); caValidServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(caSignedCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(caValidServer); SelfSignedCertificate fooCert = SelfSignedCertificate.create("foo.com"); fooFingerprint = certificateHexFingerprint(Paths.get(fooCert.keyCertOptions().getCertPath())); fooServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(fooCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(fooServer); SelfSignedCertificate barCert = SelfSignedCertificate.create("bar.com"); barFingerprint = certificateHexFingerprint(Paths.get(barCert.keyCertOptions().getCertPath())); barServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(barCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(barServer); } @BeforeEach void setupClient(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { knownServersFile = tempDir.resolve("knownclients.txt"); Files.write( knownServersFile, Arrays.asList("#First line", "localhost:" + fooServer.actualPort() + " " + fooFingerprint)); HttpClientOptions options = new HttpClientOptions(); options .setSsl(true) .setTrustOptions(VertxTrustOptions.whitelistServers(knownServersFile)) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); client = vertx.createHttpClient(options); } @AfterEach void cleanupClient() throws Exception { client.close(); List knownServers = Files.readAllLines(knownServersFile); assertEquals(2, knownServers.size(), "Host was verified via TOFU and not CA"); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + fooServer.actualPort() + " " + fooFingerprint, knownServers.get(1)); } @AfterAll static void stopServers() { caValidServer.close(); fooServer.close(); barServer.close(); System.clearProperty("javax.net.ssl.trustStore"); System.clearProperty("javax.net.ssl.trustStorePassword"); } @Test void shouldValidateUsingCertificate() { CompletableFuture statusCode = new CompletableFuture<>(); client .post( caValidServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); assertEquals((Integer) 200, statusCode.join()); } @Test void shouldValidateWhitelisted() { CompletableFuture statusCode = new CompletableFuture<>(); client .post(fooServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); assertEquals((Integer) 200, statusCode.join()); } @Test void shouldRejectNonWhitelisted() { CompletableFuture statusCode = new CompletableFuture<>(); client .post(barServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); Throwable e = assertThrows(CompletionException.class, statusCode::join); e = e.getCause(); while (!(e instanceof CertificateException)) { assertTrue(e instanceof SSLException, "Expected SSLException, but got " + e.getClass()); e = e.getCause(); } assertTrue(e.getMessage().contains("has unknown fingerprint " + barFingerprint)); } } cava-0.6.0/net/src/test/java/net/consensys/cava/net/tls/ClientRecordTest.java000066400000000000000000000220721341750772100271120ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static net.consensys.cava.net.tls.SecurityTestUtils.DUMMY_FINGERPRINT; import static net.consensys.cava.net.tls.SecurityTestUtils.startServer; import static net.consensys.cava.net.tls.TLS.certificateHexFingerprint; import static org.junit.jupiter.api.Assertions.assertEquals; import net.consensys.cava.junit.TempDirectory; import net.consensys.cava.junit.TempDirectoryExtension; import net.consensys.cava.junit.VertxExtension; import net.consensys.cava.junit.VertxInstance; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import io.vertx.core.Vertx; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.net.SelfSignedCertificate; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) @ExtendWith(VertxExtension.class) class ClientRecordTest { private static String caValidFingerprint; private static HttpServer caValidServer; private static String fooFingerprint; private static HttpServer fooServer; private static String barFingerprint; private static HttpServer barServer; private static String foobarFingerprint; private static HttpServer foobarServer; private Path knownServersFile; private HttpClient client; @BeforeAll static void startServers(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { SelfSignedCertificate caSignedCert = SelfSignedCertificate.create("localhost"); SecurityTestUtils.configureJDKTrustStore(tempDir, caSignedCert); caValidFingerprint = certificateHexFingerprint(Paths.get(caSignedCert.keyCertOptions().getCertPath())); caValidServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(caSignedCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(caValidServer); SelfSignedCertificate fooCert = SelfSignedCertificate.create("foo.com"); fooFingerprint = certificateHexFingerprint(Paths.get(fooCert.keyCertOptions().getCertPath())); fooServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(fooCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(fooServer); SelfSignedCertificate barCert = SelfSignedCertificate.create("bar.com"); barFingerprint = certificateHexFingerprint(Paths.get(barCert.keyCertOptions().getCertPath())); barServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(barCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(barServer); SelfSignedCertificate foobarCert = SelfSignedCertificate.create("foobar.com"); foobarFingerprint = certificateHexFingerprint(Paths.get(foobarCert.keyCertOptions().getCertPath())); foobarServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(foobarCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(foobarServer); } @BeforeEach void setupClient(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { knownServersFile = tempDir.resolve("known-hosts.txt"); Files.write( knownServersFile, Arrays.asList("#First line", "localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT)); HttpClientOptions options = new HttpClientOptions(); options .setSsl(true) .setTrustOptions(VertxTrustOptions.recordServerFingerprints(knownServersFile, false)) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); client = vertx.createHttpClient(options); } @AfterEach void cleanupClient() { client.close(); } @AfterAll static void stopServers() { caValidServer.close(); fooServer.close(); barServer.close(); foobarServer.close(); System.clearProperty("javax.net.ssl.trustStore"); System.clearProperty("javax.net.ssl.trustStorePassword"); } @Test void shouldNotValidateUsingCertificate() throws Exception { CompletableFuture statusCode = new CompletableFuture<>(); client .post( caValidServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); assertEquals((Integer) 200, statusCode.join()); List knownServers = Files.readAllLines(knownServersFile); assertEquals(3, knownServers.size(), "Host was verified using CA"); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT, knownServers.get(1)); assertEquals("localhost:" + caValidServer.actualPort() + " " + caValidFingerprint, knownServers.get(2)); } @Test void shouldRecordMultipleHosts() throws Exception { CompletableFuture statusCode = new CompletableFuture<>(); client .post(fooServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); assertEquals((Integer) 200, statusCode.join()); List knownServers = Files.readAllLines(knownServersFile); assertEquals(3, knownServers.size(), String.join("\n", knownServers)); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT, knownServers.get(1)); assertEquals("localhost:" + fooServer.actualPort() + " " + fooFingerprint, knownServers.get(2)); CompletableFuture secondStatusCode = new CompletableFuture<>(); client .post( barServer.actualPort(), "localhost", "/sample", response -> secondStatusCode.complete(response.statusCode())) .exceptionHandler(secondStatusCode::completeExceptionally) .end(); assertEquals((Integer) 200, secondStatusCode.join()); knownServers = Files.readAllLines(knownServersFile); assertEquals(4, knownServers.size(), String.join("\n", knownServers)); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT, knownServers.get(1)); assertEquals("localhost:" + fooServer.actualPort() + " " + fooFingerprint, knownServers.get(2)); assertEquals("localhost:" + barServer.actualPort() + " " + barFingerprint, knownServers.get(3)); } @Test void shouldReplaceFingerprint() throws Exception { CompletableFuture statusCode = new CompletableFuture<>(); client .post(fooServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); assertEquals((Integer) 200, statusCode.join()); List knownServers = Files.readAllLines(knownServersFile); assertEquals(3, knownServers.size(), String.join("\n", knownServers)); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT, knownServers.get(1)); assertEquals("localhost:" + fooServer.actualPort() + " " + fooFingerprint, knownServers.get(2)); CompletableFuture secondStatusCode = new CompletableFuture<>(); client .post( foobarServer.actualPort(), "localhost", "/sample", response -> secondStatusCode.complete(response.statusCode())) .exceptionHandler(secondStatusCode::completeExceptionally) .end(); assertEquals((Integer) 200, secondStatusCode.join()); knownServers = Files.readAllLines(knownServersFile); assertEquals(3, knownServers.size(), String.join("\n", knownServers)); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + foobarServer.actualPort() + " " + foobarFingerprint, knownServers.get(1)); assertEquals("localhost:" + fooServer.actualPort() + " " + fooFingerprint, knownServers.get(2)); } } cava-0.6.0/net/src/test/java/net/consensys/cava/net/tls/ClientTofuTest.java000066400000000000000000000161631341750772100266150ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static net.consensys.cava.net.tls.SecurityTestUtils.DUMMY_FINGERPRINT; import static net.consensys.cava.net.tls.SecurityTestUtils.startServer; import static net.consensys.cava.net.tls.TLS.certificateHexFingerprint; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.junit.TempDirectory; import net.consensys.cava.junit.TempDirectoryExtension; import net.consensys.cava.junit.VertxExtension; import net.consensys.cava.junit.VertxInstance; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.cert.CertificateException; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import javax.net.ssl.SSLException; import io.vertx.core.Vertx; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.net.SelfSignedCertificate; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) @ExtendWith(VertxExtension.class) class ClientTofuTest { private static String caValidFingerprint; private static HttpServer caValidServer; private static String fooFingerprint; private static HttpServer fooServer; private static String foobarFingerprint; private static HttpServer foobarServer; private Path knownServersFile; private HttpClient client; @BeforeAll static void startServers(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { SelfSignedCertificate caSignedCert = SelfSignedCertificate.create("localhost"); SecurityTestUtils.configureJDKTrustStore(tempDir, caSignedCert); caValidFingerprint = certificateHexFingerprint(Paths.get(caSignedCert.keyCertOptions().getCertPath())); caValidServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(caSignedCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(caValidServer); SelfSignedCertificate fooCert = SelfSignedCertificate.create("foo.com"); fooFingerprint = certificateHexFingerprint(Paths.get(fooCert.keyCertOptions().getCertPath())); fooServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(fooCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(fooServer); SelfSignedCertificate foobarCert = SelfSignedCertificate.create("foobar.com"); foobarFingerprint = certificateHexFingerprint(Paths.get(foobarCert.keyCertOptions().getCertPath())); foobarServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(foobarCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(foobarServer); } @BeforeEach void setupClient(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { knownServersFile = tempDir.resolve("known-hosts.txt"); Files.write( knownServersFile, Arrays.asList("#First line", "localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT)); HttpClientOptions options = new HttpClientOptions(); options .setSsl(true) .setTrustOptions(VertxTrustOptions.trustServerOnFirstUse(knownServersFile, false)) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); client = vertx.createHttpClient(options); } @AfterEach void cleanupClient() { client.close(); } @AfterAll static void stopServers() { caValidServer.close(); fooServer.close(); foobarServer.close(); System.clearProperty("javax.net.ssl.trustStore"); System.clearProperty("javax.net.ssl.trustStorePassword"); } @Test void shouldNotValidateUsingCertificate() throws Exception { CompletableFuture statusCode = new CompletableFuture<>(); client .post( caValidServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); assertEquals((Integer) 200, statusCode.join()); List knownServers = Files.readAllLines(knownServersFile); assertEquals(3, knownServers.size(), "Host was verified via TOFU and not CA"); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT, knownServers.get(1)); assertEquals("localhost:" + caValidServer.actualPort() + " " + caValidFingerprint, knownServers.get(2)); } @Test void shouldValidateOnFirstUse() throws Exception { CompletableFuture statusCode = new CompletableFuture<>(); client .post(fooServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); assertEquals((Integer) 200, statusCode.join()); List knownServers = Files.readAllLines(knownServersFile); assertEquals(3, knownServers.size()); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + foobarServer.actualPort() + " " + DUMMY_FINGERPRINT, knownServers.get(1)); assertEquals("localhost:" + fooServer.actualPort() + " " + fooFingerprint, knownServers.get(2)); } @Test void shouldRejectDifferentCertificate() { CompletableFuture statusCode = new CompletableFuture<>(); client .post(foobarServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); Throwable e = assertThrows(CompletionException.class, statusCode::join); e = e.getCause(); while (!(e instanceof CertificateException)) { assertTrue(e instanceof SSLException, "Expected SSLException, but got " + e.getClass()); e = e.getCause(); } assertTrue(e.getMessage().contains("Remote host identification has changed!!"), e.getMessage()); assertTrue(e.getMessage().contains("has fingerprint " + foobarFingerprint)); } } cava-0.6.0/net/src/test/java/net/consensys/cava/net/tls/ClientWhitelistTest.java000066400000000000000000000152461341750772100276550ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static net.consensys.cava.net.tls.SecurityTestUtils.startServer; import static net.consensys.cava.net.tls.TLS.certificateHexFingerprint; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.junit.TempDirectory; import net.consensys.cava.junit.TempDirectoryExtension; import net.consensys.cava.junit.VertxExtension; import net.consensys.cava.junit.VertxInstance; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.cert.CertificateException; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import javax.net.ssl.SSLException; import io.vertx.core.Vertx; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.net.SelfSignedCertificate; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) @ExtendWith(VertxExtension.class) class ClientWhitelistTest { private static String caValidFingerprint; private static HttpServer caValidServer; private static String fooFingerprint; private static HttpServer fooServer; private static String barFingerprint; private static HttpServer barServer; private Path knownServersFile; private HttpClient client; @BeforeAll static void startServers(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { SelfSignedCertificate caSignedCert = SelfSignedCertificate.create("localhost"); caValidFingerprint = certificateHexFingerprint(Paths.get(caSignedCert.keyCertOptions().getCertPath())); SecurityTestUtils.configureJDKTrustStore(tempDir, caSignedCert); caValidServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(caSignedCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(caValidServer); SelfSignedCertificate fooCert = SelfSignedCertificate.create("foo.com"); fooFingerprint = certificateHexFingerprint(Paths.get(fooCert.keyCertOptions().getCertPath())); fooServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(fooCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(fooServer); SelfSignedCertificate barCert = SelfSignedCertificate.create("bar.com"); barFingerprint = certificateHexFingerprint(Paths.get(barCert.keyCertOptions().getCertPath())); barServer = vertx .createHttpServer(new HttpServerOptions().setSsl(true).setPemKeyCertOptions(barCert.keyCertOptions())) .requestHandler(context -> context.response().end("OK")); startServer(barServer); } @BeforeEach void setupClient(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { knownServersFile = tempDir.resolve("knownclients.txt"); Files.write( knownServersFile, Arrays.asList("#First line", "localhost:" + fooServer.actualPort() + " " + fooFingerprint)); HttpClientOptions options = new HttpClientOptions(); options .setSsl(true) .setTrustOptions(VertxTrustOptions.whitelistServers(knownServersFile, false)) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); client = vertx.createHttpClient(options); } @AfterEach void cleanupClient() throws Exception { client.close(); List knownServers = Files.readAllLines(knownServersFile); assertEquals(2, knownServers.size(), "Host was verified via TOFU and not CA"); assertEquals("#First line", knownServers.get(0)); assertEquals("localhost:" + fooServer.actualPort() + " " + fooFingerprint, knownServers.get(1)); } @AfterAll static void stopServers() { caValidServer.close(); fooServer.close(); barServer.close(); System.clearProperty("javax.net.ssl.trustStore"); System.clearProperty("javax.net.ssl.trustStorePassword"); } @Test void shouldNotValidateUsingCertificate() { CompletableFuture statusCode = new CompletableFuture<>(); client .post( caValidServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); Throwable e = assertThrows(CompletionException.class, statusCode::join); e = e.getCause(); while (!(e instanceof CertificateException)) { assertTrue(e instanceof SSLException); e = e.getCause(); } assertTrue(e.getMessage().contains("has unknown fingerprint " + caValidFingerprint)); } @Test void shouldValidateWhitelisted() { CompletableFuture statusCode = new CompletableFuture<>(); client .post(fooServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); assertEquals((Integer) 200, statusCode.join()); } @Test void shouldRejectNonWhitelisted() { CompletableFuture statusCode = new CompletableFuture<>(); client .post(barServer.actualPort(), "localhost", "/sample", response -> statusCode.complete(response.statusCode())) .exceptionHandler(statusCode::completeExceptionally) .end(); Throwable e = assertThrows(CompletionException.class, statusCode::join); e = e.getCause(); while (!(e instanceof CertificateException)) { assertTrue(e instanceof SSLException, "Expected SSLException, but got " + e.getClass()); e = e.getCause(); } assertTrue(e.getMessage().contains("has unknown fingerprint " + barFingerprint)); } } cava-0.6.0/net/src/test/java/net/consensys/cava/net/tls/FingerprintRepositoryTest.java000066400000000000000000000056171341750772100311320ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.junit.TempDirectory; import net.consensys.cava.junit.TempDirectoryExtension; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.security.SecureRandom; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) class FingerprintRepositoryTest { private SecureRandom secureRandom = new SecureRandom(); private Bytes generateFingerprint() { byte[] bytes = new byte[32]; secureRandom.nextBytes(bytes); return Bytes.wrap(bytes); } @Test FingerprintRepository testAddingNewFingerprint(@TempDirectory Path tempFolder) throws IOException { FingerprintRepository repo = new FingerprintRepository(tempFolder.resolve("repo")); Bytes fingerprint = generateFingerprint(); repo.addFingerprint("foo", fingerprint); assertTrue(repo.contains("foo", fingerprint)); assertEquals( "foo " + fingerprint.toHexString().substring(2).toLowerCase(), Files.readAllLines(tempFolder.resolve("repo")).get(0)); return repo; } @Test void testUpdateFingerprint(@TempDirectory Path tempFolder) throws IOException { FingerprintRepository repo = testAddingNewFingerprint(tempFolder); Bytes fingerprint = generateFingerprint(); repo.addFingerprint("foo", fingerprint); assertTrue(repo.contains("foo", fingerprint)); assertEquals( "foo " + fingerprint.toHexString().substring(2).toLowerCase(), Files.readAllLines(tempFolder.resolve("repo")).get(0)); } @Test void testInvalidFingerprintAddedToFile(@TempDirectory Path tempFolder) throws IOException { FingerprintRepository repo = new FingerprintRepository(tempFolder.resolve("repo-bad2")); Bytes fingerprint = generateFingerprint(); Files.write( tempFolder.resolve("repo-bad2"), ("bar " + fingerprint.slice(8).toHexString().substring(2) + "GGG").getBytes(UTF_8)); assertThrows(TLSEnvironmentException.class, () -> repo.addFingerprint("foo", fingerprint)); } } cava-0.6.0/net/src/test/java/net/consensys/cava/net/tls/InsecureTrustOptions.java000066400000000000000000000022111341750772100300610ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import javax.net.ssl.TrustManagerFactory; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.vertx.core.Vertx; import io.vertx.core.net.TrustOptions; final class InsecureTrustOptions implements TrustOptions { static InsecureTrustOptions INSTANCE = new InsecureTrustOptions(); private InsecureTrustOptions() {} @Override public TrustOptions clone() { return this; } @Override public TrustManagerFactory getTrustManagerFactory(Vertx vertx) { return InsecureTrustManagerFactory.INSTANCE; } } cava-0.6.0/net/src/test/java/net/consensys/cava/net/tls/SecurityTestUtils.java000066400000000000000000000057541341750772100273750ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static net.consensys.cava.net.tls.TLS.readPemFile; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.security.KeyFactory; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.spec.PKCS8EncodedKeySpec; import java.util.concurrent.CompletableFuture; import io.vertx.core.http.HttpServer; import io.vertx.core.net.SelfSignedCertificate; class SecurityTestUtils { private SecurityTestUtils() {} static final String DUMMY_FINGERPRINT = "1111111111111111111111111111111111111111111111111111111111111111"; static void configureJDKTrustStore(Path workDir, SelfSignedCertificate clientCert) throws Exception { KeyStore ks = KeyStore.getInstance("JKS"); ks.load(null, null); KeyFactory kf = KeyFactory.getInstance("RSA"); PKCS8EncodedKeySpec keysp = new PKCS8EncodedKeySpec(readPemFile(new File(clientCert.privateKeyPath()).toPath())); PrivateKey clientPrivateKey = kf.generatePrivate(keysp); CertificateFactory cf = CertificateFactory.getInstance("X.509"); Certificate certificate = cf.generateCertificate( new ByteArrayInputStream(Files.readAllBytes(new File(clientCert.certificatePath()).toPath()))); ks.setCertificateEntry("clientCert", certificate); ks.setKeyEntry("client", clientPrivateKey, "changeit".toCharArray(), new Certificate[] {certificate}); Path tempKeystore = Files.createTempFile(workDir, "keystore", ".jks"); try (FileOutputStream output = new FileOutputStream(tempKeystore.toFile());) { ks.store(output, "changeit".toCharArray()); } System.setProperty("javax.net.ssl.trustStore", tempKeystore.toString()); System.setProperty("javax.net.ssl.trustStorePassword", "changeit"); } static void configureAndStartTestServer(HttpServer httpServer) { httpServer.requestHandler(request -> { request.response().setStatusCode(200).end("OK"); }); startServer(httpServer); } static void startServer(HttpServer server) { CompletableFuture future = new CompletableFuture<>(); server.listen(0, result -> { if (result.succeeded()) { future.complete(true); } else { future.completeExceptionally(result.cause()); } }); future.join(); } } cava-0.6.0/net/src/test/java/net/consensys/cava/net/tls/ServerCaOrRecordTest.java000066400000000000000000000215051341750772100277070ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static net.consensys.cava.net.tls.SecurityTestUtils.DUMMY_FINGERPRINT; import static net.consensys.cava.net.tls.TLS.certificateHexFingerprint; import static org.junit.jupiter.api.Assertions.assertEquals; import net.consensys.cava.junit.TempDirectory; import net.consensys.cava.junit.TempDirectoryExtension; import net.consensys.cava.junit.VertxExtension; import net.consensys.cava.junit.VertxInstance; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import io.vertx.core.Vertx; import io.vertx.core.http.ClientAuth; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpClientResponse; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.net.SelfSignedCertificate; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) @ExtendWith(VertxExtension.class) class ServerCaOrRecordTest { private static HttpClient caClient; private static String fooFingerprint; private static HttpClient fooClient; private static String barFingerprint; private static HttpClient barClient; private static String foobarFingerprint; private static HttpClient foobarClient; private Path knownClientsFile; private HttpServer httpServer; @BeforeAll static void setupClients(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { SelfSignedCertificate caClientCert = SelfSignedCertificate.create(); SecurityTestUtils.configureJDKTrustStore(tempDir, caClientCert); caClient = vertx.createHttpClient( new HttpClientOptions().setTrustOptions(InsecureTrustOptions.INSTANCE).setSsl(true).setKeyCertOptions( caClientCert.keyCertOptions())); SelfSignedCertificate fooCert = SelfSignedCertificate.create("foo.com"); fooFingerprint = certificateHexFingerprint(Paths.get(fooCert.keyCertOptions().getCertPath())); HttpClientOptions fooClientOptions = new HttpClientOptions(); fooClientOptions .setSsl(true) .setKeyCertOptions(fooCert.keyCertOptions()) .setTrustOptions(InsecureTrustOptions.INSTANCE) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); fooClient = vertx.createHttpClient(fooClientOptions); SelfSignedCertificate barCert = SelfSignedCertificate.create("bar.com"); barFingerprint = certificateHexFingerprint(Paths.get(barCert.keyCertOptions().getCertPath())); HttpClientOptions barClientOptions = new HttpClientOptions(); barClientOptions .setSsl(true) .setKeyCertOptions(barCert.keyCertOptions()) .setTrustOptions(InsecureTrustOptions.INSTANCE) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); barClient = vertx.createHttpClient(barClientOptions); SelfSignedCertificate foobarCert = SelfSignedCertificate.create("foobar.com"); foobarFingerprint = certificateHexFingerprint(Paths.get(foobarCert.keyCertOptions().getCertPath())); HttpClientOptions foobarClientOptions = new HttpClientOptions(); foobarClientOptions .setSsl(true) .setKeyCertOptions(foobarCert.keyCertOptions()) .setTrustOptions(InsecureTrustOptions.INSTANCE) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); foobarClient = vertx.createHttpClient(foobarClientOptions); } @BeforeEach void startServer(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { knownClientsFile = tempDir.resolve("known-clients.txt"); Files.write(knownClientsFile, Arrays.asList("#First line", "foobar.com " + DUMMY_FINGERPRINT)); SelfSignedCertificate serverCert = SelfSignedCertificate.create(); HttpServerOptions options = new HttpServerOptions(); options .setSsl(true) .setClientAuth(ClientAuth.REQUIRED) .setPemKeyCertOptions(serverCert.keyCertOptions()) .setTrustOptions(VertxTrustOptions.recordClientFingerprints(knownClientsFile)) .setIdleTimeout(1500) .setReuseAddress(true) .setReusePort(true); httpServer = vertx.createHttpServer(options); SecurityTestUtils.configureAndStartTestServer(httpServer); } @AfterEach void stopServer() { httpServer.close(); } @AfterAll static void cleanupClients() { caClient.close(); fooClient.close(); barClient.close(); foobarClient.close(); } @Test void shouldValidateUsingCertificate() throws Exception { HttpClientRequest req = caClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); HttpClientResponse resp = respFuture.join(); assertEquals(200, resp.statusCode()); List knownClients = Files.readAllLines(knownClientsFile); assertEquals(2, knownClients.size(), "CA verified host should not have been recorded"); assertEquals("#First line", knownClients.get(0)); assertEquals("foobar.com " + DUMMY_FINGERPRINT, knownClients.get(1)); } @Test void shouldRecordMultipleFingerprints() throws Exception { HttpClientRequest req = fooClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); HttpClientResponse resp = respFuture.join(); assertEquals(200, resp.statusCode()); List knownClients = Files.readAllLines(knownClientsFile); assertEquals(3, knownClients.size(), String.join("\n", knownClients)); assertEquals("#First line", knownClients.get(0)); assertEquals("foobar.com " + DUMMY_FINGERPRINT, knownClients.get(1)); assertEquals("foo.com " + fooFingerprint, knownClients.get(2)); req = barClient.get(httpServer.actualPort(), "localhost", "/upcheck"); respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); resp = respFuture.join(); assertEquals(200, resp.statusCode()); knownClients = Files.readAllLines(knownClientsFile); assertEquals(4, knownClients.size(), String.join("\n", knownClients)); assertEquals("#First line", knownClients.get(0)); assertEquals("foobar.com " + DUMMY_FINGERPRINT, knownClients.get(1)); assertEquals("foo.com " + fooFingerprint, knownClients.get(2)); assertEquals("bar.com " + barFingerprint, knownClients.get(3)); } @Test void shouldReplaceFingerprint() throws Exception { HttpClientRequest req = fooClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); HttpClientResponse resp = respFuture.join(); assertEquals(200, resp.statusCode()); List knownClients = Files.readAllLines(knownClientsFile); assertEquals(3, knownClients.size(), String.join("\n", knownClients)); assertEquals("#First line", knownClients.get(0)); assertEquals("foobar.com " + DUMMY_FINGERPRINT, knownClients.get(1)); assertEquals("foo.com " + fooFingerprint, knownClients.get(2)); req = foobarClient.get(httpServer.actualPort(), "localhost", "/upcheck"); respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); resp = respFuture.join(); assertEquals(200, resp.statusCode()); knownClients = Files.readAllLines(knownClientsFile); assertEquals(3, knownClients.size(), String.join("\n", knownClients)); assertEquals("#First line", knownClients.get(0)); assertEquals("foobar.com " + foobarFingerprint, knownClients.get(1)); assertEquals("foo.com " + fooFingerprint, knownClients.get(2)); } } cava-0.6.0/net/src/test/java/net/consensys/cava/net/tls/ServerCaOrTofaTest.java000066400000000000000000000152031341750772100273600ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static net.consensys.cava.net.tls.SecurityTestUtils.DUMMY_FINGERPRINT; import static net.consensys.cava.net.tls.TLS.certificateHexFingerprint; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.junit.TempDirectory; import net.consensys.cava.junit.TempDirectoryExtension; import net.consensys.cava.junit.VertxExtension; import net.consensys.cava.junit.VertxInstance; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import io.vertx.core.Vertx; import io.vertx.core.http.ClientAuth; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpClientResponse; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.net.SelfSignedCertificate; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) @ExtendWith(VertxExtension.class) class ServerCaOrTofaTest { private static HttpClient caClient; private static String fooFingerprint; private static HttpClient fooClient; private static HttpClient foobarClient; private Path knownClientsFile; private HttpServer httpServer; @BeforeAll static void setupClients(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { SelfSignedCertificate caClientCert = SelfSignedCertificate.create(); SecurityTestUtils.configureJDKTrustStore(tempDir, caClientCert); caClient = vertx.createHttpClient( new HttpClientOptions().setTrustOptions(InsecureTrustOptions.INSTANCE).setSsl(true).setKeyCertOptions( caClientCert.keyCertOptions())); SelfSignedCertificate fooCert = SelfSignedCertificate.create("foo.com"); fooFingerprint = certificateHexFingerprint(Paths.get(fooCert.keyCertOptions().getCertPath())); HttpClientOptions fooClientOptions = new HttpClientOptions(); fooClientOptions .setSsl(true) .setKeyCertOptions(fooCert.keyCertOptions()) .setTrustOptions(InsecureTrustOptions.INSTANCE) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); fooClient = vertx.createHttpClient(fooClientOptions); SelfSignedCertificate foobarCert = SelfSignedCertificate.create("foobar.com"); HttpClientOptions foobarClientOptions = new HttpClientOptions(); foobarClientOptions .setSsl(true) .setKeyCertOptions(foobarCert.keyCertOptions()) .setTrustOptions(InsecureTrustOptions.INSTANCE) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); foobarClient = vertx.createHttpClient(foobarClientOptions); } @BeforeEach void startServer(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { knownClientsFile = tempDir.resolve("known-clients.txt"); Files.write(knownClientsFile, Arrays.asList("#First line", "foobar.com " + DUMMY_FINGERPRINT)); SelfSignedCertificate serverCert = SelfSignedCertificate.create(); HttpServerOptions options = new HttpServerOptions(); options .setSsl(true) .setClientAuth(ClientAuth.REQUIRED) .setPemKeyCertOptions(serverCert.keyCertOptions()) .setTrustOptions(VertxTrustOptions.trustClientOnFirstAccess(knownClientsFile)) .setIdleTimeout(1500) .setReuseAddress(true) .setReusePort(true); httpServer = vertx.createHttpServer(options); SecurityTestUtils.configureAndStartTestServer(httpServer); } @AfterEach void stopServer() { httpServer.close(); } @AfterAll static void cleanupClients() { caClient.close(); fooClient.close(); foobarClient.close(); } @Test void shouldValidateUsingCertificate() throws Exception { HttpClientRequest req = caClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); HttpClientResponse resp = respFuture.join(); assertEquals(200, resp.statusCode()); List knownClients = Files.readAllLines(knownClientsFile); assertEquals(2, knownClients.size(), "CA verified host should not have been recorded"); assertEquals("#First line", knownClients.get(0)); assertEquals("foobar.com " + DUMMY_FINGERPRINT, knownClients.get(1)); } @Test void shouldValidateOnFirstUse() throws Exception { HttpClientRequest req = fooClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); HttpClientResponse resp = respFuture.join(); assertEquals(200, resp.statusCode()); List knownClients = Files.readAllLines(knownClientsFile); assertEquals(3, knownClients.size()); assertEquals("#First line", knownClients.get(0)); assertEquals("foobar.com " + DUMMY_FINGERPRINT, knownClients.get(1)); assertEquals("foo.com " + fooFingerprint, knownClients.get(2)); } @Test void shouldRejectDifferentCertificate() { HttpClientRequest req = foobarClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); Throwable e = assertThrows(CompletionException.class, respFuture::join); e = e.getCause().getCause(); assertTrue(e.getMessage().contains("certificate_unknown")); } } cava-0.6.0/net/src/test/java/net/consensys/cava/net/tls/ServerCaOrWhitelistTest.java000066400000000000000000000142131341750772100304430ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static net.consensys.cava.net.tls.TLS.certificateHexFingerprint; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.junit.TempDirectory; import net.consensys.cava.junit.TempDirectoryExtension; import net.consensys.cava.junit.VertxExtension; import net.consensys.cava.junit.VertxInstance; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import io.vertx.core.Vertx; import io.vertx.core.http.ClientAuth; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpClientResponse; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.net.SelfSignedCertificate; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) @ExtendWith(VertxExtension.class) class ServerCaOrWhitelistTest { private static HttpClient caClient; private static String fooFingerprint; private static HttpClient fooClient; private static HttpClient barClient; private Path knownClientsFile; private HttpServer httpServer; @BeforeAll static void setupClients(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { SelfSignedCertificate caClientCert = SelfSignedCertificate.create(); SecurityTestUtils.configureJDKTrustStore(tempDir, caClientCert); caClient = vertx.createHttpClient( new HttpClientOptions().setTrustOptions(InsecureTrustOptions.INSTANCE).setSsl(true).setKeyCertOptions( caClientCert.keyCertOptions())); SelfSignedCertificate fooCert = SelfSignedCertificate.create("foo.com"); fooFingerprint = certificateHexFingerprint(Paths.get(fooCert.keyCertOptions().getCertPath())); HttpClientOptions fooClientOptions = new HttpClientOptions(); fooClientOptions .setSsl(true) .setKeyCertOptions(fooCert.keyCertOptions()) .setTrustOptions(InsecureTrustOptions.INSTANCE) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); fooClient = vertx.createHttpClient(fooClientOptions); SelfSignedCertificate barCert = SelfSignedCertificate.create("bar.com"); HttpClientOptions barClientOptions = new HttpClientOptions(); barClientOptions .setSsl(true) .setKeyCertOptions(barCert.keyCertOptions()) .setTrustOptions(InsecureTrustOptions.INSTANCE) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); barClient = vertx.createHttpClient(barClientOptions); } @BeforeEach void startServer(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { knownClientsFile = tempDir.resolve("known-clients.txt"); Files.write(knownClientsFile, Arrays.asList("#First line", "foo.com " + fooFingerprint)); SelfSignedCertificate serverCert = SelfSignedCertificate.create(); HttpServerOptions options = new HttpServerOptions(); options .setSsl(true) .setClientAuth(ClientAuth.REQUIRED) .setPemKeyCertOptions(serverCert.keyCertOptions()) .setTrustOptions(VertxTrustOptions.whitelistClients(knownClientsFile)) .setIdleTimeout(1500) .setReuseAddress(true) .setReusePort(true); httpServer = vertx.createHttpServer(options); SecurityTestUtils.configureAndStartTestServer(httpServer); } @AfterEach void stopServer() throws Exception { httpServer.close(); List knownClients = Files.readAllLines(knownClientsFile); assertEquals(2, knownClients.size()); assertEquals("#First line", knownClients.get(0)); assertEquals("foo.com " + fooFingerprint, knownClients.get(1)); } @AfterAll static void cleanupClients() { caClient.close(); fooClient.close(); barClient.close(); } @Test void shouldValidateUsingCertificate() { HttpClientRequest req = caClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); HttpClientResponse resp = respFuture.join(); assertEquals(200, resp.statusCode()); } @Test void shouldValidateWhitelisted() { HttpClientRequest req = fooClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); HttpClientResponse resp = respFuture.join(); assertEquals(200, resp.statusCode()); } @Test void shouldRejectNonWhitelisted() { HttpClientRequest req = barClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); Throwable e = assertThrows(CompletionException.class, respFuture::join); e = e.getCause().getCause(); assertTrue(e.getMessage().contains("certificate_unknown")); } } cava-0.6.0/net/src/test/java/net/consensys/cava/net/tls/ServerRecordTest.java000066400000000000000000000220551341750772100271430ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static net.consensys.cava.net.tls.SecurityTestUtils.DUMMY_FINGERPRINT; import static net.consensys.cava.net.tls.TLS.certificateHexFingerprint; import static org.junit.jupiter.api.Assertions.assertEquals; import net.consensys.cava.junit.TempDirectory; import net.consensys.cava.junit.TempDirectoryExtension; import net.consensys.cava.junit.VertxExtension; import net.consensys.cava.junit.VertxInstance; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import io.vertx.core.Vertx; import io.vertx.core.http.ClientAuth; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpClientResponse; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.net.SelfSignedCertificate; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) @ExtendWith(VertxExtension.class) class ServerRecordTest { private static String caFingerprint; private static HttpClient caClient; private static String fooFingerprint; private static HttpClient fooClient; private static String barFingerprint; private static HttpClient barClient; private static String foobarFingerprint; private static HttpClient foobarClient; private Path knownClientsFile; private HttpServer httpServer; @BeforeAll static void setupClients(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { SelfSignedCertificate caClientCert = SelfSignedCertificate.create("example.com"); caFingerprint = certificateHexFingerprint(Paths.get(caClientCert.keyCertOptions().getCertPath())); SecurityTestUtils.configureJDKTrustStore(tempDir, caClientCert); caClient = vertx.createHttpClient( new HttpClientOptions().setTrustOptions(InsecureTrustOptions.INSTANCE).setSsl(true).setKeyCertOptions( caClientCert.keyCertOptions())); SelfSignedCertificate fooCert = SelfSignedCertificate.create("foo.com"); fooFingerprint = certificateHexFingerprint(Paths.get(fooCert.keyCertOptions().getCertPath())); HttpClientOptions fooClientOptions = new HttpClientOptions(); fooClientOptions .setSsl(true) .setKeyCertOptions(fooCert.keyCertOptions()) .setTrustOptions(InsecureTrustOptions.INSTANCE) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); fooClient = vertx.createHttpClient(fooClientOptions); SelfSignedCertificate barCert = SelfSignedCertificate.create("bar.com"); barFingerprint = certificateHexFingerprint(Paths.get(barCert.keyCertOptions().getCertPath())); HttpClientOptions barClientOptions = new HttpClientOptions(); barClientOptions .setSsl(true) .setKeyCertOptions(barCert.keyCertOptions()) .setTrustOptions(InsecureTrustOptions.INSTANCE) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); barClient = vertx.createHttpClient(barClientOptions); SelfSignedCertificate foobarCert = SelfSignedCertificate.create("foobar.com"); foobarFingerprint = certificateHexFingerprint(Paths.get(foobarCert.keyCertOptions().getCertPath())); HttpClientOptions foobarClientOptions = new HttpClientOptions(); foobarClientOptions .setSsl(true) .setKeyCertOptions(foobarCert.keyCertOptions()) .setTrustOptions(InsecureTrustOptions.INSTANCE) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); foobarClient = vertx.createHttpClient(foobarClientOptions); } @BeforeEach void startServer(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { knownClientsFile = tempDir.resolve("known-clients.txt"); Files.write(knownClientsFile, Arrays.asList("#First line", "foobar.com " + DUMMY_FINGERPRINT)); SelfSignedCertificate serverCert = SelfSignedCertificate.create(); HttpServerOptions options = new HttpServerOptions(); options .setSsl(true) .setClientAuth(ClientAuth.REQUIRED) .setPemKeyCertOptions(serverCert.keyCertOptions()) .setTrustOptions(VertxTrustOptions.recordClientFingerprints(knownClientsFile, false)) .setIdleTimeout(1500) .setReuseAddress(true) .setReusePort(true); httpServer = vertx.createHttpServer(options); SecurityTestUtils.configureAndStartTestServer(httpServer); } @AfterEach void stopServer() { httpServer.close(); } @AfterAll static void cleanupClients() { caClient.close(); fooClient.close(); barClient.close(); foobarClient.close(); } @Test void shouldNotValidateUsingCertificate() throws Exception { HttpClientRequest req = caClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); HttpClientResponse resp = respFuture.join(); assertEquals(200, resp.statusCode()); List knownClients = Files.readAllLines(knownClientsFile); assertEquals(3, knownClients.size(), "CA verified host should not have been recorded"); assertEquals("#First line", knownClients.get(0)); assertEquals("foobar.com " + DUMMY_FINGERPRINT, knownClients.get(1)); assertEquals("example.com " + caFingerprint, knownClients.get(2)); } @Test void shouldRecordMultipleFingerprints() throws Exception { HttpClientRequest req = fooClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); HttpClientResponse resp = respFuture.join(); assertEquals(200, resp.statusCode()); List knownClients = Files.readAllLines(knownClientsFile); assertEquals(3, knownClients.size(), String.join("\n", knownClients)); assertEquals("#First line", knownClients.get(0)); assertEquals("foobar.com " + DUMMY_FINGERPRINT, knownClients.get(1)); assertEquals("foo.com " + fooFingerprint, knownClients.get(2)); req = barClient.get(httpServer.actualPort(), "localhost", "/upcheck"); respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); resp = respFuture.join(); assertEquals(200, resp.statusCode()); knownClients = Files.readAllLines(knownClientsFile); assertEquals(4, knownClients.size(), String.join("\n", knownClients)); assertEquals("#First line", knownClients.get(0)); assertEquals("foobar.com " + DUMMY_FINGERPRINT, knownClients.get(1)); assertEquals("foo.com " + fooFingerprint, knownClients.get(2)); assertEquals("bar.com " + barFingerprint, knownClients.get(3)); } @Test void shouldReplaceFingerprint() throws Exception { HttpClientRequest req = fooClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); HttpClientResponse resp = respFuture.join(); assertEquals(200, resp.statusCode()); List knownClients = Files.readAllLines(knownClientsFile); assertEquals(3, knownClients.size(), String.join("\n", knownClients)); assertEquals("#First line", knownClients.get(0)); assertEquals("foobar.com " + DUMMY_FINGERPRINT, knownClients.get(1)); assertEquals("foo.com " + fooFingerprint, knownClients.get(2)); req = foobarClient.get(httpServer.actualPort(), "localhost", "/upcheck"); respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); resp = respFuture.join(); assertEquals(200, resp.statusCode()); knownClients = Files.readAllLines(knownClientsFile); assertEquals(3, knownClients.size(), String.join("\n", knownClients)); assertEquals("#First line", knownClients.get(0)); assertEquals("foobar.com " + foobarFingerprint, knownClients.get(1)); assertEquals("foo.com " + fooFingerprint, knownClients.get(2)); } } cava-0.6.0/net/src/test/java/net/consensys/cava/net/tls/ServerTofaTest.java000066400000000000000000000154711341750772100266220ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static net.consensys.cava.net.tls.SecurityTestUtils.DUMMY_FINGERPRINT; import static net.consensys.cava.net.tls.TLS.certificateHexFingerprint; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.junit.TempDirectory; import net.consensys.cava.junit.TempDirectoryExtension; import net.consensys.cava.junit.VertxExtension; import net.consensys.cava.junit.VertxInstance; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import io.vertx.core.Vertx; import io.vertx.core.http.ClientAuth; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpClientResponse; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.net.SelfSignedCertificate; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) @ExtendWith(VertxExtension.class) class ServerTofaTest { private static String caFingerprint; private static HttpClient caClient; private static String fooFingerprint; private static HttpClient fooClient; private static HttpClient foobarClient; private Path knownClientsFile; private HttpServer httpServer; @BeforeAll static void setupClients(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { SelfSignedCertificate caClientCert = SelfSignedCertificate.create("example.com"); caFingerprint = certificateHexFingerprint(Paths.get(caClientCert.keyCertOptions().getCertPath())); SecurityTestUtils.configureJDKTrustStore(tempDir, caClientCert); caClient = vertx.createHttpClient( new HttpClientOptions().setTrustOptions(InsecureTrustOptions.INSTANCE).setSsl(true).setKeyCertOptions( caClientCert.keyCertOptions())); SelfSignedCertificate fooCert = SelfSignedCertificate.create("foo.com"); fooFingerprint = certificateHexFingerprint(Paths.get(fooCert.keyCertOptions().getCertPath())); HttpClientOptions fooClientOptions = new HttpClientOptions(); fooClientOptions .setSsl(true) .setKeyCertOptions(fooCert.keyCertOptions()) .setTrustOptions(InsecureTrustOptions.INSTANCE) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); fooClient = vertx.createHttpClient(fooClientOptions); SelfSignedCertificate foobarCert = SelfSignedCertificate.create("foobar.com"); HttpClientOptions foobarClientOptions = new HttpClientOptions(); foobarClientOptions .setSsl(true) .setKeyCertOptions(foobarCert.keyCertOptions()) .setTrustOptions(InsecureTrustOptions.INSTANCE) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); foobarClient = vertx.createHttpClient(foobarClientOptions); } @BeforeEach void startServer(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { knownClientsFile = tempDir.resolve("known-clients.txt"); Files.write(knownClientsFile, Arrays.asList("#First line", "foobar.com " + DUMMY_FINGERPRINT)); SelfSignedCertificate serverCert = SelfSignedCertificate.create(); HttpServerOptions options = new HttpServerOptions(); options .setSsl(true) .setClientAuth(ClientAuth.REQUIRED) .setPemKeyCertOptions(serverCert.keyCertOptions()) .setTrustOptions(VertxTrustOptions.trustClientOnFirstAccess(knownClientsFile, false)) .setIdleTimeout(1500) .setReuseAddress(true) .setReusePort(true); httpServer = vertx.createHttpServer(options); SecurityTestUtils.configureAndStartTestServer(httpServer); } @AfterEach void stopServer() { httpServer.close(); } @AfterAll static void cleanupClients() { caClient.close(); fooClient.close(); foobarClient.close(); } @Test void shouldNotValidateUsingCertificate() throws Exception { HttpClientRequest req = caClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); HttpClientResponse resp = respFuture.join(); assertEquals(200, resp.statusCode()); List knownClients = Files.readAllLines(knownClientsFile); assertEquals(3, knownClients.size()); assertEquals("#First line", knownClients.get(0)); assertEquals("foobar.com " + DUMMY_FINGERPRINT, knownClients.get(1)); assertEquals("example.com " + caFingerprint, knownClients.get(2)); } @Test void shouldValidateOnFirstUse() throws Exception { HttpClientRequest req = fooClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); HttpClientResponse resp = respFuture.join(); assertEquals(200, resp.statusCode()); List knownClients = Files.readAllLines(knownClientsFile); assertEquals(3, knownClients.size()); assertEquals("#First line", knownClients.get(0)); assertEquals("foobar.com " + DUMMY_FINGERPRINT, knownClients.get(1)); assertEquals("foo.com " + fooFingerprint, knownClients.get(2)); } @Test void shouldRejectDifferentCertificate() { HttpClientRequest req = foobarClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); Throwable e = assertThrows(CompletionException.class, respFuture::join); e = e.getCause().getCause(); assertTrue(e.getMessage().contains("certificate_unknown")); } } cava-0.6.0/net/src/test/java/net/consensys/cava/net/tls/ServerWhitelistTest.java000066400000000000000000000143441341750772100277030ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static net.consensys.cava.net.tls.TLS.certificateHexFingerprint; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.junit.TempDirectory; import net.consensys.cava.junit.TempDirectoryExtension; import net.consensys.cava.junit.VertxExtension; import net.consensys.cava.junit.VertxInstance; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import io.vertx.core.Vertx; import io.vertx.core.http.ClientAuth; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpClientResponse; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.net.SelfSignedCertificate; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) @ExtendWith(VertxExtension.class) class ServerWhitelistTest { private static HttpClient caClient; private static String fooFingerprint; private static HttpClient fooClient; private static HttpClient barClient; private Path knownClientsFile; private HttpServer httpServer; @BeforeAll static void setupClients(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { SelfSignedCertificate caClientCert = SelfSignedCertificate.create(); SecurityTestUtils.configureJDKTrustStore(tempDir, caClientCert); caClient = vertx.createHttpClient( new HttpClientOptions().setTrustOptions(InsecureTrustOptions.INSTANCE).setSsl(true).setKeyCertOptions( caClientCert.keyCertOptions())); SelfSignedCertificate fooCert = SelfSignedCertificate.create("foo.com"); fooFingerprint = certificateHexFingerprint(Paths.get(fooCert.keyCertOptions().getCertPath())); HttpClientOptions fooClientOptions = new HttpClientOptions(); fooClientOptions .setSsl(true) .setKeyCertOptions(fooCert.keyCertOptions()) .setTrustOptions(InsecureTrustOptions.INSTANCE) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); fooClient = vertx.createHttpClient(fooClientOptions); SelfSignedCertificate barCert = SelfSignedCertificate.create("bar.com"); HttpClientOptions barClientOptions = new HttpClientOptions(); barClientOptions .setSsl(true) .setKeyCertOptions(barCert.keyCertOptions()) .setTrustOptions(InsecureTrustOptions.INSTANCE) .setConnectTimeout(1500) .setReuseAddress(true) .setReusePort(true); barClient = vertx.createHttpClient(barClientOptions); } @BeforeEach void startServer(@TempDirectory Path tempDir, @VertxInstance Vertx vertx) throws Exception { knownClientsFile = tempDir.resolve("known-clients.txt"); Files.write(knownClientsFile, Arrays.asList("#First line", "foo.com " + fooFingerprint)); SelfSignedCertificate serverCert = SelfSignedCertificate.create(); HttpServerOptions options = new HttpServerOptions(); options .setSsl(true) .setClientAuth(ClientAuth.REQUIRED) .setPemKeyCertOptions(serverCert.keyCertOptions()) .setTrustOptions(VertxTrustOptions.whitelistClients(knownClientsFile, false)) .setIdleTimeout(1500) .setReuseAddress(true) .setReusePort(true); httpServer = vertx.createHttpServer(options); SecurityTestUtils.configureAndStartTestServer(httpServer); } @AfterEach void stopServer() throws Exception { httpServer.close(); List knownClients = Files.readAllLines(knownClientsFile); assertEquals(2, knownClients.size()); assertEquals("#First line", knownClients.get(0)); assertEquals("foo.com " + fooFingerprint, knownClients.get(1)); } @AfterAll static void cleanupClients() { caClient.close(); fooClient.close(); barClient.close(); } @Test void shouldNotValidateUsingCertificate() { HttpClientRequest req = caClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); Throwable e = assertThrows(CompletionException.class, respFuture::join); e = e.getCause().getCause(); assertTrue(e.getMessage().contains("certificate_unknown")); } @Test void shouldValidateWhitelisted() { HttpClientRequest req = fooClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); HttpClientResponse resp = respFuture.join(); assertEquals(200, resp.statusCode()); } @Test void shouldRejectNonWhitelisted() { HttpClientRequest req = barClient.get(httpServer.actualPort(), "localhost", "/upcheck"); CompletableFuture respFuture = new CompletableFuture<>(); req.handler(respFuture::complete).exceptionHandler(respFuture::completeExceptionally).end(); Throwable e = assertThrows(CompletionException.class, respFuture::join); e = e.getCause().getCause(); assertTrue(e.getMessage().contains("certificate_unknown")); } } cava-0.6.0/net/src/test/java/net/consensys/cava/net/tls/TLSTest.java000066400000000000000000000100151341750772100251710ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.net.tls; import static net.consensys.cava.net.tls.TLS.readPemFile; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.junit.BouncyCastleExtension; import net.consensys.cava.junit.TempDirectory; import net.consensys.cava.junit.TempDirectoryExtension; import java.io.ByteArrayInputStream; import java.nio.file.Files; import java.nio.file.Path; import java.security.KeyFactory; import java.security.KeyPair; import java.security.Signature; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.spec.PKCS8EncodedKeySpec; import java.util.concurrent.ThreadLocalRandom; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(TempDirectoryExtension.class) @ExtendWith(BouncyCastleExtension.class) class TLSTest { @Test void createCertificateIfFilesAreNotThere(@TempDirectory Path tempDir) throws Exception { Path certificate = tempDir.resolve("foo").resolve("server.crt"); Path key = tempDir.resolve("foo").resolve("server.key"); boolean wasCreated = TLS.createSelfSignedCertificateIfMissing(key, certificate); assertTrue(wasCreated); assertTrue(Files.exists(certificate)); assertTrue(Files.exists(key)); } @Test void doesNotGenerateSelfSignedCertificateIfCertFileExists(@TempDirectory Path tempDir) throws Exception { Path certificate = tempDir.resolve("server1.crt"); Path key = tempDir.resolve("server1.key"); Files.createFile(certificate); boolean wasCreated = TLS.createSelfSignedCertificateIfMissing(key, certificate); assertFalse(wasCreated); assertTrue(Files.exists(certificate)); assertFalse(Files.exists(key)); } @Test void doesNotGenerateSelfSignedCertificateIfKeyFileExists(@TempDirectory Path tempDir) throws Exception { Path certificate = tempDir.resolve("server2.crt"); Path key = tempDir.resolve("server2.key"); Files.createFile(key); boolean wasCreated = TLS.createSelfSignedCertificateIfMissing(key, certificate); assertFalse(wasCreated); assertFalse(Files.exists(certificate)); assertTrue(Files.exists(key)); } @Test void autoGeneratedCertsAreValid(@TempDirectory Path tempDir) throws Exception { Path certificate = tempDir.resolve("server3.crt"); Path key = tempDir.resolve("server3.key"); boolean wasCreated = TLS.createSelfSignedCertificateIfMissing(key, certificate); assertTrue(wasCreated); checkKeyPair(key, certificate); } private void checkKeyPair(Path key, Path cert) throws Exception { PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(readPemFile(key)); CertificateFactory cf = CertificateFactory.getInstance("X.509"); Certificate certificate = cf.generateCertificate(new ByteArrayInputStream(Files.readAllBytes(cert))); KeyFactory kf = KeyFactory.getInstance("RSA"); KeyPair keyPair = new KeyPair(certificate.getPublicKey(), kf.generatePrivate(pkcs8KeySpec)); byte[] challenge = new byte[10000]; ThreadLocalRandom.current().nextBytes(challenge); // sign using the private key Signature sig = Signature.getInstance("SHA256withRSA"); sig.initSign(keyPair.getPrivate()); sig.update(challenge); byte[] signature = sig.sign(); // verify signature using the public key sig.initVerify(keyPair.getPublic()); sig.update(challenge); assertTrue(sig.verify(signature)); } } cava-0.6.0/rlp/000077500000000000000000000000001341750772100132335ustar00rootroot00000000000000cava-0.6.0/rlp/build.gradle000066400000000000000000000005271341750772100155160ustar00rootroot00000000000000description = 'Recursive Length Prefix (RLP) encoding and decoding.' dependencies { compile project(':bytes') compileOnly project(':units') testCompile project(':units') testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/rlp/src/000077500000000000000000000000001341750772100140225ustar00rootroot00000000000000cava-0.6.0/rlp/src/main/000077500000000000000000000000001341750772100147465ustar00rootroot00000000000000cava-0.6.0/rlp/src/main/java/000077500000000000000000000000001341750772100156675ustar00rootroot00000000000000cava-0.6.0/rlp/src/main/java/net/000077500000000000000000000000001341750772100164555ustar00rootroot00000000000000cava-0.6.0/rlp/src/main/java/net/consensys/000077500000000000000000000000001341750772100205015ustar00rootroot00000000000000cava-0.6.0/rlp/src/main/java/net/consensys/cava/000077500000000000000000000000001341750772100214135ustar00rootroot00000000000000cava-0.6.0/rlp/src/main/java/net/consensys/cava/rlp/000077500000000000000000000000001341750772100222105ustar00rootroot00000000000000cava-0.6.0/rlp/src/main/java/net/consensys/cava/rlp/AccumulatingRLPWriter.java000066400000000000000000000055071341750772100272510ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlp; import static java.util.Objects.requireNonNull; import static net.consensys.cava.rlp.RLP.encodeByteArray; import static net.consensys.cava.rlp.RLP.encodeLength; import static net.consensys.cava.rlp.RLP.encodeNumber; import net.consensys.cava.bytes.Bytes; import java.util.ArrayDeque; import java.util.Deque; import java.util.function.Consumer; final class AccumulatingRLPWriter implements RLPWriter { private static final int COMBINE_THRESHOLD = 32; private ArrayDeque values = new ArrayDeque<>(); Deque values() { return values; } @Override public void writeRLP(Bytes value) { requireNonNull(value); appendBytes(value.toArrayUnsafe()); } @Override public void writeValue(Bytes value) { requireNonNull(value); writeByteArray(value.toArrayUnsafe()); } @Override public void writeByteArray(byte[] value) { encodeByteArray(value, this::appendBytes); } @Override public void writeByte(byte value) { encodeByteArray(new byte[] {value}, this::appendBytes); } @Override public void writeLong(long value) { appendBytes(encodeNumber(value)); } @Override public void writeList(Consumer fn) { requireNonNull(fn); AccumulatingRLPWriter listWriter = new AccumulatingRLPWriter(); fn.accept(listWriter); int totalSize = 0; for (byte[] value : listWriter.values) { try { totalSize = Math.addExact(totalSize, value.length); } catch (ArithmeticException e) { throw new IllegalArgumentException("Combined length of values is too long (> Integer.MAX_VALUE)"); } } appendBytes(encodeLength(totalSize, 0xc0)); this.values.addAll(listWriter.values); } private void appendBytes(byte[] bytes) { if (bytes.length < COMBINE_THRESHOLD) { if (!values.isEmpty()) { byte[] last = values.getLast(); if (last.length <= (COMBINE_THRESHOLD - bytes.length)) { byte[] combined = new byte[last.length + bytes.length]; System.arraycopy(last, 0, combined, 0, last.length); System.arraycopy(bytes, 0, combined, last.length, bytes.length); values.pollLast(); values.add(combined); return; } } } values.add(bytes); } } cava-0.6.0/rlp/src/main/java/net/consensys/cava/rlp/ByteBufferRLPWriter.java000066400000000000000000000044021341750772100266630ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlp; import static java.util.Objects.requireNonNull; import static net.consensys.cava.rlp.RLP.encodeByteArray; import static net.consensys.cava.rlp.RLP.encodeLength; import static net.consensys.cava.rlp.RLP.encodeNumber; import net.consensys.cava.bytes.Bytes; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.util.Deque; import java.util.function.Consumer; final class ByteBufferRLPWriter implements RLPWriter { private ByteBuffer buffer; ByteBufferRLPWriter(ByteBuffer buffer) { this.buffer = buffer; } @Override public void writeRLP(Bytes value) { buffer.put(value.toArrayUnsafe()); } @Override public void writeValue(Bytes value) { encodeByteArray(value.toArrayUnsafe(), buffer::put); } @Override public void writeByteArray(byte[] value) { encodeByteArray(value, buffer::put); } @Override public void writeByte(byte value) { encodeByteArray(new byte[] {value}, buffer::put); } @Override public void writeLong(long value) { buffer.put(encodeNumber(value)); } @Override public void writeList(Consumer fn) { requireNonNull(fn); AccumulatingRLPWriter listWriter = new AccumulatingRLPWriter(); fn.accept(listWriter); writeEncodedValuesAsList(listWriter.values()); } private void writeEncodedValuesAsList(Deque values) { int totalSize = 0; for (byte[] value : values) { try { totalSize = Math.addExact(totalSize, value.length); } catch (ArithmeticException e) { throw new BufferOverflowException(); } } buffer.put(encodeLength(totalSize, 0xc0)); values.forEach(bytes -> buffer.put(bytes)); } } cava-0.6.0/rlp/src/main/java/net/consensys/cava/rlp/BytesRLPReader.java000066400000000000000000000204241341750772100256440ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlp; import net.consensys.cava.bytes.Bytes; import java.util.function.Function; final class BytesRLPReader implements RLPReader { private final Bytes content; private boolean lenient; private int index = 0; BytesRLPReader(Bytes content, boolean lenient) { this.content = content; this.lenient = lenient; } @Override public boolean isLenient() { return lenient; } @Override public Bytes readValue(boolean lenient) { int remaining = content.size() - index; if (remaining == 0) { throw new EndOfRLPException(); } int prefix = (((int) content.get(index)) & 0xFF); if (prefix <= 0x7f) { return content.slice(index++, 1); } remaining--; if (prefix <= 0xb7) { int length = prefix - 0x80; if (remaining < length) { throw new InvalidRLPEncodingException( "Insufficient bytes in RLP encoding: expected " + length + " but have only " + remaining); } Bytes bytes = content.slice(index + 1, length); if (!lenient && length == 1 && (bytes.get(0) & 0xFF) <= 0x7f) { throw new InvalidRLPEncodingException("Value should have been encoded as a single byte " + bytes.toHexString()); } index += 1 + length; return bytes; } if (prefix <= 0xbf) { int lengthOfLength = prefix - 0xb7; if (remaining < lengthOfLength) { throw new InvalidRLPEncodingException( "Insufficient bytes in RLP encoding: expected " + lengthOfLength + " but have only " + remaining); } remaining -= lengthOfLength; int length = getLength(lengthOfLength, lenient, "value"); if (remaining < length) { throw new InvalidRLPEncodingException( "Insufficient bytes in RLP encoding: expected " + length + " but have only " + remaining); } index += 1 + lengthOfLength; Bytes bytes = content.slice(index, length); index += length; return bytes; } throw new InvalidRLPTypeException("Attempted to read a value but next item is a list"); } @Override public boolean nextIsList() { int remaining = content.size() - index; if (remaining == 0) { throw new EndOfRLPException(); } int prefix = (((int) content.get(index)) & 0xFF); return prefix > 0xbf; } @Override public boolean nextIsEmpty() { int remaining = content.size() - index; if (remaining == 0) { throw new EndOfRLPException(); } int prefix = (((int) content.get(index)) & 0xFF); return prefix == 0x80; } @Override public T readList(boolean lenient, Function fn) { return fn.apply(new BytesRLPReader(readList(lenient), lenient)); } @Override public void skipNext(boolean lenient) { int remaining = content.size() - index; if (remaining == 0) { throw new EndOfRLPException(); } int prefix = (((int) content.get(index)) & 0xFF); if (prefix <= 0x7f) { index++; return; } remaining--; if (prefix <= 0xb7) { int length = prefix - 0x80; if (remaining < length) { throw new InvalidRLPEncodingException( "Insufficient bytes in RLP encoding: expected " + length + " but have only " + remaining); } if (!lenient && length == 1 && (content.get(index + 1) & 0xFF) <= 0x7f) { throw new InvalidRLPEncodingException( "Value should have been encoded as a single byte " + content.slice(index + 1, 1).toHexString()); } index += 1 + length; return; } if (prefix <= 0xbf) { int lengthOfLength = prefix - 0xb7; if (remaining < lengthOfLength) { throw new InvalidRLPEncodingException( "Insufficient bytes in RLP encoding: expected " + lengthOfLength + " but have only " + remaining); } remaining -= lengthOfLength; int length = getLength(lengthOfLength, lenient, "value"); if (remaining < length) { throw new InvalidRLPEncodingException( "Insufficient bytes in RLP encoding: expected " + length + " but have only " + remaining); } index += 1 + lengthOfLength + length; return; } if (prefix <= 0xf7) { int length = prefix - 0xc0; if (remaining < length) { throw new InvalidRLPEncodingException( "Insufficient bytes in RLP encoding: expected " + length + " but have only " + remaining); } index += 1 + length; return; } int lengthOfLength = prefix - 0xf7; if (remaining < lengthOfLength) { throw new InvalidRLPEncodingException( "Insufficient bytes in RLP encoding: expected " + lengthOfLength + " but have only " + remaining); } remaining -= lengthOfLength; int length = getLength(lengthOfLength, lenient, "list"); if (remaining < length) { throw new InvalidRLPEncodingException( "Insufficient bytes in RLP encoding: expected " + lengthOfLength + " but have only " + remaining); } index += 1 + lengthOfLength + length; } @Override public int remaining() { int oldIndex = index; try { int count = 0; while (!isComplete()) { count++; skipNext(); } return count; } finally { index = oldIndex; } } @Override public boolean isComplete() { return (content.size() - index) == 0; } private Bytes readList(boolean lenient) { int remaining = content.size() - index; if (remaining == 0) { throw new EndOfRLPException(); } int prefix = (((int) content.get(index)) & 0xFF); if (prefix <= 0xbf) { throw new InvalidRLPTypeException("Attempted to read a list but next item is a value"); } remaining--; if (prefix <= 0xf7) { int length = prefix - 0xc0; if (remaining < length) { throw new InvalidRLPEncodingException( "Insufficient bytes in RLP encoding: expected " + length + " but have only " + remaining); } index++; Bytes bytes = content.slice(index, length); index += length; return bytes; } int lengthOfLength = prefix - 0xf7; if (remaining < lengthOfLength) { throw new InvalidRLPEncodingException( "Insufficient bytes in RLP encoding: expected " + lengthOfLength + " but have only " + remaining); } remaining -= lengthOfLength; int length = getLength(lengthOfLength, lenient, "list"); if (remaining < length) { throw new InvalidRLPEncodingException( "Insufficient bytes in RLP encoding: expected " + length + " but have only " + remaining); } index += 1 + lengthOfLength; Bytes bytes = content.slice(index, length); index += length; return bytes; } private int getLength(int lengthOfLength, boolean lenient, String type) { Bytes lengthBytes = content.slice(index + 1, lengthOfLength); if (!lenient) { if (lengthBytes.hasLeadingZeroByte()) { throw new InvalidRLPEncodingException("RLP " + type + " length contains leading zero bytes"); } } else { lengthBytes = lengthBytes.trimLeadingZeros(); } if (lengthBytes.size() == 0) { throw new InvalidRLPEncodingException("RLP " + type + " length is zero"); } // Check if the length is greater than a 4 byte integer if (lengthBytes.size() > 4) { throw new InvalidRLPEncodingException("RLP " + type + " length is oversized"); } int length = lengthBytes.toInt(); if (length < 0) { // Java ints are two's compliment, so this was oversized throw new InvalidRLPEncodingException("RLP " + type + " length is oversized"); } assert length > 0; if (!lenient && length <= 55) { throw new InvalidRLPEncodingException("RLP " + type + " length of " + length + " was not minimally encoded"); } return length; } } cava-0.6.0/rlp/src/main/java/net/consensys/cava/rlp/BytesRLPWriter.java000066400000000000000000000020201341750772100257060ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlp; import net.consensys.cava.bytes.Bytes; import java.util.Deque; final class BytesRLPWriter extends DelegatingRLPWriter { BytesRLPWriter() { super(new AccumulatingRLPWriter()); } Bytes toBytes() { Deque values = delegate.values(); if (values.isEmpty()) { return Bytes.EMPTY; } return Bytes.wrap(values.stream().map(Bytes::wrap).toArray(Bytes[]::new)); } } cava-0.6.0/rlp/src/main/java/net/consensys/cava/rlp/DelegatingRLPWriter.java000066400000000000000000000034731341750772100267000ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlp; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.units.bigints.UInt256; import java.math.BigInteger; import java.util.function.Consumer; class DelegatingRLPWriter implements RLPWriter { T delegate; DelegatingRLPWriter(T delegate) { this.delegate = delegate; } @Override public void writeRLP(Bytes value) { delegate.writeRLP(value); } @Override public void writeValue(Bytes value) { delegate.writeValue(value); } @Override public void writeByteArray(byte[] value) { delegate.writeByteArray(value); } @Override public void writeByte(byte value) { delegate.writeByte(value); } @Override public void writeInt(int value) { delegate.writeInt(value); } @Override public void writeLong(long value) { delegate.writeLong(value); } @Override public void writeUInt256(UInt256 value) { delegate.writeUInt256(value); } @Override public void writeBigInteger(BigInteger value) { delegate.writeBigInteger(value); } @Override public void writeString(String str) { delegate.writeString(str); } @Override public void writeList(Consumer fn) { delegate.writeList(fn); } } cava-0.6.0/rlp/src/main/java/net/consensys/cava/rlp/EndOfRLPException.java000066400000000000000000000014721341750772100263070ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlp; /** * Indicates the end of the RLP source has been reached unexpectedly. */ public class EndOfRLPException extends RLPException { public EndOfRLPException() { super("End of RLP source reached"); } } cava-0.6.0/rlp/src/main/java/net/consensys/cava/rlp/InvalidRLPEncodingException.java000066400000000000000000000014721341750772100303510ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlp; /** * Indicates that invalid RLP encoding was encountered. */ public class InvalidRLPEncodingException extends RLPException { public InvalidRLPEncodingException(String message) { super(message); } } cava-0.6.0/rlp/src/main/java/net/consensys/cava/rlp/InvalidRLPTypeException.java000066400000000000000000000015021341750772100275360ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlp; /** * Indicates that an unexpected type was encountered when decoding RLP. */ public class InvalidRLPTypeException extends RLPException { public InvalidRLPTypeException(String message) { super(message); } } cava-0.6.0/rlp/src/main/java/net/consensys/cava/rlp/RLP.java000066400000000000000000000376301341750772100235210ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlp; import static com.google.common.base.Preconditions.checkArgument; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; import net.consensys.cava.bytes.Bytes; import java.math.BigInteger; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.ReadOnlyBufferException; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; /** * Recursive Length Prefix (RLP) encoding and decoding. */ public final class RLP { private static final byte[] EMPTY_VALUE = new byte[] {(byte) 0x80}; private RLP() {} /** * Encode values to a {@link Bytes} value. *

* Important: this method does not write any list prefix to the result. If you are writing a RLP encoded list of * values, you usually want to use {@link #encodeList(Consumer)}. * * @param fn A consumer that will be provided with a {@link RLPWriter} that can consume values. * @return The RLP encoding in a {@link Bytes} value. */ public static Bytes encode(Consumer fn) { requireNonNull(fn); BytesRLPWriter writer = new BytesRLPWriter(); fn.accept(writer); return writer.toBytes(); } /** * Encode values to a {@link ByteBuffer}. *

* Important: this method does not write any list prefix to the result. If you are writing a RLP encoded list of * values, you usually want to use {@link #encodeList(Consumer)}. * * @param buffer The buffer to write into, starting from its current position. * @param fn A consumer that will be provided with a {@link RLPWriter} that can consume values. * @param The type of the buffer. * @return The buffer. * @throws BufferOverflowException If the writer attempts to write more than the provided buffer can hold. * @throws ReadOnlyBufferException If the provided buffer is read-only. */ public static T encodeTo(T buffer, Consumer fn) { requireNonNull(fn); ByteBufferRLPWriter writer = new ByteBufferRLPWriter(buffer); fn.accept(writer); return buffer; } /** * Encode a list of values to a {@link Bytes} value. * * @param fn A consumer that will be provided with a {@link RLPWriter} that can consume values. * @return The RLP encoding in a {@link Bytes} value. */ public static Bytes encodeList(Consumer fn) { requireNonNull(fn); BytesRLPWriter writer = new BytesRLPWriter(); writer.writeList(fn); return writer.toBytes(); } /** * Encode a list of values to a {@link ByteBuffer}. * * @param buffer The buffer to write into, starting from its current position. * @param fn A consumer that will be provided with a {@link RLPWriter} that can consume values. * @param The type of the buffer. * @return The buffer. * @throws BufferOverflowException If the writer attempts to write more than the provided buffer can hold. * @throws ReadOnlyBufferException If the provided buffer is read-only. */ public static T encodeListTo(T buffer, Consumer fn) { requireNonNull(fn); ByteBufferRLPWriter writer = new ByteBufferRLPWriter(buffer); writer.writeList(fn); return buffer; } /** * Encode a value to a {@link Bytes} value. * * @param value The value to encode. * @return The RLP encoding in a {@link Bytes} value. */ public static Bytes encodeValue(Bytes value) { requireNonNull(value); return encodeValue(value.toArrayUnsafe()); } /** * Encode a value to a {@link Bytes} value. * * @param value The value to encode. * @return The RLP encoding in a {@link Bytes} value. */ public static Bytes encodeByteArray(byte[] value) { requireNonNull(value); return encodeValue(value); } private static Bytes encodeValue(byte[] value) { int maxSize = value.length + 5; ByteBuffer buffer = ByteBuffer.allocate(maxSize); encodeByteArray(value, buffer::put); return Bytes.wrap(buffer.array(), 0, buffer.position()); } static void encodeByteArray(byte[] value, Consumer appender) { requireNonNull(value); int size = value.length; if (size == 0) { appender.accept(EMPTY_VALUE); return; } if (size == 1) { byte b = value[0]; if ((b & 0xFF) <= 0x7f) { appender.accept(value); return; } } appender.accept(encodeLength(size, 0x80)); appender.accept(value); } /** * Encode a integer to a {@link Bytes} value. * * @param value The integer to encode. * @return The RLP encoding in a {@link Bytes} value. */ public static Bytes encodeInt(int value) { return encodeLong(value); } /** * Encode a long to a {@link Bytes} value. * * @param value The long to encode. * @return The RLP encoding in a {@link Bytes} value. */ public static Bytes encodeLong(long value) { return Bytes.wrap(encodeNumber(value)); } static byte[] encodeNumber(long value) { if (value == 0x00) { return EMPTY_VALUE; } if (value <= 0x7f) { return new byte[] {(byte) (value & 0xFF)}; } return encodeLongBytes(value, 0x80); } private static byte[] encodeLongBytes(long value, int offset) { int zeros = Long.numberOfLeadingZeros(value); int resultBytes = 8 - (zeros / 8); byte[] encoded = new byte[resultBytes + 1]; encoded[0] = (byte) ((offset + resultBytes) & 0xFF); int shift = 0; for (int i = 0; i < resultBytes; i++) { encoded[resultBytes - i] = (byte) ((value >> shift) & 0xFF); shift += 8; } return encoded; } /** * Encode a big integer to a {@link Bytes} value. * * @param value The big integer to encode. * @return The RLP encoding in a {@link Bytes} value. */ public static Bytes encodeBigInteger(BigInteger value) { requireNonNull(value); return encode(writer -> writer.writeBigInteger(value)); } /** * Encode a string to a {@link Bytes} value. * * @param str The string to encode. * @return The RLP encoding in a {@link Bytes} value. */ public static Bytes encodeString(String str) { requireNonNull(str); return encodeByteArray(str.getBytes(UTF_8)); } static byte[] encodeLength(int length, int offset) { if (length <= 55) { return new byte[] {(byte) ((offset + length) & 0xFF)}; } return encodeLongBytes(length, offset + 55); } /** * Read and decode RLP from a {@link Bytes} value. *

* Important: this method does not consume any list prefix from the source data. If you are reading a RLP encoded list * of values, you usually want to use {@link #decodeList(Bytes, Function)}. * * @param source The RLP encoded bytes. * @param fn A function that will be provided a {@link RLPReader}. * @param The result type of the reading function. * @return The result from the reading function. */ public static T decode(Bytes source, Function fn) { return decode(source, false, fn); } /** * Read and decode RLP from a {@link Bytes} value. *

* Important: this method does not consume any list prefix from the source data. If you are reading a RLP encoded list * of values, you usually want to use {@link #decodeList(Bytes, Function)}. * * @param source The RLP encoded bytes. * @param lenient If {@code false}, an exception will be thrown if the value is not minimally encoded. * @param fn A function that will be provided a {@link RLPReader}. * @param The result type of the reading function. * @return The result from the reading function. */ public static T decode(Bytes source, boolean lenient, Function fn) { requireNonNull(source); requireNonNull(fn); return fn.apply(new BytesRLPReader(source, lenient)); } /** * Read an RLP encoded list of values from a {@link Bytes} value. * * @param source The RLP encoded bytes. * @param fn A function that will be provided a {@link RLPReader}. * @param The result type of the reading function. * @return The result from the reading function. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws InvalidRLPTypeException If the first RLP value is not a list. */ public static T decodeList(Bytes source, Function fn) { return decodeList(source, false, fn); } /** * Read an RLP encoded list of values from a {@link Bytes} value. * * @param source The RLP encoded bytes. * @param lenient If {@code false}, an exception will be thrown if the value is not minimally encoded. * @param fn A function that will be provided a {@link RLPReader}. * @param The result type of the reading function. * @return The result from the reading function. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws InvalidRLPTypeException If the first RLP value is not a list. */ public static T decodeList(Bytes source, boolean lenient, Function fn) { requireNonNull(source); requireNonNull(fn); checkArgument(source.size() > 0, "source is empty"); return decode(source, lenient, reader -> reader.readList(fn)); } /** * Read an RLP encoded list of values from a {@link Bytes} value, populating a mutable output list. * * @param source The RLP encoded bytes. * @param fn A function that will be provided a {@link RLPReader}. * @return The list supplied to {@code fn}. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws InvalidRLPTypeException If the first RLP value is not a list. */ public static List decodeToList(Bytes source, BiConsumer> fn) { return decodeToList(source, false, fn); } /** * Read an RLP encoded list of values from a {@link Bytes} value, populating a mutable output list. * * @param source The RLP encoded bytes. * @param lenient If {@code false}, an exception will be thrown if the value is not minimally encoded. * @param fn A function that will be provided a {@link RLPReader}. * @return The list supplied to {@code fn}. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws InvalidRLPTypeException If the first RLP value is not a list. */ public static List decodeToList(Bytes source, boolean lenient, BiConsumer> fn) { requireNonNull(source); requireNonNull(fn); checkArgument(source.size() > 0, "source is empty"); return decode(source, lenient, reader -> reader.readList(fn)); } /** * Read an RLP encoded value from a {@link Bytes} value. * * @param source The RLP encoded bytes. * @return The bytes for the value. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws EndOfRLPException If there are no RLP values to read. */ public static Bytes decodeValue(Bytes source) { return decodeValue(source, false); } /** * Read an RLP encoded value from a {@link Bytes} value. * * @param source The RLP encoded bytes. * @param lenient If {@code false}, an exception will be thrown if the value is not minimally encoded. * @return The bytes for the value. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws EndOfRLPException If there are no RLP values to read. */ public static Bytes decodeValue(Bytes source, boolean lenient) { requireNonNull(source); return decode(source, lenient, RLPReader::readValue); } /** * Read an RLP encoded integer from a {@link Bytes} value. * * @param source The RLP encoded bytes. * @return An integer. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. */ public static int decodeInt(Bytes source) { return decodeInt(source, false); } /** * Read an RLP encoded integer from a {@link Bytes} value. * * @param source The RLP encoded bytes. * @param lenient If {@code false}, an exception will be thrown if the value is not minimally encoded. * @return An integer. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. */ public static int decodeInt(Bytes source, boolean lenient) { requireNonNull(source); checkArgument(source.size() > 0, "source is empty"); return decode(source, lenient, RLPReader::readInt); } /** * Read an RLP encoded long from a {@link Bytes} value. * * @param source The RLP encoded bytes. * @return A long. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. */ public static long decodeLong(Bytes source) { return decodeLong(source, false); } /** * Read an RLP encoded long from a {@link Bytes} value. * * @param source The RLP encoded bytes. * @param lenient If {@code false}, an exception will be thrown if the value is not minimally encoded. * @return A long. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. */ public static long decodeLong(Bytes source, boolean lenient) { requireNonNull(source); checkArgument(source.size() > 0, "source is empty"); return decode(source, lenient, RLPReader::readLong); } /** * Read an RLP encoded big integer from a {@link Bytes} value. * * @param source The RLP encoded bytes. * @return A {@link BigInteger}. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. */ public static BigInteger decodeBigInteger(Bytes source) { return decodeBigInteger(source, false); } /** * Read an RLP encoded big integer from a {@link Bytes} value. * * @param source The RLP encoded bytes. * @param lenient If {@code false}, an exception will be thrown if the value is not minimally encoded. * @return A {@link BigInteger}. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. */ public static BigInteger decodeBigInteger(Bytes source, boolean lenient) { requireNonNull(source); checkArgument(source.size() > 0, "source is empty"); return decode(source, lenient, RLPReader::readBigInteger); } /** * Read an RLP encoded string from a {@link Bytes} value. * * @param source The RLP encoded bytes. * @return A string. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. */ public static String decodeString(Bytes source) { return decodeString(source, false); } /** * Read an RLP encoded string from a {@link Bytes} value. * * @param source The RLP encoded bytes. * @param lenient If {@code false}, an exception will be thrown if the value is not minimally encoded. * @return A string. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. */ public static String decodeString(Bytes source, boolean lenient) { requireNonNull(source); checkArgument(source.size() > 0, "source is empty"); return decode(source, lenient, RLPReader::readString); } /** * Check if the {@link Bytes} value contains an RLP encoded list. * * @param value The value to check. * @return {@code true} if the value contains a list. */ public static boolean isList(Bytes value) { requireNonNull(value); checkArgument(value.size() > 0, "value is empty"); return decode(value, RLPReader::nextIsList); } } cava-0.6.0/rlp/src/main/java/net/consensys/cava/rlp/RLPException.java000066400000000000000000000016741341750772100253770ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlp; /** * Base type for all RLP encoding and decoding exceptions. */ public class RLPException extends RuntimeException { public RLPException(String message) { super(message); } public RLPException(Throwable cause) { super(cause); } public RLPException(String message, Throwable cause) { super(message, cause); } } cava-0.6.0/rlp/src/main/java/net/consensys/cava/rlp/RLPReader.java000066400000000000000000000327371341750772100246470ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlp; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.units.bigints.UInt256; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Function; /** * A reader for consuming values from an RLP encoded source. */ public interface RLPReader { /** * Determine if this reader is lenient by default. *

* A non-lenient reader will throw {@link InvalidRLPEncodingException} from any read method if the source RLP has not * used a minimal encoding format for the value. * * @return {@code true} if the reader is lenient, and {@code false} otherwise (default). */ boolean isLenient(); /** * Read the next value from the RLP source. * * @return The bytes for the next value. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws EndOfRLPException If there are no more RLP values to read. */ default Bytes readValue() { return readValue(isLenient()); } /** * Read the next value from the RLP source. * * @param lenient If {@code false}, an exception will be thrown if the value is not minimally encoded. * @return The bytes for the next value. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws EndOfRLPException If there are no more RLP values to read. */ Bytes readValue(boolean lenient); /** * Read a byte array from the RLP source. * * @return The byte array for the next value. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws EndOfRLPException If there are no more RLP values to read. */ default byte[] readByteArray() { return readValue().toArrayUnsafe(); } /** * Read a byte from the RLP source. * * @return The byte for the next value. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws EndOfRLPException If there are no more RLP values to read. */ default byte readByte() { return readByte(isLenient()); } /** * Read a byte from the RLP source. * * @param lenient If {@code false}, an exception will be thrown if the byte is not minimally encoded. * @return The byte for the next value. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws EndOfRLPException If there are no more RLP values to read. */ default byte readByte(boolean lenient) { Bytes bytes = readValue(lenient); if (bytes.size() != 1) { throw new InvalidRLPTypeException("Value is not a single byte"); } return bytes.get(0); } /** * Read an integer value from the RLP source. * * @return An integer. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws InvalidRLPTypeException If the next RLP value cannot be represented as an integer. * @throws EndOfRLPException If there are no more RLP values to read. */ default int readInt() { return readInt(isLenient()); } /** * Read an integer value from the RLP source. * * @param lenient If {@code false}, an exception will be thrown if the integer is not minimally encoded. * @return An integer. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source, or the integer is not minimally * encoded and `lenient` is {@code false}. * @throws InvalidRLPTypeException If the next RLP value cannot be represented as an integer. * @throws EndOfRLPException If there are no more RLP values to read. */ default int readInt(boolean lenient) { Bytes bytes = readValue(); if (!lenient && bytes.hasLeadingZeroByte()) { throw new InvalidRLPEncodingException("Integer value was not minimally encoded"); } try { return bytes.toInt(); } catch (IllegalArgumentException e) { throw new InvalidRLPTypeException("Value is too large to be represented as an int"); } } /** * Read a long value from the RLP source. * * @return A long. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws InvalidRLPTypeException If the next RLP value cannot be represented as a long. * @throws EndOfRLPException If there are no more RLP values to read. */ default long readLong() { return readLong(isLenient()); } /** * Read a long value from the RLP source. * * @param lenient If {@code false}, an exception will be thrown if the integer is not minimally encoded. * @return A long. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source, or the integer is not minimally * encoded and `lenient` is {@code false}. * @throws InvalidRLPTypeException If the next RLP value cannot be represented as a long. * @throws EndOfRLPException If there are no more RLP values to read. */ default long readLong(boolean lenient) { Bytes bytes = readValue(); if (!lenient && bytes.hasLeadingZeroByte()) { throw new InvalidRLPEncodingException("Integer value was not minimally encoded"); } try { return bytes.toLong(); } catch (IllegalArgumentException e) { throw new InvalidRLPTypeException("Value is too large to be represented as a long"); } } /** * Read a {@link UInt256} value from the RLP source. * * @return A {@link UInt256} value. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws InvalidRLPTypeException If the next RLP value cannot be represented as a long. * @throws EndOfRLPException If there are no more RLP values to read. */ default UInt256 readUInt256() { return readUInt256(isLenient()); } /** * Read a {@link UInt256} value from the RLP source. * * @param lenient If {@code false}, an exception will be thrown if the integer is not minimally encoded. * @return A {@link UInt256} value. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source, or the integer is not minimally * encoded and `lenient` is {@code false}. * @throws InvalidRLPTypeException If the next RLP value cannot be represented as a long. * @throws EndOfRLPException If there are no more RLP values to read. */ default UInt256 readUInt256(boolean lenient) { Bytes bytes = readValue(); if (!lenient && bytes.hasLeadingZeroByte()) { throw new InvalidRLPEncodingException("Integer value was not minimally encoded"); } try { return UInt256.fromBytes(bytes); } catch (IllegalArgumentException e) { throw new InvalidRLPTypeException("Value is too large to be represented as a UInt256"); } } /** * Read a big integer value from the RLP source. * * @return A big integer. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws InvalidRLPTypeException If the next RLP value cannot be represented as a big integer. * @throws EndOfRLPException If there are no more RLP values to read. */ default BigInteger readBigInteger() { return readBigInteger(isLenient()); } /** * Read a big integer value from the RLP source. * * @param lenient If {@code false}, an exception will be thrown if the integer is not minimally encoded. * @return A big integer. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source, or the integer is not minimally * encoded and `lenient` is {@code false}. * @throws InvalidRLPTypeException If the next RLP value cannot be represented as a big integer. * @throws EndOfRLPException If there are no more RLP values to read. */ default BigInteger readBigInteger(boolean lenient) { Bytes bytes = readValue(); if (!lenient && bytes.hasLeadingZeroByte()) { throw new InvalidRLPEncodingException("Integer value was not minimally encoded"); } return bytes.toUnsignedBigInteger(); } /** * Read a string value from the RLP source. * * @return A string. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws InvalidRLPTypeException If the next RLP value cannot be represented as a string. * @throws EndOfRLPException If there are no more RLP values to read. */ default String readString() { return readString(isLenient()); } /** * Read a string value from the RLP source. * * @param lenient If {@code false}, an exception will be thrown if the integer is not minimally encoded. * @return A string. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws InvalidRLPTypeException If the next RLP value cannot be represented as a string. * @throws EndOfRLPException If there are no more RLP values to read. */ default String readString(boolean lenient) { return new String(readValue(lenient).toArrayUnsafe(), UTF_8); } /** * Check if the next item to be read is a list. * * @return {@code true} if the next item to be read is a list. * @throws EndOfRLPException If there are no more RLP values to read. */ boolean nextIsList(); /** * Check if the next item to be read is empty. * * @return {@code true} if the next item to be read is empty. * @throws EndOfRLPException If there are no more RLP values to read. */ boolean nextIsEmpty(); /** * Read a list of values from the RLP source. * * @param fn A function that will be provided a {@link RLPReader}. * @param The result type of the reading function. * @return The result from the reading function. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws InvalidRLPTypeException If the next RLP value is not a list. * @throws EndOfRLPException If there are no more RLP values to read. */ default T readList(Function fn) { return readList(isLenient(), fn); } /** * Read a list of values from the RLP source. * * @param lenient If {@code false}, an exception will be thrown if the integer is not minimally encoded. * @param fn A function that will be provided a {@link RLPReader}. * @param The result type of the reading function. * @return The result from the reading function. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws InvalidRLPTypeException If the next RLP value is not a list. * @throws EndOfRLPException If there are no more RLP values to read. */ T readList(boolean lenient, Function fn); /** * Read a list of values from the RLP source, populating a mutable output list. * * @param fn A function that will be provided with a {@link RLPReader} and a mutable output list. * @return The list supplied to {@code fn}. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws InvalidRLPTypeException If the next RLP value is not a list. * @throws EndOfRLPException If there are no more RLP values to read. */ default List readList(BiConsumer> fn) { return readList(isLenient(), fn); } /** * Read a list of values from the RLP source, populating a mutable output list. * * @param lenient If {@code false}, an exception will be thrown if the integer is not minimally encoded. * @param fn A function that will be provided with a {@link RLPReader} and a mutable output list. * @return The list supplied to {@code fn}. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws InvalidRLPTypeException If the next RLP value is not a list. * @throws EndOfRLPException If there are no more RLP values to read. */ default List readList(boolean lenient, BiConsumer> fn) { requireNonNull(fn); return readList(lenient, reader -> { List list = new ArrayList<>(); fn.accept(reader, list); return list; }); } /** * Skip the next value or list in the RLP source. * * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws EndOfRLPException If there are no more RLP values to read. */ default void skipNext() { skipNext(isLenient()); } /** * Skip the next value or list in the RLP source. * * @param lenient If {@code false}, an exception will be thrown if the integer is not minimally encoded. * @throws InvalidRLPEncodingException If there is an error decoding the RLP source. * @throws EndOfRLPException If there are no more RLP values to read. */ void skipNext(boolean lenient); /** * The number of remaining values to read. * * @return The number of remaining values to read. */ int remaining(); /** * Check if all values have been read. * * @return {@code true} if all values have been read. */ boolean isComplete(); } cava-0.6.0/rlp/src/main/java/net/consensys/cava/rlp/RLPWriter.java000066400000000000000000000056751341750772100247220ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlp; import static java.nio.charset.StandardCharsets.UTF_8; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.units.bigints.UInt256; import java.math.BigInteger; import java.util.function.Consumer; /** * A writer for encoding values to RLP. */ public interface RLPWriter { /** * Append an already RLP encoded value. * *

* Note that this method may not validate that {@code value} is a valid RLP sequence. Appending an invalid RLP * sequence will cause the entire RLP encoding produced by this writer to also be invalid. * * @param value The RLP encoded bytes to append. */ void writeRLP(Bytes value); /** * Encode a {@link Bytes} value to RLP. * * @param value The byte array to encode. */ void writeValue(Bytes value); /** * Encode a byte array to RLP. * * @param value The byte array to encode. */ default void writeByteArray(byte[] value) { writeValue(Bytes.wrap(value)); } /** * Encode a byte to RLP. * * @param value The byte value to encode. */ default void writeByte(byte value) { writeValue(Bytes.of(value)); } /** * Write an integer to the output. * * @param value The integer to write. */ default void writeInt(int value) { writeLong(value); } /** * Write a long to the output. * * @param value The long value to write. */ void writeLong(long value); /** * Write a {@link UInt256} to the output. * * @param value The {@link UInt256} value to write. */ default void writeUInt256(UInt256 value) { writeValue(value.toMinimalBytes()); } /** * Write a big integer to the output. * * @param value The integer to write. */ default void writeBigInteger(BigInteger value) { if (value.signum() == 0) { writeInt(0); return; } byte[] byteArray = value.toByteArray(); if (byteArray[0] == 0) { writeValue(Bytes.wrap(byteArray).slice(1)); } else { writeByteArray(byteArray); } } /** * Write a string to the output. * * @param str The string to write. */ default void writeString(String str) { writeByteArray(str.getBytes(UTF_8)); } /** * Write a list of values. * * @param fn A consumer that will be provided with a {@link RLPWriter} that can consume values. */ void writeList(Consumer fn); } cava-0.6.0/rlp/src/main/java/net/consensys/cava/rlp/package-info.java000066400000000000000000000010521341750772100253750ustar00rootroot00000000000000/** * 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). */ @ParametersAreNonnullByDefault package net.consensys.cava.rlp; import javax.annotation.ParametersAreNonnullByDefault; cava-0.6.0/rlp/src/test/000077500000000000000000000000001341750772100150015ustar00rootroot00000000000000cava-0.6.0/rlp/src/test/java/000077500000000000000000000000001341750772100157225ustar00rootroot00000000000000cava-0.6.0/rlp/src/test/java/net/000077500000000000000000000000001341750772100165105ustar00rootroot00000000000000cava-0.6.0/rlp/src/test/java/net/consensys/000077500000000000000000000000001341750772100205345ustar00rootroot00000000000000cava-0.6.0/rlp/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100214465ustar00rootroot00000000000000cava-0.6.0/rlp/src/test/java/net/consensys/cava/rlp/000077500000000000000000000000001341750772100222435ustar00rootroot00000000000000cava-0.6.0/rlp/src/test/java/net/consensys/cava/rlp/ByteBufferWriterTest.java000066400000000000000000000127721341750772100272110ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlp; import static java.nio.charset.StandardCharsets.UTF_8; import static net.consensys.cava.bytes.Bytes.fromHexString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.units.bigints.UInt256; import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; class ByteBufferWriterTest { @ParameterizedTest @CsvSource({"8203e8, 1000", "830186a0, 100000"}) void shouldWriteSmallIntegers(String expectedHex, int value) { ByteBuffer buffer = ByteBuffer.allocate(64); RLP.encodeTo(buffer, writer -> writer.writeInt(value)); buffer.flip(); assertEquals(fromHexString(expectedHex), Bytes.wrapByteBuffer(buffer)); } @Test void shouldWriteLongIntegers() { ByteBuffer buffer = ByteBuffer.allocate(64); RLP.encodeTo(buffer, writer -> writer.writeLong(100000L)); buffer.flip(); assertEquals(fromHexString("830186a0"), Bytes.wrapByteBuffer(buffer)); } @Test void shouldWriteUInt256Integers() { ByteBuffer buffer = ByteBuffer.allocate(64); RLP.encodeTo(buffer, writer -> writer.writeUInt256(UInt256.valueOf(100000L))); buffer.flip(); assertEquals(fromHexString("830186a0"), Bytes.wrapByteBuffer(buffer)); buffer.clear(); RLP.encodeTo( buffer, writer -> writer .writeUInt256(UInt256.fromHexString("0x0400000000000000000000000000000000000000000000000000f100000000ab"))); buffer.flip(); assertEquals( fromHexString("a00400000000000000000000000000000000000000000000000000f100000000ab"), Bytes.wrapByteBuffer(buffer)); } @Test void shouldWriteBigIntegers() { ByteBuffer buffer = ByteBuffer.allocate(64); RLP.encodeTo(buffer, writer -> writer.writeBigInteger(BigInteger.valueOf(100000))); buffer.flip(); assertEquals(fromHexString("830186a0"), Bytes.wrapByteBuffer(buffer)); buffer.clear(); RLP.encodeTo(buffer, writer -> writer.writeBigInteger(BigInteger.valueOf(127).pow(16))); buffer.flip(); assertEquals(fromHexString("8ee1ceefa5bbd9ed1c97f17a1df801"), Bytes.wrapByteBuffer(buffer)); } @Test void shouldWriteEmptyStrings() { ByteBuffer buffer = ByteBuffer.allocate(64); RLP.encodeTo(buffer, writer -> writer.writeString("")); buffer.flip(); assertEquals(fromHexString("80"), Bytes.wrapByteBuffer(buffer)); } @Test void shouldWriteOneCharactersStrings() { ByteBuffer buffer = ByteBuffer.allocate(64); RLP.encodeTo(buffer, writer -> writer.writeString("d")); buffer.flip(); assertEquals(fromHexString("64"), Bytes.wrapByteBuffer(buffer)); } @Test void shouldWriteStrings() { ByteBuffer buffer = ByteBuffer.allocate(64); RLP.encodeTo(buffer, writer -> writer.writeString("dog")); buffer.flip(); assertEquals(fromHexString("83646f67"), Bytes.wrapByteBuffer(buffer)); } @Test void shouldWriteShortLists() { List strings = Arrays.asList("asdf", "qwer", "zxcv", "asdf", "qwer", "zxcv", "asdf", "qwer", "zxcv", "asdf", "qwer"); ByteBuffer buffer = ByteBuffer.allocate(64); RLP.encodeListTo(buffer, listWriter -> strings.forEach(listWriter::writeString)); buffer.flip(); assertEquals( fromHexString( "f784617364668471776572847a78637684617364668471776572847a" + "78637684617364668471776572847a78637684617364668471776572"), Bytes.wrapByteBuffer(buffer)); } @Test void shouldWriteNestedLists() { ByteBuffer buffer = ByteBuffer.allocate(1024); RLP.encodeListTo(buffer, listWriter -> { listWriter.writeString("asdf"); listWriter.writeString("qwer"); for (int i = 30; i >= 0; --i) { listWriter.writeList(subListWriter -> { subListWriter.writeString("zxcv"); subListWriter.writeString("asdf"); subListWriter.writeString("qwer"); }); } }); buffer.flip(); assertTrue(RLP.decodeList(Bytes.wrapByteBuffer(buffer), listReader -> { assertEquals("asdf", listReader.readString()); assertEquals("qwer", listReader.readString()); for (int i = 30; i >= 0; --i) { assertTrue(listReader.readList(subListReader -> { assertEquals("zxcv", subListReader.readString()); assertEquals("asdf", subListReader.readString()); assertEquals("qwer", subListReader.readString()); return true; })); } return true; })); } @Test void shouldWritePreviouslyEncodedValues() { ByteBuffer buffer = ByteBuffer.allocate(64); RLP.encodeTo(buffer, writer -> writer.writeRLP(RLP.encodeByteArray("abc".getBytes(UTF_8)))); buffer.flip(); assertEquals("abc", RLP.decodeString(Bytes.wrapByteBuffer(buffer))); } } cava-0.6.0/rlp/src/test/java/net/consensys/cava/rlp/BytesRLPReaderTest.java000066400000000000000000000305611341750772100265420ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlp; import static net.consensys.cava.bytes.Bytes.fromHexString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.bytes.Bytes; import java.math.BigInteger; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; class BytesRLPReaderTest { private static final Bytes SHORT_LIST = fromHexString( "f784617364668471776572847a78637684617364668471776572847a78637684617364668471776572847a78637684617364668471776572"); private static final Bytes LONG_LIST = fromHexString( "f90200cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf8461736" + "4668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572" + "847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf8" + "4617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471" + "776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786" + "376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364" + "668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf846173646684717765728" + "47a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84" + "617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf846173646684717" + "76572847a786376cf84617364668471776572847a786376"); private static class SomeObject { private final String name; private final int number; private final BigInteger longNumber; SomeObject(String name, int number, BigInteger longNumber) { this.name = name; this.number = number; this.longNumber = longNumber; } } @Test void shouldParseFullObjects() { Bytes bytes = fromHexString("83426f620486011F71B70768"); SomeObject readObject = RLP.decode(bytes, reader -> new SomeObject(reader.readString(), reader.readInt(), reader.readBigInteger())); assertEquals("Bob", readObject.name); assertEquals(4, readObject.number); assertEquals(BigInteger.valueOf(1234563434344L), readObject.longNumber); } @ParameterizedTest @CsvSource({"80, 0", "01, 1", "10, 16", "4f, 79", "7f, 127", "8180, 128", "8203e8, 1000", "830186a0, 100000"}) void shouldReadIntegers(String hex, int value) { assertTrue(RLP.decode(fromHexString(hex), reader -> { assertEquals(value, reader.readInt()); return true; })); } @ParameterizedTest // @formatter:off @CsvSource({ "80, ''", "00, '\u0000'", "01, '\u0001'", "7f, '\u007F'", "83646f67, dog", "b74c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c69" + ", 'Lorem ipsum dolor sit amet, consectetur adipisicing eli'", "b8384c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c6974" + ", 'Lorem ipsum dolor sit amet, consectetur adipisicing elit'", "b904004c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20437572616269747572206d6175726973206d61676e612c20737573636970697420736564207665686963756c61206e6f6e2c20696163756c697320666175636962757320746f72746f722e2050726f696e20737573636970697420756c74726963696573206d616c6573756164612e204475697320746f72746f7220656c69742c2064696374756d2071756973207472697374697175652065752c20756c7472696365732061742072697375732e204d6f72626920612065737420696d70657264696574206d6920756c6c616d636f7270657220616c6971756574207375736369706974206e6563206c6f72656d2e2041656e65616e2071756973206c656f206d6f6c6c69732c2076756c70757461746520656c6974207661726975732c20636f6e73657175617420656e696d2e204e756c6c6120756c74726963657320747572706973206a7573746f2c20657420706f73756572652075726e6120636f6e7365637465747572206e65632e2050726f696e206e6f6e20636f6e76616c6c6973206d657475732e20446f6e65632074656d706f7220697073756d20696e206d617572697320636f6e67756520736f6c6c696369747564696e2e20566573746962756c756d20616e746520697073756d207072696d697320696e206661756369627573206f726369206c756374757320657420756c74726963657320706f737565726520637562696c69612043757261653b2053757370656e646973736520636f6e76616c6c69732073656d2076656c206d617373612066617563696275732c2065676574206c6163696e6961206c616375732074656d706f722e204e756c6c61207175697320756c747269636965732070757275732e2050726f696e20617563746f722072686f6e637573206e69626820636f6e64696d656e74756d206d6f6c6c69732e20416c697175616d20636f6e73657175617420656e696d206174206d65747573206c75637475732c206120656c656966656e6420707572757320656765737461732e20437572616269747572206174206e696268206d657475732e204e616d20626962656e64756d2c206e6571756520617420617563746f72207472697374697175652c206c6f72656d206c696265726f20616c697175657420617263752c206e6f6e20696e74657264756d2074656c6c7573206c65637475732073697420616d65742065726f732e20437261732072686f6e6375732c206d65747573206163206f726e617265206375727375732c20646f6c6f72206a7573746f20756c747269636573206d657475732c20617420756c6c616d636f7270657220766f6c7574706174" + ", 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur mauris magna, suscipit sed vehicula non, iaculis faucibus tortor. Proin suscipit ultricies malesuada. Duis tortor elit, dictum quis tristique eu, ultrices at risus. Morbi a est imperdiet mi ullamcorper aliquet suscipit nec lorem. Aenean quis leo mollis, vulputate elit varius, consequat enim. Nulla ultrices turpis justo, et posuere urna consectetur nec. Proin non convallis metus. Donec tempor ipsum in mauris congue sollicitudin. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse convallis sem vel massa faucibus, eget lacinia lacus tempor. Nulla quis ultricies purus. Proin auctor rhoncus nibh condimentum mollis. Aliquam consequat enim at metus luctus, a eleifend purus egestas. Curabitur at nibh metus. Nam bibendum, neque at auctor tristique, lorem libero aliquet arcu, non interdum tellus lectus sit amet eros. Cras rhoncus, metus ac ornare cursus, dolor justo ultrices metus, at ullamcorper volutpat'" }) // @formatter:on void shouldReadStrings(String hex, String value) { assertTrue(RLP.decode(fromHexString(hex), reader -> { assertEquals(value, reader.readString()); return true; })); } @Test void shouldThrowWhenInputExhausted() { EndOfRLPException ex = assertThrows(EndOfRLPException.class, () -> RLP.decode(Bytes.EMPTY, RLPReader::readInt)); assertEquals("End of RLP source reached", ex.getMessage()); } @Test void shouldThrowWhenNextItemIsAList() { InvalidRLPTypeException ex = assertThrows(InvalidRLPTypeException.class, () -> RLP.decode(SHORT_LIST, RLPReader::readInt)); assertEquals("Attempted to read a value but next item is a list", ex.getMessage()); } @Test void shouldThrowWheSourceIsTruncated() { InvalidRLPEncodingException ex = assertThrows(InvalidRLPEncodingException.class, () -> RLP.decode(fromHexString("830186"), RLPReader::readInt)); assertEquals("Insufficient bytes in RLP encoding: expected 3 but have only 2", ex.getMessage()); } @Test void shouldThrowWhenLowValueIsntEncodedToSingleByte() { Bytes bytes1 = fromHexString("8128"); InvalidRLPEncodingException ex1 = assertThrows(InvalidRLPEncodingException.class, () -> RLP.decodeInt(bytes1)); assertEquals("Value should have been encoded as a single byte 0x28", ex1.getMessage()); assertEquals(40, RLP.decodeInt(bytes1, true)); Bytes bytes2 = fromHexString("b80128"); InvalidRLPEncodingException ex2 = assertThrows(InvalidRLPEncodingException.class, () -> RLP.decodeInt(bytes1)); assertEquals("Value should have been encoded as a single byte 0x28", ex2.getMessage()); assertEquals(40, RLP.decodeInt(bytes2, true)); } @Test void shouldThrowWhenValueLengthContainsLeadingZeros() { Bytes bytes1 = fromHexString( "b900384c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c6974"); InvalidRLPEncodingException ex1 = assertThrows(InvalidRLPEncodingException.class, () -> RLP.decodeString(bytes1)); assertEquals("RLP value length contains leading zero bytes", ex1.getMessage()); assertEquals("Lorem ipsum dolor sit amet, consectetur adipisicing elit", RLP.decodeString(bytes1, true)); Bytes bytes2 = fromHexString("bb0000000028"); InvalidRLPEncodingException ex2 = assertThrows(InvalidRLPEncodingException.class, () -> RLP.decodeInt(bytes2)); assertEquals("RLP value length contains leading zero bytes", ex2.getMessage()); InvalidRLPEncodingException ex3 = assertThrows(InvalidRLPEncodingException.class, () -> RLP.decodeInt(bytes2, true)); assertEquals("RLP value length is zero", ex3.getMessage()); Bytes bytes3 = fromHexString("bd00000000000128"); InvalidRLPEncodingException ex4 = assertThrows(InvalidRLPEncodingException.class, () -> RLP.decodeInt(bytes3)); assertEquals("RLP value length contains leading zero bytes", ex4.getMessage()); assertEquals(40, RLP.decodeInt(bytes3, true)); } @Test void shouldThrowWhenListLengthContainsLeadingZeros() { Bytes bytes1 = fromHexString("0xF9000101"); InvalidRLPEncodingException ex1 = assertThrows(InvalidRLPEncodingException.class, () -> RLP.decodeList(bytes1, RLPReader::readInt)); assertEquals("RLP list length contains leading zero bytes", ex1.getMessage()); assertEquals(Integer.valueOf(1), RLP.decodeList(bytes1, true, RLPReader::readInt)); Bytes bytes2 = fromHexString("0xF80101"); InvalidRLPEncodingException ex2 = assertThrows(InvalidRLPEncodingException.class, () -> RLP.decodeList(bytes2, RLPReader::readInt)); assertEquals("RLP list length of 1 was not minimally encoded", ex2.getMessage()); assertEquals(Integer.valueOf(1), RLP.decodeList(bytes2, true, RLPReader::readInt)); } @Test void shouldThrowWhenLengthIsOversized() { Bytes bytes1 = fromHexString("bc0aaaaaaaaa28"); InvalidRLPEncodingException ex1 = assertThrows(InvalidRLPEncodingException.class, () -> RLP.decodeInt(bytes1)); assertEquals("RLP value length is oversized", ex1.getMessage()); Bytes bytes2 = fromHexString("bb8000000128"); InvalidRLPEncodingException ex2 = assertThrows(InvalidRLPEncodingException.class, () -> RLP.decodeInt(bytes2)); assertEquals("RLP value length is oversized", ex2.getMessage()); } @Test void shouldReadShortList() { List expected = Arrays.asList("asdf", "qwer", "zxcv", "asdf", "qwer", "zxcv", "asdf", "qwer", "zxcv", "asdf", "qwer"); List result = RLP.decodeToList(SHORT_LIST, (reader, list) -> { assertEquals(11, reader.remaining()); for (int i = 10; i >= 0; --i) { list.add(reader.readString()); } }); assertEquals(expected, result); } @Test void shouldReadLongList() { List> expected = Stream.generate(() -> Arrays.asList("asdf", "qwer", "zxcv")).limit(31).collect(Collectors.toList()); List result = RLP.decodeToList(LONG_LIST, (reader, list) -> { for (int i = 30; i >= 0; --i) { list.add(reader.readList((subReader, subList) -> { subList.add(subReader.readString()); subList.add(subReader.readString()); subList.add(subReader.readString()); })); } }); assertEquals(expected, result); } } cava-0.6.0/rlp/src/test/java/net/consensys/cava/rlp/BytesRLPWriterTest.java000066400000000000000000000135331341750772100266140ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlp; import static java.nio.charset.StandardCharsets.UTF_8; import static net.consensys.cava.bytes.Bytes.fromHexString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.units.bigints.UInt256; import java.math.BigInteger; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; class BytesRLPWriterTest { private static class SomeObject { private final String name; private final int number; private final BigInteger longNumber; SomeObject(String name, int number, BigInteger longNumber) { this.name = name; this.number = number; this.longNumber = longNumber; } } @Test void shouldWriteFullObjects() { SomeObject bob = new SomeObject("Bob", 4, BigInteger.valueOf(1234563434344L)); Bytes bytes = RLP.encode(writer -> { writer.writeString(bob.name); writer.writeInt(bob.number); writer.writeBigInteger(bob.longNumber); }); assertTrue(RLP.decode(bytes, reader -> { assertEquals("Bob", reader.readString()); assertEquals(4, reader.readInt()); assertEquals(BigInteger.valueOf(1234563434344L), reader.readBigInteger()); return true; })); } @Test void shouldWriteSmallIntegers() { assertEquals(fromHexString("80"), RLP.encode(writer -> writer.writeInt(0))); assertEquals(fromHexString("01"), RLP.encode(writer -> writer.writeInt(1))); assertEquals(fromHexString("0f"), RLP.encode(writer -> writer.writeInt(15))); assertEquals(fromHexString("8203e8"), RLP.encode(writer -> writer.writeInt(1000))); assertEquals(fromHexString("820400"), RLP.encode(writer -> writer.writeInt(1024))); assertEquals(fromHexString("830186a0"), RLP.encode(writer -> writer.writeInt(100000))); } @Test void shouldWriteLongIntegers() { assertEquals(fromHexString("80"), RLP.encode(writer -> writer.writeLong(0L))); assertEquals(fromHexString("01"), RLP.encode(writer -> writer.writeLong(1))); assertEquals(fromHexString("0f"), RLP.encode(writer -> writer.writeLong(15))); assertEquals(fromHexString("8203e8"), RLP.encode(writer -> writer.writeLong(1000))); assertEquals(fromHexString("820400"), RLP.encode(writer -> writer.writeLong(1024))); assertEquals(fromHexString("830186a0"), RLP.encode(writer -> writer.writeLong(100000L))); } @Test void shouldWriteUInt256Integers() { assertEquals(fromHexString("80"), RLP.encode(writer -> writer.writeUInt256(UInt256.valueOf(0L)))); assertEquals(fromHexString("830186a0"), RLP.encode(writer -> writer.writeUInt256(UInt256.valueOf(100000L)))); assertEquals( fromHexString("a00400000000000000000000000000000000000000000000000000f100000000ab"), RLP.encode( writer -> writer.writeUInt256( UInt256.fromHexString("0x0400000000000000000000000000000000000000000000000000f100000000ab")))); } @Test void shouldWriteBigIntegers() { assertEquals(fromHexString("830186a0"), RLP.encode(writer -> writer.writeBigInteger(BigInteger.valueOf(100000)))); assertEquals( fromHexString("8ee1ceefa5bbd9ed1c97f17a1df801"), RLP.encode(writer -> writer.writeBigInteger(BigInteger.valueOf(127).pow(16)))); } @Test void shouldWriteEmptyStrings() { assertEquals(fromHexString("80"), RLP.encode(writer -> writer.writeString(""))); } @Test void shouldWriteOneCharactersStrings() { assertEquals(fromHexString("64"), RLP.encode(writer -> writer.writeString("d"))); } @Test void shouldWriteStrings() { assertEquals(fromHexString("83646f67"), RLP.encode(writer -> writer.writeString("dog"))); } @Test void shouldWriteShortLists() { List strings = Arrays.asList("asdf", "qwer", "zxcv", "asdf", "qwer", "zxcv", "asdf", "qwer", "zxcv", "asdf", "qwer"); assertEquals( fromHexString( "f784617364668471776572847a78637684617364668471776572847a" + "78637684617364668471776572847a78637684617364668471776572"), RLP.encodeList(listWriter -> strings.forEach(listWriter::writeString))); } @Test void shouldWriteNestedLists() { Bytes bytes = RLP.encodeList(listWriter -> { listWriter.writeString("asdf"); listWriter.writeString("qwer"); for (int i = 30; i >= 0; --i) { listWriter.writeList(subListWriter -> { subListWriter.writeString("zxcv"); subListWriter.writeString("asdf"); subListWriter.writeString("qwer"); }); } }); assertTrue(RLP.decodeList(bytes, listReader -> { assertEquals("asdf", listReader.readString()); assertEquals("qwer", listReader.readString()); for (int i = 30; i >= 0; --i) { assertTrue(listReader.readList(subListReader -> { assertEquals("zxcv", subListReader.readString()); assertEquals("asdf", subListReader.readString()); assertEquals("qwer", subListReader.readString()); return true; })); } return true; })); } @Test void shouldWritePreviouslyEncodedValues() { Bytes output = RLP.encode(writer -> writer.writeRLP(RLP.encodeByteArray("abc".getBytes(UTF_8)))); assertEquals("abc", RLP.decodeString(output)); } } cava-0.6.0/rlpx/000077500000000000000000000000001341750772100134235ustar00rootroot00000000000000cava-0.6.0/rlpx/build.gradle000066400000000000000000000011741341750772100157050ustar00rootroot00000000000000dependencies { compile project(':crypto') compile project(':bytes') compile project(':concurrent') compile project(':rlp') compile 'com.google.guava:guava' compile 'org.logl:logl-api' compileOnly 'io.vertx:vertx-core' compile 'org.xerial.snappy:snappy-java' compile 'org.bouncycastle:bcprov-jdk15on' testCompile project(':junit') testCompile 'io.vertx:vertx-core' testCompile 'org.bouncycastle:bcprov-jdk15on' testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testCompile 'org.logl:logl-logl' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/rlpx/src/000077500000000000000000000000001341750772100142125ustar00rootroot00000000000000cava-0.6.0/rlpx/src/main/000077500000000000000000000000001341750772100151365ustar00rootroot00000000000000cava-0.6.0/rlpx/src/main/java/000077500000000000000000000000001341750772100160575ustar00rootroot00000000000000cava-0.6.0/rlpx/src/main/java/net/000077500000000000000000000000001341750772100166455ustar00rootroot00000000000000cava-0.6.0/rlpx/src/main/java/net/consensys/000077500000000000000000000000001341750772100206715ustar00rootroot00000000000000cava-0.6.0/rlpx/src/main/java/net/consensys/cava/000077500000000000000000000000001341750772100216035ustar00rootroot00000000000000cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/000077500000000000000000000000001341750772100225705ustar00rootroot00000000000000cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/EthereumIESEncryptionEngine.java000066400000000000000000000411151341750772100307550ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx; import java.io.ByteArrayInputStream; import java.io.IOException; import java.math.BigInteger; import org.bouncycastle.crypto.*; import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.crypto.generators.EphemeralKeyPairGenerator; import org.bouncycastle.crypto.params.*; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.BigIntegers; import org.bouncycastle.util.Pack; /** * Support class for constructing integrated encryption ciphers for doing basic message exchanges on top of key * agreement ciphers. Follows the description given in IEEE Std 1363a. */ public class EthereumIESEncryptionEngine { BasicAgreement agree; DerivationFunction kdf; Mac mac; BufferedBlockCipher cipher; byte[] macBuf; // Ethereum addition: commonMac added when performing the MAC encryption. byte[] commonMac; boolean forEncryption; CipherParameters privParam, pubParam; IESParameters param; byte[] V; private EphemeralKeyPairGenerator keyPairGenerator; private KeyParser keyParser; private byte[] IV; /** * Set up for use with stream mode, where the key derivation function is used to provide a stream of bytes to xor with * the message. * * @param agree the key agreement used as the basis for the encryption * @param kdf the key derivation function used for byte generation * @param mac the message authentication code generator for the message * @param commonMac the common MAC bytes to append to the mac */ public EthereumIESEncryptionEngine(BasicAgreement agree, DerivationFunction kdf, Mac mac, byte[] commonMac) { this.agree = agree; this.kdf = kdf; this.mac = mac; this.macBuf = new byte[mac.getMacSize()]; this.commonMac = commonMac; this.cipher = null; } /** * Set up for use in conjunction with a block cipher to handle the message. It is strongly recommended that the * cipher is not in ECB mode. * * @param agree the key agreement used as the basis for the encryption * @param kdf the key derivation function used for byte generation * @param mac the message authentication code generator for the message * @param commonMac the common MAC bytes to append to the mac * @param cipher the cipher to used for encrypting the message */ public EthereumIESEncryptionEngine( BasicAgreement agree, DerivationFunction kdf, Mac mac, byte[] commonMac, BufferedBlockCipher cipher) { this.agree = agree; this.kdf = kdf; this.mac = mac; this.macBuf = new byte[mac.getMacSize()]; this.commonMac = commonMac; this.cipher = cipher; } /** * Initialise the encryptor. * * @param forEncryption whether or not this is encryption/decryption. * @param privParam our private key parameters * @param pubParam the recipient's/sender's public key parameters * @param params encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher. */ public void init( boolean forEncryption, CipherParameters privParam, CipherParameters pubParam, CipherParameters params) { this.forEncryption = forEncryption; this.privParam = privParam; this.pubParam = pubParam; this.V = new byte[0]; extractParams(params); } /** * Initialise the decryptor. * * @param publicKey the recipient's/sender's public key parameters * @param params encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher. * @param ephemeralKeyPairGenerator the ephemeral key pair generator to use. */ public void init( AsymmetricKeyParameter publicKey, CipherParameters params, EphemeralKeyPairGenerator ephemeralKeyPairGenerator) { this.forEncryption = true; this.pubParam = publicKey; this.keyPairGenerator = ephemeralKeyPairGenerator; extractParams(params); } /** * Initialise the encryptor. * * @param privateKey the recipient's private key. * @param params encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher. * @param publicKeyParser the parser for reading the ephemeral public key. */ public void init(AsymmetricKeyParameter privateKey, CipherParameters params, KeyParser publicKeyParser) { this.forEncryption = false; this.privParam = privateKey; this.keyParser = publicKeyParser; extractParams(params); } private void extractParams(CipherParameters params) { if (params instanceof ParametersWithIV) { this.IV = ((ParametersWithIV) params).getIV(); this.param = (IESParameters) ((ParametersWithIV) params).getParameters(); } else { this.IV = null; this.param = (IESParameters) params; } } public BufferedBlockCipher getCipher() { return cipher; } public Mac getMac() { return mac; } private byte[] encryptBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException { byte[] C = null, K = null, K1 = null, K2 = null; int len; if (cipher == null) { // Streaming mode. K1 = new byte[inLen]; K2 = new byte[param.getMacKeySize() / 8]; K = new byte[K1.length + K2.length]; kdf.generateBytes(K, 0, K.length); if (V.length != 0) { System.arraycopy(K, 0, K2, 0, K2.length); System.arraycopy(K, K2.length, K1, 0, K1.length); } else { System.arraycopy(K, 0, K1, 0, K1.length); System.arraycopy(K, inLen, K2, 0, K2.length); } C = new byte[inLen]; for (int i = 0; i != inLen; i++) { C[i] = (byte) (in[inOff + i] ^ K1[i]); } len = inLen; } else { // Block cipher mode. K1 = new byte[((IESWithCipherParameters) param).getCipherKeySize() / 8]; K2 = new byte[param.getMacKeySize() / 8]; K = new byte[K1.length + K2.length]; kdf.generateBytes(K, 0, K.length); System.arraycopy(K, 0, K1, 0, K1.length); System.arraycopy(K, K1.length, K2, 0, K2.length); // If iv provided use it to initialise the cipher if (IV != null) { cipher.init(true, new ParametersWithIV(new KeyParameter(K1), IV)); } else { cipher.init(true, new KeyParameter(K1)); } C = new byte[cipher.getOutputSize(inLen)]; len = cipher.processBytes(in, inOff, inLen, C, 0); len += cipher.doFinal(C, len); } // Convert the length of the encoding vector into a byte array. byte[] P2 = param.getEncodingV(); byte[] L2 = null; if (V.length != 0) { L2 = getLengthTag(P2); } // Apply the MAC. byte[] T = new byte[mac.getMacSize()]; // Ethereum change: // Instead of initializing the mac with the bytes, we initialize with the hash of the bytes. // Old code: mac.init(new KeyParameter(K2)); Digest hash = new SHA256Digest(); byte[] K2hash = new byte[hash.getDigestSize()]; hash.reset(); hash.update(K2, 0, K2.length); hash.doFinal(K2hash, 0); mac.init(new KeyParameter(K2hash)); // we also update the mac with the IV: mac.update(IV, 0, IV.length); // end of Ethereum change. mac.update(C, 0, C.length); if (P2 != null) { mac.update(P2, 0, P2.length); } if (V.length != 0) { mac.update(L2, 0, L2.length); } mac.update(commonMac, 0, commonMac.length); mac.doFinal(T, 0); // Output the triple (V,C,T). byte[] Output = new byte[V.length + len + T.length]; System.arraycopy(V, 0, Output, 0, V.length); System.arraycopy(C, 0, Output, V.length, len); System.arraycopy(T, 0, Output, V.length + len, T.length); return Output; } private byte[] decryptBlock(byte[] in_enc, int inOff, int inLen) throws InvalidCipherTextException { byte[] M, K, K1, K2; int len = 0; // Ensure that the length of the input is greater than the MAC in bytes if (inLen < V.length + mac.getMacSize()) { throw new InvalidCipherTextException("Length of input must be greater than the MAC and V combined"); } // note order is important: set up keys, do simple encryptions, check mac, do final encryption. if (cipher == null) { // Streaming mode. K1 = new byte[inLen - V.length - mac.getMacSize()]; K2 = new byte[param.getMacKeySize() / 8]; K = new byte[K1.length + K2.length]; kdf.generateBytes(K, 0, K.length); if (V.length != 0) { System.arraycopy(K, 0, K2, 0, K2.length); System.arraycopy(K, K2.length, K1, 0, K1.length); } else { System.arraycopy(K, 0, K1, 0, K1.length); System.arraycopy(K, K1.length, K2, 0, K2.length); } // process the message M = new byte[K1.length]; for (int i = 0; i != K1.length; i++) { M[i] = (byte) (in_enc[inOff + V.length + i] ^ K1[i]); } } else { // Block cipher mode. K1 = new byte[((IESWithCipherParameters) param).getCipherKeySize() / 8]; K2 = new byte[param.getMacKeySize() / 8]; K = new byte[K1.length + K2.length]; kdf.generateBytes(K, 0, K.length); System.arraycopy(K, 0, K1, 0, K1.length); System.arraycopy(K, K1.length, K2, 0, K2.length); CipherParameters cp = new KeyParameter(K1); // If IV provide use it to initialize the cipher if (IV != null) { cp = new ParametersWithIV(cp, IV); } cipher.init(false, cp); M = new byte[cipher.getOutputSize(inLen - V.length - mac.getMacSize())]; // do initial processing len = cipher.processBytes(in_enc, inOff + V.length, inLen - V.length - mac.getMacSize(), M, 0); } // Convert the length of the encoding vector into a byte array. byte[] P2 = param.getEncodingV(); byte[] L2 = null; if (V.length != 0) { L2 = getLengthTag(P2); } // Verify the MAC. int end = inOff + inLen; byte[] T1 = Arrays.copyOfRange(in_enc, end - mac.getMacSize(), end); byte[] T2 = new byte[T1.length]; // Ethereum change: // Instead of initializing the mac with the bytes, we initialize with the hash of the bytes. // Old code: mac.init(new KeyParameter(K2)); Digest hash = new SHA256Digest(); byte[] K2hash = new byte[hash.getDigestSize()]; hash.reset(); hash.update(K2, 0, K2.length); hash.doFinal(K2hash, 0); mac.init(new KeyParameter(K2hash)); // we also update the mac with the IV: mac.update(IV, 0, IV.length); // end of Ethereum change. mac.update(in_enc, inOff + V.length, inLen - V.length - T2.length); if (P2 != null) { mac.update(P2, 0, P2.length); } if (V.length != 0) { mac.update(L2, 0, L2.length); } mac.update(commonMac, 0, commonMac.length); mac.doFinal(T2, 0); if (!Arrays.constantTimeAreEqual(T1, T2)) { throw new InvalidCipherTextException("invalid MAC"); } if (cipher == null) { return M; } else { len += cipher.doFinal(M, len); return Arrays.copyOfRange(M, 0, len); } } public byte[] processBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException { if (forEncryption) { if (keyPairGenerator != null) { EphemeralKeyPair ephKeyPair = keyPairGenerator.generate(); this.privParam = ephKeyPair.getKeyPair().getPrivate(); this.V = ephKeyPair.getEncodedPublicKey(); } } else { if (keyParser != null) { ByteArrayInputStream bIn = new ByteArrayInputStream(in, inOff, inLen); try { this.pubParam = keyParser.readKey(bIn); } catch (IOException e) { throw new InvalidCipherTextException("unable to recover ephemeral public key: " + e.getMessage(), e); } catch (IllegalArgumentException e) { throw new InvalidCipherTextException("unable to recover ephemeral public key: " + e.getMessage(), e); } int encLength = (inLen - bIn.available()); this.V = Arrays.copyOfRange(in, inOff, inOff + encLength); } } // Compute the common value and convert to byte array. agree.init(privParam); BigInteger z = agree.calculateAgreement(pubParam); byte[] Z = BigIntegers.asUnsignedByteArray(agree.getFieldSize(), z); // Create input to KDF. if (V.length != 0) { byte[] VZ = Arrays.concatenate(V, Z); Arrays.fill(Z, (byte) 0); Z = VZ; } try { // Initialise the KDF. KDFParameters kdfParam = new KDFParameters(Z, param.getDerivationV()); kdf.init(kdfParam); return forEncryption ? encryptBlock(in, inOff, inLen) : decryptBlock(in, inOff, inLen); } finally { Arrays.fill(Z, (byte) 0); } } // as described in Shroup's paper and P1363a protected byte[] getLengthTag(byte[] p2) { byte[] L2 = new byte[8]; if (p2 != null) { Pack.longToBigEndian(p2.length * 8L, L2, 0); } return L2; } /** * Basic KDF generator for derived keys and ivs as defined by IEEE P1363a/ISO 18033
* This implementation is based on ISO 18033/P1363a. *

* This class has been adapted from the BaseKDFBytesGenerator implementation of Bouncy Castle. Only one * change is present specifically for Ethereum. */ static class ECIESHandshakeKDFFunction implements DigestDerivationFunction { private int counterStart; private Digest digest; private byte[] shared; private byte[] iv; /** * Construct a KDF Parameters generator. *

* * @param counterStart value of counter. * @param digest the digest to be used as the source of derived keys. */ protected ECIESHandshakeKDFFunction(int counterStart, Digest digest) { this.counterStart = counterStart; this.digest = digest; } @Override public void init(DerivationParameters param) { if (param instanceof KDFParameters) { KDFParameters p = (KDFParameters) param; shared = p.getSharedSecret(); iv = p.getIV(); } else if (param instanceof ISO18033KDFParameters) { ISO18033KDFParameters p = (ISO18033KDFParameters) param; shared = p.getSeed(); iv = null; } else { throw new IllegalArgumentException("KDF parameters required for generator"); } } /** * return the underlying digest. */ @Override public Digest getDigest() { return digest; } /** * fill len bytes of the output buffer with bytes generated from the derivation function. * * @throws IllegalArgumentException if the size of the request will cause an overflow. * @throws DataLengthException if the out buffer is too small. */ @Override public int generateBytes(byte[] out, int outOff, int len) throws DataLengthException, IllegalArgumentException { if ((out.length - len) < outOff) { throw new OutputLengthException("output buffer too small"); } long oBytes = len; int outLen = digest.getDigestSize(); // // this is at odds with the standard implementation, the // maximum value should be hBits * (2^32 - 1) where hBits // is the digest output size in bits. We can't have an // array with a long index at the moment... // if (oBytes > ((2L << 32) - 1)) { throw new IllegalArgumentException("Output length too large"); } int cThreshold = (int) ((oBytes + outLen - 1) / outLen); byte[] dig = new byte[digest.getDigestSize()]; byte[] C = new byte[4]; Pack.intToBigEndian(counterStart, C, 0); int counterBase = counterStart & ~0xFF; for (int i = 0; i < cThreshold; i++) { // only change for Ethereum: invert those 2 lines. digest.update(C, 0, C.length); digest.update(shared, 0, shared.length); // End of change for Ethereum. if (iv != null) { digest.update(iv, 0, iv.length); } digest.doFinal(dig, 0); if (len > outLen) { System.arraycopy(dig, 0, out, outOff, outLen); outOff += outLen; len -= outLen; } else { System.arraycopy(dig, 0, out, outOff, len); } if (++C[3] == 0) { counterBase += 0x100; Pack.intToBigEndian(counterBase, C, 0); } } digest.reset(); return (int) oBytes; } } } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/HandshakeMessage.java000066400000000000000000000017571341750772100266400ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx; import net.consensys.cava.bytes.Bytes32; import net.consensys.cava.crypto.SECP256K1; /** * Contents of a message sent as part of a RLPx handshake. */ public interface HandshakeMessage { /** * @return the ephemeral public key included in the response */ public SECP256K1.PublicKey ephemeralPublicKey(); /** * @return the response nonce */ public Bytes32 nonce(); } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/InitiatorHandshakeMessage.java000066400000000000000000000060021341750772100305070ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import net.consensys.cava.crypto.SECP256K1; import net.consensys.cava.crypto.SECP256K1.KeyPair; import net.consensys.cava.crypto.SECP256K1.PublicKey; import net.consensys.cava.crypto.SECP256K1.SecretKey; import net.consensys.cava.crypto.SECP256K1.Signature; import net.consensys.cava.rlp.RLP; /** * The initial message sent during a RLPx handshake. */ final class InitiatorHandshakeMessage implements HandshakeMessage { static final int VERSION = 4; private final PublicKey publicKey; private final Signature signature; private final PublicKey ephemeralPublicKey; private final Bytes32 nonce; private InitiatorHandshakeMessage( PublicKey publicKey, Signature signature, PublicKey ephemeralPublicKey, Bytes32 nonce) { this.publicKey = publicKey; this.signature = signature; this.ephemeralPublicKey = ephemeralPublicKey; this.nonce = nonce; } static InitiatorHandshakeMessage create( PublicKey ourPubKey, KeyPair ephemeralKeyPair, Bytes32 staticSharedSecret, Bytes32 nonce) { Bytes32 toSign = staticSharedSecret.xor(nonce); return new InitiatorHandshakeMessage( ourPubKey, SECP256K1.signHashed(toSign, ephemeralKeyPair), ephemeralKeyPair.publicKey(), nonce); } static InitiatorHandshakeMessage decode(Bytes payload, SecretKey privateKey) { return RLP.decodeList(payload, reader -> { Signature signature = Signature.fromBytes(reader.readValue()); PublicKey pubKey = PublicKey.fromBytes(reader.readValue()); Bytes32 nonce = Bytes32.wrap(reader.readValue()); Bytes32 staticSharedSecret = SECP256K1.calculateKeyAgreement(privateKey, pubKey); Bytes32 toSign = staticSharedSecret.xor(nonce); PublicKey ephemeralPublicKey = PublicKey.recoverFromHashAndSignature(toSign, signature); return new InitiatorHandshakeMessage(pubKey, signature, ephemeralPublicKey, nonce); }); } Bytes encode() { return RLP.encodeList(writer -> { writer.writeValue(signature.bytes()); writer.writeValue(publicKey.bytes()); writer.writeValue(nonce); writer.writeInt(VERSION); }); } PublicKey publicKey() { return publicKey; } @Override public PublicKey ephemeralPublicKey() { return ephemeralPublicKey; } @Override public Bytes32 nonce() { return nonce; } } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/InvalidMACException.java000066400000000000000000000015751341750772100272310ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx; /** * Exception thrown when the message contents do not match the Message Authentication Code. */ public class InvalidMACException extends RuntimeException { InvalidMACException(Throwable t) { super(t); } InvalidMACException(String msg) { super(msg); } } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/MemoryWireConnectionsRepository.java000066400000000000000000000026441341750772100320430ustar00rootroot00000000000000/* * Copyright 2019 ConsenSys AG. * * 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.rlpx; import net.consensys.cava.rlpx.wire.WireConnection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * In-memory implementation of the wire connections repository. * */ public class MemoryWireConnectionsRepository implements WireConnectionRepository { private final Map connections = new ConcurrentHashMap<>(); @Override public void add(WireConnection wireConnection) { connections.put(wireConnection.id(), wireConnection); } @Override public WireConnection get(String id) { return connections.get(id); } @Override public Iterable asIterable() { return connections.values(); } @Override public void close() { connections.clear(); } public Map asMap() { return connections; } } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/RLPxConnection.java000066400000000000000000000260261341750772100263060ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import net.consensys.cava.bytes.MutableBytes; import net.consensys.cava.crypto.SECP256K1; import net.consensys.cava.rlp.RLP; import net.consensys.cava.rlpx.wire.HelloMessage; import java.io.IOException; import java.util.Arrays; import java.util.Objects; import java.util.function.Consumer; import org.bouncycastle.crypto.digests.KeccakDigest; import org.bouncycastle.crypto.engines.AESEngine; import org.bouncycastle.crypto.modes.SICBlockCipher; import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.ParametersWithIV; import org.xerial.snappy.Snappy; /** * Connection between 2 peers over the RLPx protocol. *

* The RLPx protocol creates a exchange of unique secrets during an initial handshake. The peers proceed to communicate * using the shared secrets. *

* This connection allows encrypting and decrypting messages with a remote peer. */ public final class RLPxConnection { /** * Checks if two RLPx connections represent both ends of a connection. *

* Used for testing. * * @param one one RLPx connection * @param other an other RLPx connection * @return true if both connections refer to the same peers, on each side of the connection */ public static boolean isComplementedBy(RLPxConnection one, RLPxConnection other) { return Objects.equals(one.aesSecret, other.aesSecret) && Objects.equals(one.macSecret, other.macSecret) && Objects.equals(one.token, other.token) && Objects.equals(snapshot(one.egressMac), snapshot(other.ingressMac)) && Objects.equals(snapshot(one.ingressMac), snapshot(other.egressMac)); } private static Bytes32 snapshot(KeccakDigest digest) { byte[] out = new byte[32]; new KeccakDigest(digest).doFinal(out, 0); return Bytes32.wrap(out); } private final Bytes32 aesSecret; private final Bytes32 macSecret; private final Bytes32 token; private final KeccakDigest egressMac = new KeccakDigest(Bytes32.SIZE * 8); private final KeccakDigest ingressMac = new KeccakDigest(Bytes32.SIZE * 8); private final SECP256K1.PublicKey publicKey; private final SECP256K1.PublicKey peerPublicKey; private final AESEngine macEncryptionEngine; private boolean applySnappyCompression = false; private Bytes buffer = Bytes.EMPTY; RLPxConnection( Bytes32 aesSecret, Bytes32 macSecret, Bytes32 token, Bytes egressMac, Bytes ingressMac, SECP256K1.PublicKey publicKey, SECP256K1.PublicKey peerPublicKey) { this.aesSecret = aesSecret; this.macSecret = macSecret; this.token = token; KeyParameter macKey = new KeyParameter(macSecret.toArrayUnsafe()); macEncryptionEngine = new AESEngine(); macEncryptionEngine.init(true, macKey); updateEgress(egressMac); updateIngress(ingressMac); this.publicKey = publicKey; this.peerPublicKey = peerPublicKey; } /** * * @return our public key associated with this connection */ public SECP256K1.PublicKey publicKey() { return publicKey; } /** * * @return the public key of the peer associated with this connection */ public SECP256K1.PublicKey peerPublicKey() { return peerPublicKey; } public void configureAfterHandshake(HelloMessage helloMessage) { this.applySnappyCompression = helloMessage.p2pVersion() >= 5; } public synchronized void stream(Bytes newBytes, Consumer messageConsumer) { buffer = Bytes.concatenate(buffer, newBytes); RLPxMessage message = null; do { message = readFrame(buffer); if (message != null) { buffer = buffer.slice(message.bytesLength()); messageConsumer.accept(message); } } while (buffer.size() != 0 && message != null); } public RLPxMessage readFrame(Bytes messageFrame) { if (messageFrame.size() < 32) { return null; } KeyParameter aesKey = new KeyParameter(aesSecret.toArrayUnsafe()); byte[] IV = new byte[16]; Arrays.fill(IV, (byte) 0); SICBlockCipher decryptionCipher = new SICBlockCipher(new AESEngine()); decryptionCipher.init(false, new ParametersWithIV(aesKey, IV)); Bytes macBytes = messageFrame.slice(16, 16); Bytes headerBytes = messageFrame.slice(0, 16); Bytes decryptedHeader = Bytes.wrap(new byte[16]); decryptionCipher.processBytes(headerBytes.toArrayUnsafe(), 0, 16, decryptedHeader.toArrayUnsafe(), 0); int frameSize = decryptedHeader.get(0) & 0xff; frameSize = (frameSize << 8) + (decryptedHeader.get(1) & 0xff); frameSize = (frameSize << 8) + (decryptedHeader.get(2) & 0xff); int pad = frameSize % 16 == 0 ? 0 : 16 - frameSize % 16; if (messageFrame.size() < 32 + frameSize + pad + 16) { return null; } Bytes expectedMac = calculateMac(headerBytes, true); if (!macBytes.equals(expectedMac)) { throw new InvalidMACException( String.format( "Header MAC did not match expected MAC; expected: %s, received: %s", expectedMac.toHexString(), macBytes.toHexString())); } Bytes frameData = messageFrame.slice(32, frameSize); Bytes frameMac = messageFrame.slice(32 + frameSize + pad, 16); Bytes newFrameMac = Bytes.wrap(new byte[16]); Bytes frameMacSeed = updateIngress(messageFrame.slice(32, frameSize + pad)); macEncryptionEngine.processBlock(frameMacSeed.toArrayUnsafe(), 0, newFrameMac.toArrayUnsafe(), 0); Bytes expectedFrameMac = updateIngress(newFrameMac.xor(frameMacSeed.slice(0, 16))).slice(0, 16); if (!expectedFrameMac.equals(frameMac)) { throw new InvalidMACException( String.format( "Frame MAC did not match expected MAC; expected: %s, received: %s", expectedFrameMac.toHexString(), frameMac.toHexString())); } Bytes decryptedFrameData = Bytes.wrap(new byte[frameData.size()]); decryptionCipher .processBytes(frameData.toArrayUnsafe(), 0, frameData.size(), decryptedFrameData.toArrayUnsafe(), 0); int messageType = RLP.decodeInt(decryptedFrameData.slice(0, 1)); Bytes messageData = decryptedFrameData.slice(1); if (applySnappyCompression) { try { messageData = Bytes.wrap(Snappy.uncompress(messageData.toArrayUnsafe())); } catch (IOException e) { throw new IllegalArgumentException(e); } } return new RLPxMessage(messageType, messageData, 32 + frameSize + pad + 16); } /** * Frames a message for sending to an RLPx peer, encrypting it and calculating the appropriate MACs. * * @param message The message to frame. * @return The framed message, as byte buffer. */ public Bytes write(RLPxMessage message) { KeyParameter aesKey = new KeyParameter(aesSecret.toArrayUnsafe()); byte[] IV = new byte[16]; Arrays.fill(IV, (byte) 0); SICBlockCipher encryptionCipher = new SICBlockCipher(new AESEngine()); encryptionCipher.init(true, new ParametersWithIV(aesKey, IV)); // Compress message Bytes messageData = message.content(); if (applySnappyCompression) { try { messageData = Bytes.wrap(Snappy.compress(messageData.toArrayUnsafe())); } catch (IOException e) { throw new IllegalArgumentException(e); } } int frameSize = messageData.size() + 1; int pad = frameSize % 16 == 0 ? 0 : 16 - frameSize % 16; // Generate the header data. MutableBytes frameSizeBytes = MutableBytes.create(3); frameSizeBytes.set(0, (byte) ((frameSize >> 16) & 0xff)); frameSizeBytes.set(1, (byte) ((frameSize >> 8) & 0xff)); frameSizeBytes.set(2, (byte) (frameSize & 0xff)); Bytes protocolHeader = RLP.encodeList(writer -> { writer.writeValue(Bytes.EMPTY); writer.writeValue(Bytes.EMPTY); }); byte[] zeros = new byte[16 - frameSizeBytes.size() - protocolHeader.size()]; Arrays.fill(zeros, (byte) 0x00); Bytes headerBytes = Bytes.concatenate(frameSizeBytes, protocolHeader, Bytes.wrap(zeros)); Bytes encryptedHeaderBytes = Bytes.wrap(new byte[16]); encryptionCipher.processBytes(headerBytes.toArrayUnsafe(), 0, 16, encryptedHeaderBytes.toArrayUnsafe(), 0); Bytes headerMac = calculateMac(encryptedHeaderBytes, false); Bytes idBytes = RLP.encodeInt(message.messageId()); assert idBytes.size() == 1; Bytes encryptedPayload = Bytes.wrap(new byte[idBytes.size() + messageData.size() + pad]); encryptionCipher.processBytes( Bytes.concatenate(idBytes, messageData, Bytes.wrap(new byte[pad])).toArrayUnsafe(), 0, encryptedPayload.size(), encryptedPayload.toArrayUnsafe(), 0); // Calculate the frame MAC. Bytes payloadMacSeed = updateEgress(encryptedPayload).slice(0, 16); Bytes payloadMac = Bytes.wrap(new byte[16]); macEncryptionEngine.processBlock(payloadMacSeed.toArrayUnsafe(), 0, payloadMac.toArrayUnsafe(), 0); payloadMac = updateEgress(payloadMacSeed.xor(payloadMac)).slice(0, 16); Bytes finalBytes = Bytes.concatenate(encryptedHeaderBytes, headerMac, encryptedPayload, payloadMac); return finalBytes; } private Bytes calculateMac(Bytes input, boolean ingress) { Bytes mac = Bytes.wrap(new byte[16]); macEncryptionEngine.processBlock( snapshot(ingress ? ingressMac : egressMac).slice(0, 16).toArrayUnsafe(), 0, mac.toArrayUnsafe(), 0); mac = mac.xor(input); if (ingress) { mac = updateIngress(mac).slice(0, 16); } else { mac = updateEgress(mac).slice(0, 16); } return mac.slice(0, 16); } private Bytes32 updateEgress(Bytes bytes) { egressMac.update(bytes.toArrayUnsafe(), 0, bytes.size()); return snapshot(egressMac); } private Bytes32 updateIngress(Bytes bytes) { ingressMac.update(bytes.toArrayUnsafe(), 0, bytes.size()); return snapshot(ingressMac); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } RLPxConnection that = (RLPxConnection) obj; return Objects.equals(aesSecret, that.aesSecret) && Objects.equals(macSecret, that.macSecret) && Objects.equals(token, that.token) && Objects.equals(snapshot(egressMac), snapshot(that.egressMac)) && Objects.equals(snapshot(ingressMac), snapshot(that.ingressMac)); } @Override public int hashCode() { return Objects .hash(Objects.hashCode(aesSecret), Objects.hashCode(macSecret), Objects.hashCode(token), egressMac, ingressMac); } } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/RLPxConnectionFactory.java000066400000000000000000000327411341750772100276370ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx; import static net.consensys.cava.bytes.Bytes.concatenate; import static net.consensys.cava.crypto.Hash.keccak256; import static net.consensys.cava.crypto.SECP256K1.Parameters.CURVE; import static net.consensys.cava.crypto.SECP256K1.calculateKeyAgreement; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import net.consensys.cava.concurrent.AsyncResult; import net.consensys.cava.crypto.SECP256K1.KeyPair; import net.consensys.cava.crypto.SECP256K1.PublicKey; import net.consensys.cava.crypto.SECP256K1.SecretKey; import java.math.BigInteger; import java.security.SecureRandom; import java.util.function.Consumer; import java.util.function.Function; import org.bouncycastle.crypto.BasicAgreement; import org.bouncycastle.crypto.BufferedBlockCipher; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.agreement.ECDHBasicAgreement; import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.crypto.engines.AESEngine; import org.bouncycastle.crypto.macs.HMac; import org.bouncycastle.crypto.modes.SICBlockCipher; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.params.IESWithCipherParameters; import org.bouncycastle.crypto.params.KDFParameters; import org.bouncycastle.crypto.params.ParametersWithIV; import org.bouncycastle.util.BigIntegers; /** * Factory creating RLPxConnection, either from initiating a handshake or responding to a handshake request. */ public final class RLPxConnectionFactory { private static final SecureRandom random = new SecureRandom(); /** * Creates a complete interaction to run a handshake with a remote peer. * * @param keyPair our key pair * @param remotePublicKey the peer public key * @param initAndResponse a function giving us the peer response, and allowing us to respond to them to finalize the * handshake * @return a future RLPxConnection created as the result of the handshake */ public static AsyncResult createHandshake( KeyPair keyPair, PublicKey remotePublicKey, Function> initAndResponse) { Bytes32 nonce = generateRandomBytes32(); KeyPair ephemeralKeyPair = KeyPair.random(); Bytes initHandshakeMessage = init(keyPair, remotePublicKey, ephemeralKeyPair, nonce); AsyncResult response = initAndResponse.apply(initHandshakeMessage); return response.thenApply(responseBytes -> { HandshakeMessage responseMessage = readResponse(responseBytes, keyPair.secretKey()); return createConnection( true, initHandshakeMessage, responseBytes, ephemeralKeyPair.secretKey(), responseMessage.ephemeralPublicKey(), nonce, responseMessage.nonce(), keyPair.publicKey(), remotePublicKey); }); } /** * Creates a RLPxConnection in response to a handshake initiation message. * * @param initiatorMessageBytes the initiation message raw bytes * @param keyPair our key pair * @param responseHandler a function to respond back to the peer that we acknowledged the connection * @return a valid RLPxConnection */ public static RLPxConnection respondToHandshake( Bytes initiatorMessageBytes, KeyPair keyPair, Consumer responseHandler) { InitiatorHandshakeMessage initiatorHandshakeMessage = read(initiatorMessageBytes, keyPair.secretKey()); Bytes32 nonce = Bytes32.wrap(new byte[32]); random.nextBytes(nonce.toArrayUnsafe()); KeyPair ephemeralKeyPair = KeyPair.random(); PublicKey initiatorPublicKey = initiatorHandshakeMessage.publicKey(); ResponderHandshakeMessage responderMessage = ResponderHandshakeMessage.create(ephemeralKeyPair.publicKey(), nonce); Bytes responseBytes = encryptMessage(responderMessage.encode(), initiatorPublicKey); responseHandler.accept(responseBytes); return createConnection( false, initiatorMessageBytes, responseBytes, ephemeralKeyPair.secretKey(), initiatorHandshakeMessage.ephemeralPublicKey(), initiatorHandshakeMessage.nonce(), nonce, keyPair.publicKey(), initiatorPublicKey); } /** * Creates a handshake initiation message using ephemeral keys and a random nonce. * * @param keyPair our key pair * @param remotePublicKey the peer public key * @param ephemeralKeyPair our ephemeral key pair for this connection * @param initiatorNonce our random nonce * @return the bytes of a handshake initiation message for a given peer */ public static Bytes init( KeyPair keyPair, PublicKey remotePublicKey, KeyPair ephemeralKeyPair, Bytes32 initiatorNonce) { Bytes32 sharedSecret = calculateKeyAgreement(keyPair.secretKey(), remotePublicKey); InitiatorHandshakeMessage message = InitiatorHandshakeMessage.create(keyPair.publicKey(), ephemeralKeyPair, sharedSecret, initiatorNonce); return encryptMessage(message.encode(), remotePublicKey); } /** * Decrypts the handshake response using our private key. * * @param response the raw response bytes * @param privateKey our private key * @return a decrypted handshake response message */ public static HandshakeMessage readResponse(Bytes response, SecretKey privateKey) { return ResponderHandshakeMessage.decode(decryptMessage(response, privateKey)); } /** * Generates a new random 32 byte array. * * @return a new Bytes32 object filled with random bytes */ public static Bytes32 generateRandomBytes32() { Bytes32 nonce = Bytes32.wrap(new byte[32]); random.nextBytes(nonce.toArrayUnsafe()); return nonce; } /** * Creates a RLPxConnection based off the complete handshake exchange. * * @param initiator whether we initiated the handshake * @param initiatorMessage the bytes of the initiation message * @param responderMessage the bytes of the response message * @param ourEphemeralPrivateKey our ephemeral private key * @param peerEphemeralPublicKey the peer ephemeral public key * @param initiatorNonce the initiation random nonce * @param responderNonce the responder random nonce * @param ourPublicKey our public key * @param peerPublicKey the public key of the peer * @return a valid RPLx connection to communicate between peers */ public static RLPxConnection createConnection( boolean initiator, Bytes initiatorMessage, Bytes responderMessage, SecretKey ourEphemeralPrivateKey, PublicKey peerEphemeralPublicKey, Bytes32 initiatorNonce, Bytes32 responderNonce, PublicKey ourPublicKey, PublicKey peerPublicKey) { Bytes agreedSecret = calculateKeyAgreement(ourEphemeralPrivateKey, peerEphemeralPublicKey); Bytes sharedSecret = keccak256(concatenate(agreedSecret, keccak256(concatenate(responderNonce, initiatorNonce)))); Bytes32 aesSecret = keccak256(concatenate(agreedSecret, sharedSecret)); Bytes32 macSecret = keccak256(concatenate(agreedSecret, aesSecret)); Bytes32 token = keccak256(sharedSecret); Bytes initiatorMac = concatenate(macSecret.xor(responderNonce), initiatorMessage); Bytes responderMac = concatenate(macSecret.xor(initiatorNonce), responderMessage); if (initiator) { return new RLPxConnection(aesSecret, macSecret, token, initiatorMac, responderMac, ourPublicKey, peerPublicKey); } else { return new RLPxConnection(aesSecret, macSecret, token, responderMac, initiatorMac, ourPublicKey, peerPublicKey); } } static InitiatorHandshakeMessage read(Bytes payload, SecretKey privateKey) { return InitiatorHandshakeMessage.decode(decryptMessage(payload, privateKey), privateKey); } static Bytes encryptMessage(Bytes message, PublicKey remoteKey) { byte[] ivb = new byte[16]; random.nextBytes(ivb); Bytes iv = Bytes.wrap(ivb); KeyPair ephemeralKeyPair = KeyPair.random(); Bytes bytes = addPadding(message); int size = bytes.size() + 65 + 16 + 32; Bytes sizePrefix = Bytes.of((byte) (size >>> 8), (byte) size); EthereumIESEncryptionEngine engine = forEncryption(remoteKey, iv, sizePrefix, ephemeralKeyPair); byte[] encrypted; try { encrypted = engine.processBlock(bytes.toArrayUnsafe(), 0, bytes.size()); } catch (InvalidCipherTextException e) { throw new IllegalArgumentException(e); } // Create the output message by concatenating the ephemeral public key (prefixed with // 0x04 to designate uncompressed), IV, and encrypted bytes. Bytes finalBytes = concatenate( Bytes.of(sizePrefix.get(0), sizePrefix.get(1), (byte) 0x04), ephemeralKeyPair.publicKey().bytes(), iv, Bytes.wrap(encrypted)); return finalBytes; } private static EthereumIESEncryptionEngine forEncryption( PublicKey pubKey, Bytes iv, Bytes commonMac, KeyPair ephemeralKeyPair) { CipherParameters pubParam = new ECPublicKeyParameters(pubKey.asEcPoint(), CURVE); CipherParameters privParam = new ECPrivateKeyParameters(ephemeralKeyPair.secretKey().bytes().toUnsignedBigInteger(), CURVE); BasicAgreement agree = new ECDHBasicAgreement(); agree.init(privParam); BigInteger z = agree.calculateAgreement(pubParam); byte[] zbytes = BigIntegers.asUnsignedByteArray(agree.getFieldSize(), z); IESWithCipherParameters iesWithCipherParameters = new IESWithCipherParameters(new byte[0], new byte[0], 128, 128); // Initialise the KDF. EthereumIESEncryptionEngine.ECIESHandshakeKDFFunction kdf = new EthereumIESEncryptionEngine.ECIESHandshakeKDFFunction(1, new SHA256Digest()); kdf.init(new KDFParameters(zbytes, iesWithCipherParameters.getDerivationV())); EthereumIESEncryptionEngine engine = new EthereumIESEncryptionEngine( agree, kdf, new HMac(new SHA256Digest()), commonMac.toArrayUnsafe(), new BufferedBlockCipher(new SICBlockCipher(new AESEngine()))); ParametersWithIV cipherParameters = new ParametersWithIV(iesWithCipherParameters, iv.toArrayUnsafe()); engine.init(true, privParam, pubParam, cipherParameters); return engine; } /** * Identify the size of a handshake message based on elements of the common MAC. * * @param msgBytes the bytes of the message * @return the size of the message, including MAC, key and IV */ public static int messageSize(Bytes msgBytes) { Bytes commonMac = msgBytes.slice(0, 2); int size = (commonMac.get(1) & 0xFF) + ((commonMac.get(0) & 0xFF) << 8); return size + 2; } static Bytes decryptMessage(Bytes msgBytes, SecretKey ourKey) { Bytes commonMac = msgBytes.slice(0, 2); int size = (commonMac.get(1) & 0xFF) + ((commonMac.get(0) & 0xFF) << 8); PublicKey ephemeralPublicKey = PublicKey.fromBytes(msgBytes.slice(3, 64)); Bytes iv = msgBytes.slice(67, 16); Bytes encrypted = msgBytes.slice(83, size - 81); EthereumIESEncryptionEngine decryptor = forDecryption(ourKey, ephemeralPublicKey, iv, commonMac); byte[] result; try { result = decryptor.processBlock(encrypted.toArrayUnsafe(), 0, encrypted.size()); } catch (InvalidCipherTextException e) { throw new InvalidMACException(e); } return Bytes.wrap(result); } private static Bytes addPadding(final Bytes message) { final int padding = 100 + random.nextInt(200); final byte[] paddingBytes = new byte[padding]; random.nextBytes(paddingBytes); return concatenate(message, Bytes.wrap(paddingBytes)); } private static EthereumIESEncryptionEngine forDecryption( SecretKey privateKey, PublicKey ephemeralPublicKey, Bytes iv, Bytes commonMac) { CipherParameters pubParam = new ECPublicKeyParameters(ephemeralPublicKey.asEcPoint(), CURVE); CipherParameters privParam = new ECPrivateKeyParameters(privateKey.bytes().toUnsignedBigInteger(), CURVE); BasicAgreement agreement = new ECDHBasicAgreement(); agreement.init(privParam); byte[] agreementValue = BigIntegers.asUnsignedByteArray(agreement.getFieldSize(), agreement.calculateAgreement(pubParam)); IESWithCipherParameters iesWithCipherParameters = new IESWithCipherParameters(new byte[0], new byte[0], 128, 128); EthereumIESEncryptionEngine.ECIESHandshakeKDFFunction kdf = new EthereumIESEncryptionEngine.ECIESHandshakeKDFFunction(1, new SHA256Digest()); kdf.init(new KDFParameters(agreementValue, iesWithCipherParameters.getDerivationV())); EthereumIESEncryptionEngine engine = new EthereumIESEncryptionEngine( agreement, kdf, new HMac(new SHA256Digest()), commonMac.toArrayUnsafe(), new BufferedBlockCipher(new SICBlockCipher(new AESEngine()))); ParametersWithIV cipherParameters = new ParametersWithIV(iesWithCipherParameters, iv.toArrayUnsafe()); engine.init(false, privParam, pubParam, cipherParameters); return engine; } } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/RLPxMessage.java000066400000000000000000000036451341750772100255750ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx; import net.consensys.cava.bytes.Bytes; import java.util.Objects; /** * Message exchanged over a RLPx connection. *

* The message is identified by a negotiated code, offset according to the subprotocol mapping. *

* The message includes the raw content of the message as bytes. */ public final class RLPxMessage { private final int messageId; // messageId with the proper offset private final Bytes content; private final int bytesLength; public RLPxMessage(int messageId, Bytes content) { this(messageId, content, 0); } RLPxMessage(int messageId, Bytes content, int bytesLength) { this.messageId = messageId; this.content = content; this.bytesLength = bytesLength; } int bytesLength() { return bytesLength; } /** * @return the raw content of the message */ public Bytes content() { return content; } /** * @return the message ID */ public int messageId() { return messageId; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; RLPxMessage that = (RLPxMessage) o; return messageId == that.messageId && Objects.equals(content, that.content); } @Override public int hashCode() { return Objects.hash(messageId, content); } } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/RLPxService.java000066400000000000000000000037601341750772100256070ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx; import net.consensys.cava.concurrent.AsyncCompletion; import net.consensys.cava.crypto.SECP256K1; import net.consensys.cava.rlpx.wire.WireSubProtocolMessage; import java.net.InetSocketAddress; /** * Service allowing connections to remote peers over RLPx connections. */ public interface RLPxService { /** * Connects to a remote peer * * @param peerPublicKey the peer public key * @param peerAddress the peer host and port * @return a handle that completes if the peer connects successfully. */ AsyncCompletion connectTo(SECP256K1.PublicKey peerPublicKey, InetSocketAddress peerAddress); /** * Starts the service. * * @return a future handler tracking starting the service. */ AsyncCompletion start(); /** * Stops the service. * * @return a future handler tracking stopping the service. */ AsyncCompletion stop(); /** * Sends a wire message to a peer. * * @param message the message, addressed to a connection. */ void send(WireSubProtocolMessage message); /** * Sends a wire message to all connected peers. * * @param message the message to broadcast. */ void broadcast(WireSubProtocolMessage message); /** * Gets the wire connections repository associated with this service. * * @return the repository of wire connections associated with this service. */ WireConnectionRepository repository(); } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/ResponderHandshakeMessage.java000066400000000000000000000040271341750772100305130ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import net.consensys.cava.crypto.SECP256K1.PublicKey; import net.consensys.cava.rlp.RLP; /** * The decrypted contents of a handshake response message. */ final class ResponderHandshakeMessage implements HandshakeMessage { private final PublicKey ephemeralPublicKey; private final Bytes32 nonce; static ResponderHandshakeMessage create(PublicKey ephemeralPublicKey, Bytes32 nonce) { return new ResponderHandshakeMessage(ephemeralPublicKey, nonce); } static ResponderHandshakeMessage decode(Bytes payload) { return RLP.decodeList( payload, reader -> new ResponderHandshakeMessage( PublicKey.fromBytes(reader.readValue()), Bytes32.wrap(reader.readValue()))); } private ResponderHandshakeMessage(PublicKey ephemeralPublicKey, Bytes32 nonce) { this.ephemeralPublicKey = ephemeralPublicKey; this.nonce = nonce; } /** * @return the ephemeral public key included in the response */ @Override public PublicKey ephemeralPublicKey() { return ephemeralPublicKey; } /** * @return the response nonce */ @Override public Bytes32 nonce() { return nonce; } Bytes encode() { return RLP.encodeList(writer -> { writer.writeValue(ephemeralPublicKey.bytes()); writer.writeValue(nonce); writer.writeInt(InitiatorHandshakeMessage.VERSION); }); } } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/WireConnectionRepository.java000066400000000000000000000032721341750772100304650ustar00rootroot00000000000000/* * Copyright 2019 ConsenSys AG. * * 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.rlpx; import net.consensys.cava.rlpx.wire.WireConnection; /** * A repository managing wire connections. * */ public interface WireConnectionRepository { /** * Adds a new wire connection to the repository. * * @param wireConnection the new wire connection */ void add(WireConnection wireConnection); /** * Gets a wire connection by its identifier, as provided by * net.consensys.cava.rlpx.wire.WireConnection#id * * @param id the identifier of the wire connection * @return the wire connection associated with the identifier, or null if no such wire connection exists. */ WireConnection get(String id); /** * Provides a view of the wire connections as an iterable. There is no guarantee of sorting wire connections. * * @return an Iterable object allowing to traverse all wire connections held by this repository */ Iterable asIterable(); /** * Closes the repository. After it has been closed, the repository should no longer be able to add or retrieve * connections. * */ void close(); } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/package-info.java000066400000000000000000000005601341750772100257600ustar00rootroot00000000000000/** * Classes and utilities for working with the RLPx wire protocol. * *

* These classes are included in the standard Cava distribution, or separately when using the gradle dependency * 'net.consensys.cava:cava-rlpx' (cava-rlpx.jar). */ @ParametersAreNonnullByDefault package net.consensys.cava.rlpx; import javax.annotation.ParametersAreNonnullByDefault; cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/vertx/000077500000000000000000000000001341750772100237405ustar00rootroot00000000000000cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/vertx/VertxRLPxService.java000066400000000000000000000310311341750772100300000ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.vertx; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import net.consensys.cava.concurrent.AsyncCompletion; import net.consensys.cava.concurrent.CompletableAsyncCompletion; import net.consensys.cava.crypto.SECP256K1.KeyPair; import net.consensys.cava.crypto.SECP256K1.PublicKey; import net.consensys.cava.rlpx.HandshakeMessage; import net.consensys.cava.rlpx.MemoryWireConnectionsRepository; import net.consensys.cava.rlpx.RLPxConnection; import net.consensys.cava.rlpx.RLPxConnectionFactory; import net.consensys.cava.rlpx.RLPxService; import net.consensys.cava.rlpx.WireConnectionRepository; import net.consensys.cava.rlpx.wire.DisconnectReason; import net.consensys.cava.rlpx.wire.SubProtocol; import net.consensys.cava.rlpx.wire.SubProtocolHandler; import net.consensys.cava.rlpx.wire.WireConnection; import net.consensys.cava.rlpx.wire.WireSubProtocolMessage; import java.net.InetSocketAddress; import java.util.LinkedHashMap; import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.core.net.NetClient; import io.vertx.core.net.NetClientOptions; import io.vertx.core.net.NetServer; import io.vertx.core.net.NetServerOptions; import io.vertx.core.net.NetSocket; import org.logl.Logger; import org.logl.LoggerProvider; /** * Implementation of RLPx service using Vert.x. */ public final class VertxRLPxService implements RLPxService { private final static int DEVP2P_VERSION = 5; private final LoggerProvider loggerProvider; private final Logger logger; private final AtomicBoolean started = new AtomicBoolean(false); private final Vertx vertx; private final int listenPort; private final String networkInterface; private final int advertisedPort; private final KeyPair keyPair; private final List subProtocols; private final String clientId; private LinkedHashMap handlers; private NetClient client; private NetServer server; private final WireConnectionRepository repository; private static void checkPort(int port) { if (port < 0 || port > 65536) { throw new IllegalArgumentException("Invalid port: " + port); } } /** * Default constructor. * * @param vertx Vert.x object used to build the network components * @param loggerProvider logger provider to log messages * @param listenPort the port to listen to * @param networkInterface the network interface to bind to * @param advertisedPort the port to advertise in HELLO messages to peers * @param identityKeyPair the identity of this client * @param subProtocols subprotocols supported * @param clientId the client identifier, such as "RLPX 1.2/build 389" */ public VertxRLPxService( Vertx vertx, LoggerProvider loggerProvider, int listenPort, String networkInterface, int advertisedPort, KeyPair identityKeyPair, List subProtocols, String clientId) { this( vertx, loggerProvider, listenPort, networkInterface, advertisedPort, identityKeyPair, subProtocols, clientId, new MemoryWireConnectionsRepository()); } /** * Default constructor. * * @param vertx Vert.x object used to build the network components * @param loggerProvider logger provider to log messages * @param listenPort the port to listen to * @param networkInterface the network interface to bind to * @param advertisedPort the port to advertise in HELLO messages to peers * @param identityKeyPair the identity of this client * @param subProtocols subprotocols supported * @param clientId the client identifier, such as "RLPX 1.2/build 389" */ public VertxRLPxService( Vertx vertx, LoggerProvider loggerProvider, int listenPort, String networkInterface, int advertisedPort, KeyPair identityKeyPair, List subProtocols, String clientId, WireConnectionRepository repository) { checkPort(listenPort); checkPort(advertisedPort); if (clientId == null || clientId.trim().isEmpty()) { throw new IllegalArgumentException("Client ID must contain a valid identifier"); } this.vertx = vertx; this.loggerProvider = loggerProvider; this.listenPort = listenPort; this.networkInterface = networkInterface; this.advertisedPort = advertisedPort; this.keyPair = identityKeyPair; this.subProtocols = subProtocols; this.clientId = clientId; this.repository = repository; this.logger = loggerProvider.getLogger("VertxRLPxService"); } @Override public AsyncCompletion start() { if (started.compareAndSet(false, true)) { handlers = new LinkedHashMap(); for (SubProtocol subProtocol : subProtocols) { handlers.put(subProtocol, subProtocol.createHandler(this)); } client = vertx.createNetClient(new NetClientOptions()); server = vertx .createNetServer(new NetServerOptions().setPort(listenPort).setHost(networkInterface).setTcpKeepAlive(true)) .connectHandler(this::receiveMessage); CompletableAsyncCompletion complete = AsyncCompletion.incomplete(); server.listen(res -> { if (res.succeeded()) { complete.complete(); } else { complete.completeExceptionally(res.cause()); } }); return complete; } else { return AsyncCompletion.completed(); } } @Override public void send(WireSubProtocolMessage message) { if (!started.get()) { throw new IllegalStateException("The RLPx service is not active"); } WireConnection conn = wireConnection(message.connectionId()); if (conn != null) { conn.sendMessage(message); } } @Override public void broadcast(WireSubProtocolMessage message) { if (!started.get()) { throw new IllegalStateException("The RLPx service is not active"); } for (WireConnection conn : repository.asIterable()) { conn.sendMessage(message); } } private void receiveMessage(NetSocket netSocket) { netSocket.handler(new Handler() { private RLPxConnection conn; private WireConnection wireConnection; @Override public void handle(Buffer buffer) { if (conn == null) { conn = RLPxConnectionFactory.respondToHandshake( Bytes.wrapBuffer(buffer), keyPair, bytes -> netSocket.write(Buffer.buffer(bytes.toArrayUnsafe()))); if (wireConnection == null) { this.wireConnection = createConnection(conn, netSocket); wireConnection.handleConnectionStart(); } } else { conn.stream(Bytes.wrapBuffer(buffer), wireConnection::messageReceived); } } }); } @Override public AsyncCompletion stop() { if (started.compareAndSet(true, false)) { for (WireConnection conn : repository.asIterable()) { conn.disconnect(DisconnectReason.CLIENT_QUITTING); } repository.close(); client.close(); CompletableAsyncCompletion completableAsyncCompletion = AsyncCompletion.incomplete(); server.close(res -> { if (res.succeeded()) { completableAsyncCompletion.complete(); } else { completableAsyncCompletion.completeExceptionally(res.cause()); } }); return completableAsyncCompletion; } else { return AsyncCompletion.completed(); } } /** * * @return the port used by the server * @throws IllegalStateException if the service is not started */ public int actualPort() { if (!started.get()) { throw new IllegalStateException("The RLPx service is not active"); } return server.actualPort(); } /** * * @return the port advertised by the server * @throws IllegalStateException if the service is not started */ public int advertisedPort() { if (!started.get()) { throw new IllegalStateException("The RLPx service is not active"); } return listenPort == 0 ? actualPort() : advertisedPort; } @Override public WireConnectionRepository repository() { return repository; } @Override public AsyncCompletion connectTo(PublicKey peerPublicKey, InetSocketAddress peerAddress) { if (!started.get()) { throw new IllegalStateException("The RLPx service is not active"); } CompletableAsyncCompletion connected = AsyncCompletion.incomplete(); logger.debug("Connecting to {} with public key {}", peerAddress, peerPublicKey); client.connect( peerAddress.getPort(), peerAddress.getHostString(), netSocketFuture -> netSocketFuture.map(netSocket -> { Bytes32 nonce = RLPxConnectionFactory.generateRandomBytes32(); KeyPair ephemeralKeyPair = KeyPair.random(); Bytes initHandshakeMessage = RLPxConnectionFactory.init(keyPair, peerPublicKey, ephemeralKeyPair, nonce); logger.debug("Initiating handshake to {}", peerAddress); netSocket.write(Buffer.buffer(initHandshakeMessage.toArrayUnsafe())); netSocket.handler(new Handler() { private RLPxConnection conn; private WireConnection wireConnection; @Override public void handle(Buffer buffer) { try { Bytes messageBytes = Bytes.wrapBuffer(buffer); if (conn == null) { int messageSize = RLPxConnectionFactory.messageSize(messageBytes); Bytes responseBytes = messageBytes; if (messageBytes.size() > messageSize) { responseBytes = responseBytes.slice(0, messageSize); } messageBytes = messageBytes.slice(messageSize); HandshakeMessage responseMessage = RLPxConnectionFactory.readResponse(responseBytes, keyPair.secretKey()); conn = RLPxConnectionFactory.createConnection( true, initHandshakeMessage, responseBytes, ephemeralKeyPair.secretKey(), responseMessage.ephemeralPublicKey(), nonce, responseMessage.nonce(), keyPair.publicKey(), peerPublicKey); this.wireConnection = createConnection(conn, netSocket); connected.complete(); if (messageBytes.isEmpty()) { return; } } if (conn != null) { conn.stream(messageBytes, wireConnection::messageReceived); } } catch (Exception e) { logger.error(e.getMessage(), e); connected.completeExceptionally(e); netSocket.close(); } } }); return null; })); return connected; } private WireConnection createConnection(RLPxConnection conn, NetSocket netSocket) { String id = UUID.randomUUID().toString(); WireConnection wireConnection = new WireConnection( id, conn.publicKey().bytes(), conn.peerPublicKey().bytes(), loggerProvider.getLogger("wireConnection-" + id), message -> { synchronized (conn) { Bytes bytes = conn.write(message); vertx.eventBus().send(netSocket.writeHandlerID(), Buffer.buffer(bytes.toArrayUnsafe())); } }, conn::configureAfterHandshake, netSocket::end, handlers, DEVP2P_VERSION, clientId, advertisedPort()); repository.add(wireConnection); return wireConnection; } private WireConnection wireConnection(String id) { if (!started.get()) { throw new IllegalStateException("The RLPx service is not active"); } return repository.get(id); } } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/vertx/package-info.java000066400000000000000000000005661341750772100271360ustar00rootroot00000000000000/** * Classes and utilities for working with the RLPx wire protocol. * *

* These classes are included in the standard Cava distribution, or separately when using the gradle dependency * 'net.consensys.cava:cava-rlpx' (cava-rlpx.jar). */ @ParametersAreNonnullByDefault package net.consensys.cava.rlpx.vertx; import javax.annotation.ParametersAreNonnullByDefault; cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/wire/000077500000000000000000000000001341750772100235365ustar00rootroot00000000000000cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/wire/Capability.java000066400000000000000000000026131341750772100264640ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; import java.util.Objects; final class Capability { private final String name; private final int version; Capability(String name, int version) { this.name = name; this.version = version; } String name() { return name; } int version() { return version; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Capability that = (Capability) o; return Objects.equals(name, that.name) && Objects.equals(version, that.version); } @Override public int hashCode() { return Objects.hash(name, version); } @Override public String toString() { return "Capability{" + "name='" + name + '\'' + ", version='" + version + '\'' + '}'; } } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/wire/DefaultSubProtocolIdentifier.java000066400000000000000000000020511341750772100321620ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; /** * Default implementation of a sub protocol identifier */ final class DefaultSubProtocolIdentifier implements SubProtocolIdentifier { private final String name; private final int version; public DefaultSubProtocolIdentifier(String name, int version) { this.name = name; this.version = version; } @Override public String name() { return name; } @Override public int version() { return version; } } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/wire/DisconnectMessage.java000066400000000000000000000023721341750772100300030ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.rlp.RLP; final class DisconnectMessage implements WireProtocolMessage { private final int reason; DisconnectMessage(DisconnectReason reason) { this(reason.code); } DisconnectMessage(int reason) { this.reason = reason; } static DisconnectMessage read(Bytes data) { return RLP.decodeList(data, source -> new DisconnectMessage(source.readInt())); } @Override public Bytes toBytes() { return RLP.encodeList(writer -> writer.writeInt(reason)); } @Override public int messageType() { return 1; } int reason() { return reason; } } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/wire/DisconnectReason.java000066400000000000000000000021201341750772100276350ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; /** * Enumeration of all reasons disconnect may happen. * */ public enum DisconnectReason { REQUESTED(0), TCP_ERROR(1), PROTOCOL_BREACH(2), USELESS_PEER(3), TOO_MANY_PEERS(4), ALREADY_CONNECTED(5), INCOMPATIBLE_DEVP2P_VERSION(6), NULL_NODE_IDENTITY_RECEIVED(7), CLIENT_QUITTING(8), UNEXPECTED_IDENTITY(9), CONNECTED_TO_SELF(10), TIMEOUT(11), SUBPROTOCOL_REASON(16); DisconnectReason(int code) { this.code = code; } public final int code; } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/wire/HelloMessage.java000066400000000000000000000101321341750772100267460ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.rlp.RLP; import java.util.ArrayList; import java.util.List; import java.util.Objects; public final class HelloMessage implements WireProtocolMessage { private final Bytes nodeId; private final int listenPort; private final String clientId; private final int p2pVersion; private final List capabilities; private HelloMessage(Bytes nodeId, int listenPort, String clientId, int p2pVersion, List capabilities) { this.nodeId = nodeId; this.listenPort = listenPort; this.clientId = clientId; this.p2pVersion = p2pVersion; this.capabilities = capabilities; } static HelloMessage create( Bytes nodeId, int listenPort, int p2pVersion, String clientId, List capabilities) { return new HelloMessage(nodeId, listenPort, clientId, p2pVersion, capabilities); } static HelloMessage read(Bytes data) { return RLP.decodeList(data, reader -> { int p2pVersion = reader.readInt(); String clientId = reader.readString(); List capabilities = reader.readList(capabilitiesReader -> { List caps = new ArrayList<>(); while (!capabilitiesReader.isComplete()) { caps.add( capabilitiesReader.readList( capabilityReader -> new Capability(capabilityReader.readString(), capabilityReader.readInt()))); } return caps; }); int listenPort = reader.readInt(); Bytes nodeId = reader.readValue(); return new HelloMessage(nodeId, listenPort, clientId, p2pVersion, capabilities); }); } @Override public Bytes toBytes() { return RLP.encodeList(writer -> { writer.writeInt(p2pVersion); writer.writeString(clientId); writer.writeList(capabilitiesWriter -> { for (Capability cap : capabilities) { capabilitiesWriter.writeList(capabilityWriter -> { capabilityWriter.writeString(cap.name()); capabilityWriter.writeInt(cap.version()); }); } }); writer.writeInt(listenPort); writer.writeValue(nodeId); }); } @Override public int messageType() { return 0; } Bytes nodeId() { return nodeId; } List capabilities() { return capabilities; } public int p2pVersion() { return p2pVersion; } String clientId() { return clientId; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; HelloMessage that = (HelloMessage) o; if (capabilities.size() != that.capabilities.size()) { return false; } for (int i = 0; i < capabilities.size(); i++) { if (!Objects.equals(capabilities.get(i), that.capabilities.get(i))) { return false; } } return listenPort == that.listenPort && p2pVersion == that.p2pVersion && Objects.equals(nodeId, that.nodeId) && Objects.equals(clientId, that.clientId); } @Override public int hashCode() { return Objects.hash(nodeId, listenPort, clientId, p2pVersion, capabilities); } @Override public String toString() { return "HelloMessage{" + "nodeId=" + nodeId + ", listenPort=" + listenPort + ", clientId='" + clientId + '\'' + ", p2pVersion=" + p2pVersion + ", capabilities=" + capabilities + '}'; } } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/wire/PingMessage.java000066400000000000000000000017261341750772100266110ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; import net.consensys.cava.bytes.Bytes; final class PingMessage implements WireProtocolMessage { static PingMessage read(Bytes data) { return new PingMessage(); } @Override public Bytes toBytes() { throw new UnsupportedOperationException(); } @Override public int messageType() { throw new UnsupportedOperationException(); } } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/wire/PongMessage.java000066400000000000000000000017251341750772100266160ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; import net.consensys.cava.bytes.Bytes; final class PongMessage implements WireProtocolMessage { static PongMessage read(Bytes data) { return new PongMessage(); } @Override public Bytes toBytes() { throw new UnsupportedOperationException(); } @Override public int messageType() { throw new UnsupportedOperationException(); } } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/wire/SubProtocol.java000066400000000000000000000032171341750772100266570ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; import net.consensys.cava.rlpx.RLPxService; /** * Defines a subprotocol to be used for wire connections */ public interface SubProtocol { /** * @return the identifier of the subprotocol */ SubProtocolIdentifier id(); /** * @param subProtocolIdentifier the identifier of the subprotocol * @return true if the subprotocol ID and version are supported, false otherwise */ boolean supports(SubProtocolIdentifier subProtocolIdentifier); /** * Provides the length of the range of message types supported by the subprotocol for a given version * * @param version the version of the subprotocol to associate with the range * @return the length of the range of message types supported by the subprotocol for a given version */ int versionRange(int version); /** * Creates a new handler for the subprotocol. * * @param service the rlpx service that will use the handler * @return a new handler for the subprotocol, bound to the service. */ SubProtocolHandler createHandler(RLPxService service); } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/wire/SubProtocolHandler.java000066400000000000000000000023711341750772100301550ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; import net.consensys.cava.concurrent.AsyncCompletion; /** * Handler managing messages and new connections of peers related for a given subprotocol. */ public interface SubProtocolHandler { /** * Handle an incoming wire protocol message * * @param message the message to be handled */ void handle(WireSubProtocolMessage message); /** * Handle a new peer connection * * @param conn the new peer connection */ void newPeerConnection(WireConnection conn); /** * Stops a subprotocol operations. * * @return a handle to track when the subprotocol operations have stopped */ AsyncCompletion stop(); } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/wire/SubProtocolIdentifier.java000066400000000000000000000017751341750772100306710ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; /** * Identifier of a subprotocol, comprised of a name and version. */ public interface SubProtocolIdentifier { static SubProtocolIdentifier of(String name, int version) { return new DefaultSubProtocolIdentifier(name, version); } /** * * @return the name of the subprotocol */ String name(); /** * * @return the version of the subprotocol */ int version(); } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/wire/WireConnection.java000066400000000000000000000223461341750772100273360ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.concurrent.AsyncCompletion; import net.consensys.cava.concurrent.CompletableAsyncCompletion; import net.consensys.cava.rlpx.RLPxMessage; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; import java.util.stream.Collectors; import com.google.common.collect.BoundType; import com.google.common.collect.Range; import com.google.common.collect.RangeMap; import com.google.common.collect.TreeRangeMap; import org.logl.Logger; /** * A stateful connection between two peers under the Devp2p wire protocol. */ public final class WireConnection { private static class WireSubprotocolMessageImpl implements WireSubProtocolMessage { private final SubProtocolIdentifier subProtocolIdentifier; private final Bytes data; private final int messageType; private final String connectionId; WireSubprotocolMessageImpl( SubProtocolIdentifier subProtocolIdentifier, Bytes data, int messageType, String connectionId) { this.subProtocolIdentifier = subProtocolIdentifier; this.data = data; this.messageType = messageType; this.connectionId = connectionId; } @Override public Bytes toBytes() { return data; } @Override public int messageType() { return messageType; } @Override public SubProtocolIdentifier subProtocolIdentifier() { return subProtocolIdentifier; } @Override public String connectionId() { return connectionId; } } private final Bytes nodeId; private final Bytes peerNodeId; private final Logger logger; private final String id; private final Consumer writer; private final Consumer afterHandshakeListener; private final Runnable disconnectHandler; private final LinkedHashMap subprotocols; private final int p2pVersion; private final String clientId; private final int advertisedPort; private CompletableAsyncCompletion awaitingPong; private HelloMessage myHelloMessage; private HelloMessage peerHelloMessage; private RangeMap subprotocolRangeMap = TreeRangeMap.create(); /** * Default constructor. * * @param id the id of the connection * @param nodeId the node id of this node * @param peerNodeId the node id of the peer * @param logger a logger * @param writer the message writer * @param afterHandshakeListener a listener called after the handshake is complete with the peer hello message. * @param disconnectHandler the handler to run upon receiving a disconnect message * @param subprotocols the subprotocols supported by this connection * @param p2pVersion the version of the devp2p protocol supported by this client * @param clientId the client ID to announce in HELLO messages * @param advertisedPort the port we listen to, to announce in HELLO messages */ public WireConnection( String id, Bytes nodeId, Bytes peerNodeId, Logger logger, Consumer writer, Consumer afterHandshakeListener, Runnable disconnectHandler, LinkedHashMap subprotocols, int p2pVersion, String clientId, int advertisedPort) { this.id = id; this.nodeId = nodeId; this.peerNodeId = peerNodeId; this.logger = logger; this.writer = writer; this.afterHandshakeListener = afterHandshakeListener; this.disconnectHandler = disconnectHandler; this.subprotocols = subprotocols; this.p2pVersion = p2pVersion; this.clientId = clientId; this.advertisedPort = advertisedPort; logger.debug("New wire connection created"); } public void messageReceived(RLPxMessage message) { if (message.messageId() == 0) { peerHelloMessage = HelloMessage.read(message.content()); logger.debug("Received peer Hello message {}", peerHelloMessage); initSupportedRange(peerHelloMessage.capabilities()); if (peerHelloMessage.nodeId() == null || peerHelloMessage.nodeId().isEmpty()) { disconnect(DisconnectReason.NULL_NODE_IDENTITY_RECEIVED); return; } if (!peerHelloMessage.nodeId().equals(peerNodeId)) { disconnect(DisconnectReason.UNEXPECTED_IDENTITY); return; } if (peerHelloMessage.nodeId().equals(nodeId)) { disconnect(DisconnectReason.CONNECTED_TO_SELF); return; } if (peerHelloMessage.p2pVersion() > p2pVersion) { disconnect(DisconnectReason.INCOMPATIBLE_DEVP2P_VERSION); return; } if (myHelloMessage == null) { sendHello(); } afterHandshakeListener.accept(peerHelloMessage); for (SubProtocol subProtocol : subprotocolRangeMap.asMapOfRanges().values()) { subprotocols.get(subProtocol).newPeerConnection(this); } return; } else if (message.messageId() == 1) { DisconnectMessage.read(message.content()); disconnectHandler.run(); return; } if (peerHelloMessage == null || myHelloMessage == null) { logger.debug("Message sent before hello exchanged {}", message.messageId()); disconnect(DisconnectReason.PROTOCOL_BREACH); } if (message.messageId() == 2) { sendPong(); } else if (message.messageId() == 3) { if (awaitingPong != null) { awaitingPong.complete(); } } else { Map.Entry, SubProtocol> subProtocolEntry = subprotocolRangeMap.getEntry(message.messageId()); if (subProtocolEntry == null) { disconnect(DisconnectReason.PROTOCOL_BREACH); } else { int offset = subProtocolEntry.getKey().lowerEndpoint(); WireSubprotocolMessageImpl wireProtocolMessage = new WireSubprotocolMessageImpl( subProtocolEntry.getValue().id(), message.content(), message.messageId() - offset, id()); subprotocols.get(subProtocolEntry.getValue()).handle(wireProtocolMessage); } } } private void initSupportedRange(List capabilities) { int startRange = 17; for (Capability cap : capabilities) { for (SubProtocol sp : subprotocols.keySet()) { if (sp.supports(SubProtocolIdentifier.of(cap.name(), cap.version()))) { int numberOfMessageTypes = sp.versionRange(cap.version()); subprotocolRangeMap .put(Range.range(startRange, BoundType.CLOSED, startRange + numberOfMessageTypes, BoundType.CLOSED), sp); startRange += numberOfMessageTypes + 1; break; } } } } /** * Sends a message to the peer explaining that we are about to disconnect. * * @param reason the reason for disconnection */ public void disconnect(DisconnectReason reason) { logger.debug("Sending disconnect message with reason {}", reason); writer.accept(new RLPxMessage(1, new DisconnectMessage(reason).toBytes())); disconnectHandler.run(); } /** * Sends a ping message to the remote peer. * * @return a handler marking completion when a pong response is received */ public AsyncCompletion sendPing() { logger.debug("Sending ping message"); writer.accept(new RLPxMessage(2, Bytes.EMPTY)); this.awaitingPong = AsyncCompletion.incomplete(); return awaitingPong; } private void sendPong() { logger.debug("Sending pong message"); writer.accept(new RLPxMessage(3, Bytes.EMPTY)); } void sendHello() { myHelloMessage = HelloMessage.create( nodeId, advertisedPort, p2pVersion, clientId, subprotocols.keySet().stream().map(sp -> new Capability(sp.id().name(), sp.id().version())).collect( Collectors.toList())); logger.debug("Sending hello message {}", myHelloMessage); writer.accept(new RLPxMessage(0, myHelloMessage.toBytes())); } public String id() { return id; } public void sendMessage(WireSubProtocolMessage message) { logger.debug("Sending sub-protocol message {}", message); Integer offset = null; for (Map.Entry, SubProtocol> entry : subprotocolRangeMap.asMapOfRanges().entrySet()) { if (entry.getValue().supports(message.subProtocolIdentifier())) { offset = entry.getKey().lowerEndpoint(); break; } } if (offset == null) { throw new UnsupportedOperationException(); // no subprotocol mapped to this connection. Exit. } writer.accept(new RLPxMessage(message.messageType() + offset, message.toBytes())); } public void handleConnectionStart() { sendHello(); } @Override public String toString() { return peerNodeId.toHexString(); } } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/wire/WireProtocolMessage.java000066400000000000000000000020011341750772100303270ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; import net.consensys.cava.bytes.Bytes; /** * A set of bytes made available to a subprotocol after it has been successfully decrypted. */ public interface WireProtocolMessage { /** * @return the payload of the wire message, ready for consumption. */ Bytes toBytes(); /** * @return the code associated with the message type according to the subprotocol. */ int messageType(); } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/wire/WireSubProtocolMessage.java000066400000000000000000000017531341750772100310160ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; /** * A message specific to a subprotocol of the wire protocol. */ public interface WireSubProtocolMessage extends WireProtocolMessage { /** * @return the subprotocol associated with the message. */ SubProtocolIdentifier subProtocolIdentifier(); /** * * @return the identifier of the connection the message was exchanged on. */ String connectionId(); } cava-0.6.0/rlpx/src/main/java/net/consensys/cava/rlpx/wire/package-info.java000066400000000000000000000005651341750772100267330ustar00rootroot00000000000000/** * Classes and utilities for working with the RLPx wire protocol. * *

* These classes are included in the standard Cava distribution, or separately when using the gradle dependency * 'net.consensys.cava:cava-rlpx' (cava-rlpx.jar). */ @ParametersAreNonnullByDefault package net.consensys.cava.rlpx.wire; import javax.annotation.ParametersAreNonnullByDefault; cava-0.6.0/rlpx/src/test/000077500000000000000000000000001341750772100151715ustar00rootroot00000000000000cava-0.6.0/rlpx/src/test/java/000077500000000000000000000000001341750772100161125ustar00rootroot00000000000000cava-0.6.0/rlpx/src/test/java/net/000077500000000000000000000000001341750772100167005ustar00rootroot00000000000000cava-0.6.0/rlpx/src/test/java/net/consensys/000077500000000000000000000000001341750772100207245ustar00rootroot00000000000000cava-0.6.0/rlpx/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100216365ustar00rootroot00000000000000cava-0.6.0/rlpx/src/test/java/net/consensys/cava/rlpx/000077500000000000000000000000001341750772100226235ustar00rootroot00000000000000cava-0.6.0/rlpx/src/test/java/net/consensys/cava/rlpx/RLPxConnectionFactoryTest.java000066400000000000000000000455731341750772100305410ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import net.consensys.cava.concurrent.AsyncResult; import net.consensys.cava.crypto.SECP256K1; import net.consensys.cava.crypto.SECP256K1.KeyPair; import net.consensys.cava.crypto.SECP256K1.SecretKey; import net.consensys.cava.junit.BouncyCastleExtension; import java.security.SecureRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(BouncyCastleExtension.class) class RLPxConnectionFactoryTest { @Test void roundtripPayload() { KeyPair exampleKeyPair = SECP256K1.KeyPair.fromSecretKey( SecretKey .fromBytes(Bytes32.fromHexString("0xEE647A774DF811AB577BA5F397D56BE6567DA58AF7A65368F01DD7A8313812D8"))); Bytes payload = Bytes.fromHexString( "0xF8A7B84135A22239600070940908090D5F051B2C597981B090E386360B87163A8AF1EDF0434A84AA31A582DF93A0396D4CC2E3574C919B0E8D47DCEA095647446C88B36D01B840B8497006E23B1F35BD6E988EC53EE759BC852162049972F777B92B5E029B840BE8BE93F513DA55B81AEE463254930EE30667825B0B6FE30938FFFA7024A03C5AA02B310D67A36F599EAB6B8D03FECB9D782CC7A0EB12FECBFF454A4094557A2EB704"); InitiatorHandshakeMessage initial = InitiatorHandshakeMessage.decode(payload, exampleKeyPair.secretKey()); Bytes encoded = initial.encode(); assertEquals(payload, encoded); } @Test void roundtripInitiatorHandshakeBytes() { KeyPair keyPair = KeyPair.random(); KeyPair peerKeyPair = KeyPair.random(); byte[] nonce = new byte[32]; new SecureRandom().nextBytes(nonce); Bytes payload = RLPxConnectionFactory.init(keyPair, peerKeyPair.publicKey(), KeyPair.random(), Bytes32.wrap(nonce)); InitiatorHandshakeMessage init = RLPxConnectionFactory.read(payload, peerKeyPair.secretKey()); assertEquals(keyPair.publicKey(), init.publicKey()); assertEquals(Bytes.wrap(nonce), init.nonce()); } @Test void roundtripResponseHandshakeBytes() { KeyPair keyPair = KeyPair.random(); KeyPair peerKeyPair = KeyPair.random(); byte[] nonce = new byte[32]; new SecureRandom().nextBytes(nonce); Bytes payload = RLPxConnectionFactory.init(keyPair, peerKeyPair.publicKey(), KeyPair.random(), Bytes32.wrap(nonce)); AtomicReference ref = new AtomicReference<>(); RLPxConnectionFactory.respondToHandshake(payload, peerKeyPair, ref::set); HandshakeMessage responder = RLPxConnectionFactory.readResponse(ref.get(), keyPair.secretKey()); assertNotNull(responder); } @Test void createHandshake() { KeyPair keyPair = KeyPair.random(); KeyPair peerKeyPair = KeyPair.random(); byte[] nonce = new byte[32]; new SecureRandom().nextBytes(nonce); KeyPair ephemeralKeyPair = KeyPair.random(); Bytes payload = RLPxConnectionFactory.init(keyPair, peerKeyPair.publicKey(), ephemeralKeyPair, Bytes32.wrap(nonce)); AtomicReference ref = new AtomicReference<>(); RLPxConnection conn = RLPxConnectionFactory.respondToHandshake(payload, peerKeyPair, ref::set); HandshakeMessage responder = RLPxConnectionFactory.readResponse(ref.get(), keyPair.secretKey()); assertNotNull(conn); assertNotNull(responder); } @Test void createHandshakeAsync() throws TimeoutException, InterruptedException { KeyPair keyPair = KeyPair.random(); KeyPair peerKeyPair = KeyPair.random(); AtomicReference peerConnectionReference = new AtomicReference<>(); Function> wireBytes = (bytes) -> { AtomicReference responseReference = new AtomicReference<>(); peerConnectionReference.set(RLPxConnectionFactory.respondToHandshake(bytes, peerKeyPair, responseReference::set)); return AsyncResult.completed(responseReference.get()); }; AsyncResult futureConn = RLPxConnectionFactory.createHandshake(keyPair, peerKeyPair.publicKey(), wireBytes); RLPxConnection conn = futureConn.get(1, TimeUnit.SECONDS); assertNotNull(conn); assertTrue(RLPxConnection.isComplementedBy(conn, peerConnectionReference.get())); } @Test void createHandshakeAndExchangeMessages() throws TimeoutException, InterruptedException { KeyPair keyPair = KeyPair.random(); KeyPair peerKeyPair = KeyPair.random(); AtomicReference peerConnectionReference = new AtomicReference<>(); Function> wireBytes = (bytes) -> { AtomicReference responseReference = new AtomicReference<>(); peerConnectionReference.set(RLPxConnectionFactory.respondToHandshake(bytes, peerKeyPair, responseReference::set)); return AsyncResult.completed(responseReference.get()); }; AsyncResult futureConn = RLPxConnectionFactory.createHandshake(keyPair, peerKeyPair.publicKey(), wireBytes); RLPxConnection conn = futureConn.get(1, TimeUnit.SECONDS); RLPxConnection conn2 = peerConnectionReference.get(); { Bytes message = conn.write(new RLPxMessage(1, Bytes.fromHexString("deadbeef"))); RLPxMessage readMessage = conn2.readFrame(message); assertEquals(Bytes.fromHexString("deadbeef"), readMessage.content()); assertEquals(1, readMessage.messageId()); } { Bytes message = conn.write(new RLPxMessage(1, Bytes.fromHexString("deadbeef"))); RLPxMessage readMessage = conn2.readFrame(message); assertEquals(Bytes.fromHexString("deadbeef"), readMessage.content()); assertEquals(1, readMessage.messageId()); } { Bytes message = conn2.write(new RLPxMessage(1, Bytes.fromHexString("deadbeef"))); RLPxMessage readMessage = conn.readFrame(message); assertEquals(Bytes.fromHexString("deadbeef"), readMessage.content()); assertEquals(1, readMessage.messageId()); } } private static final SECP256K1.PublicKey publicKey = SECP256K1.KeyPair.random().publicKey(); private static final SECP256K1.PublicKey peerPublicKey = SECP256K1.KeyPair.random().publicKey(); @Test void fixedMessage() { RLPxConnection conn = new RLPxConnection( Bytes32.fromHexString("0x401EED08125776F3A23201D09847EEBEC539FD18E9CB793A53B21F7A23CEFED4"), Bytes32.fromHexString("0x82808970451A460E89DBA968ADAA99B56BC4C6270C4285DA1CB0D40116BB02B7"), Bytes32.fromHexString("0xEDC55BCCD06BAA6A2D593D3836D7580407FB0C01A96544C63EAA05D863E05744"), Bytes.fromHexString( "0x2B72DAFCF28E915725B511BC0A73C760C785B5704EB303E961D1954D21BCC9B801F90474C2A4769AC420A0C26387A2C963264B2596CE626C679588EA733600EC4091BC7B06157E24E0CC741DDBA1E5C6645D83E1149B2CB95AF3915DC52B9485E0122EE6B4AD0B1A80D53D2CBD50BF67E22C4FCB80059CBC3EA672681350765F360F58FB934F0165B50AE928A4D8CB37F68A9B5FC845960E1D37E0869AA593E6C63ABFEB384E6512E511075F7C5D8EB16067A0C59F4882CC4F7EB415F231CDC6A0D78FC38629E38FA0A5741535378680D7E5426ED397304B83AEAEBC43F812C8172E48497DA5E52CE087267A1FEAD8221BA34398B68C3A54E9F0D18B4CECA4472C177E5BE45D631C9D5DC525E0B8D31BD926AB15465922DFD01EE9AE50D67BF78B0CCCB415D034FD89A8A1F3C4E58F1F0DC2FA87AA4E4A956CDB459102571DBB67637CE05927806D09E218EC66ADB2B6A1702C6CBC40CF33DD5FCA373E9C63570C4F4CEC523881199579447B4B557674D9428BEB035FD9807D36AF304CB05C680F8BD9752E9F347C5B9EB02DCB9E09177B5ECE2CA65E7693932B932A98798DE4B428A7D5420173BC2F5BB5AEC985D565A4BD1B7F987906D7F2D4BF51726D279850C46CF65FAF1D1EF81565630618705FF673FB8BC714991796382A07294E1100D51E5321123DB87B248CED97EEB65C3274685CF9C791114756C9B8F0B1824C4A3CFA4EB172A238025EC20973992945ACC886D593DD91555C"), Bytes.fromHexString( "0xE23AC9D32A1BAD577821EB0858615BFED01C5030A5A39342DC992802679C3C1F0189049E5A5FD009FF4BE044146296CFA5AF029BF1CE5F0913D4DB477D89360E484363C8DFC8E96C6398B20440639323591C8F6337DF2A1DFE7B56DB9B2447401771FEF89700CCB6DDDCF7BA0AC80AE57374449E34F82F65FD0280306E9F62C808690390946CDFF9E9C8A5243ED7BA88E29AA7AC128DD9AE4E79497237B75B4F6D5511FDDD2E1916057ED7CC95B512299C20E90EC2134E2DDA87B7F73F3BFBFD4D68417976AA0F7C74E39BFECF677982E9EE3ED15BDEEC31D7867FA80A18A331AD0BC1C1687F9DB5AD3AB9C81B948E1EE11C1F5CFFE86B5A931CEFD2266D112458B80AA1AD6E9C7814B76CEA4B4C57B360132BFF81ACA35B9620D065F24E0000B4EB27D7B3B126DE353C6C265B391084E3CF27086FCBDF11DA364A480DD61F0899E41539F0D8DD958C95D3D09F0EB4C11136686B5EBB9D89ADFB81FAC83C5895874860F0F75CBDA479EF461CA4B75C2EE0F30FF7E0BD259E31DFE4579E334688303AB29EEAC11DBB963A34F2C09D18242F00BCF5CB5340FB7E03DE02ADEE1F3042246B0E21EF4BAF"), publicKey, peerPublicKey); RLPxMessage message = conn.readFrame( Bytes.fromHexString( "0x5B5B56F71CE97B8DEA5F469808441FD10675F244B062222245F5831747924934841D86A431AD89EB3C825C867F0A9820E8E4134C8438A529BE15445073C2C2C33729E6ED374BF2EEC1CA5F3D60972892")); } @Test void invalidHeaderMAC() { RLPxConnection conn = new RLPxConnection( Bytes32.fromHexString("0x401EED08125776F3A23201D09847EEBEC539FD18E9CB793A53B21F7A23CEFED4"), Bytes32.fromHexString("0x82808970451A460E89DBA968ADAA99B56BC4C6270C4285DA1CB0D40116BB02B7"), Bytes32.fromHexString("0xEDC55BCCD06BAA6A2D593D3836D7580407FB0C01A96544C63EAA05D863E05744"), Bytes.fromHexString( "0x2B72DAFCF28E915725B511BC0A73C760C785B5704EB303E961D1954D21BCC9B801F90474C2A4769AC420A0C26387A2C963264B2596CE626C679588EA733600EC4091BC7B06157E24E0CC741DDBA1E5C6645D83E1149B2CB95AF3915DC52B9485E0122EE6B4AD0B1A80D53D2CBD50BF67E22C4FCB80059CBC3EA672681350765F360F58FB934F0165B50AE928A4D8CB37F68A9B5FC845960E1D37E0869AA593E6C63ABFEB384E6512E511075F7C5D8EB16067A0C59F4882CC4F7EB415F231CDC6A0D78FC38629E38FA0A5741535378680D7E5426ED397304B83AEAEBC43F812C8172E48497DA5E52CE087267A1FEAD8221BA34398B68C3A54E9F0D18B4CECA4472C177E5BE45D631C9D5DC525E0B8D31BD926AB15465922DFD01EE9AE50D67BF78B0CCCB415D034FD89A8A1F3C4E58F1F0DC2FA87AA4E4A956CDB459102571DBB67637CE05927806D09E218EC66ADB2B6A1702C6CBC40CF33DD5FCA373E9C63570C4F4CEC523881199579447B4B557674D9428BEB035FD9807D36AF304CB05C680F8BD9752E9F347C5B9EB02DCB9E09177B5ECE2CA65E7693932B932A98798DE4B428A7D5420173BC2F5BB5AEC985D565A4BD1B7F987906D7F2D4BF51726D279850C46CF65FAF1D1EF81565630618705FF673FB8BC714991796382A07294E1100D51E5321123DB87B248CED97EEB65C3274685CF9C791114756C9B8F0B1824C4A3CFA4EB172A238025EC20973992945ACC886D593DD91555C"), Bytes.fromHexString( "0xE23AC9D32A1BAD577821EB0858615BFED01C5030A5A39342DC992802679C3C1F0189049E5A5FD009FF4BE044146296CFA5AF029BF1CE5F0913D4DB477D89360E484363C8DFC8E96C6398B20440639323591C8F6337DF2A1DFE7B56DB9B2447401771FEF89700CCB6DDDCF7BA0AC80AE57374449E34F82F65FD0280306E9F62C808690390946CDFF9E9C8A5243ED7BA88E29AA7AC128DD9AE4E79497237B75B4F6D5511FDDD2E1916057ED7CC95B512299C20E90EC2134E2DDA87B7F73F3BFBFD4D68417976AA0F7C74E39BFECF677982E9EE3ED15BDEEC31D7867FA80A18A331AD0BC1C1687F9DB5AD3AB9C81B948E1EE11C1F5CFFE86B5A931CEFD2266D112458B80AA1AD6E9C7814B76CEA4B4C57B360132BFF81ACA35B9620D065F24E0000B4EB27D7B3B126DE353C6C265B391084E3CF27086FCBDF11DA364A480DD61F0899E41539F0D8DD958C95D3D09F0EB4C11136686B5EBB9D89ADFB81FAC83C5895874860F0F75CBDA479EF461CA4B75C2EE0F30FF7E0BD259E31DFE4579E334688303AB29EEAC11DBB963A34F2C09D18242F00BCF5CB5340FB7E03DE02ADEE1F3042246B0E21EF4BAF"), publicKey, peerPublicKey); assertThrows( InvalidMACException.class, () -> conn.readFrame( Bytes.fromHexString( "0x5B5B56E71CE97B8DEA5F469808441FD10675F244B062222245F5831747924934841D86A431AD89EB3C825C867F0A9820E8E4134C8438A529BE15445073C2C2C33729E6ED374BF2EEC1CA5F3D60972892"))); } @Test void invalidFrameMAC() { RLPxConnection conn = new RLPxConnection( Bytes32.fromHexString("0x401EED08125776F3A23201D09847EEBEC539FD18E9CB793A53B21F7A23CEFED4"), Bytes32.fromHexString("0x82808970451A460E89DBA968ADAA99B56BC4C6270C4285DA1CB0D40116BB02B7"), Bytes32.fromHexString("0xEDC55BCCD06BAA6A2D593D3836D7580407FB0C01A96544C63EAA05D863E05744"), Bytes.fromHexString( "0x2B72DAFCF28E915725B511BC0A73C760C785B5704EB303E961D1954D21BCC9B801F90474C2A4769AC420A0C26387A2C963264B2596CE626C679588EA733600EC4091BC7B06157E24E0CC741DDBA1E5C6645D83E1149B2CB95AF3915DC52B9485E0122EE6B4AD0B1A80D53D2CBD50BF67E22C4FCB80059CBC3EA672681350765F360F58FB934F0165B50AE928A4D8CB37F68A9B5FC845960E1D37E0869AA593E6C63ABFEB384E6512E511075F7C5D8EB16067A0C59F4882CC4F7EB415F231CDC6A0D78FC38629E38FA0A5741535378680D7E5426ED397304B83AEAEBC43F812C8172E48497DA5E52CE087267A1FEAD8221BA34398B68C3A54E9F0D18B4CECA4472C177E5BE45D631C9D5DC525E0B8D31BD926AB15465922DFD01EE9AE50D67BF78B0CCCB415D034FD89A8A1F3C4E58F1F0DC2FA87AA4E4A956CDB459102571DBB67637CE05927806D09E218EC66ADB2B6A1702C6CBC40CF33DD5FCA373E9C63570C4F4CEC523881199579447B4B557674D9428BEB035FD9807D36AF304CB05C680F8BD9752E9F347C5B9EB02DCB9E09177B5ECE2CA65E7693932B932A98798DE4B428A7D5420173BC2F5BB5AEC985D565A4BD1B7F987906D7F2D4BF51726D279850C46CF65FAF1D1EF81565630618705FF673FB8BC714991796382A07294E1100D51E5321123DB87B248CED97EEB65C3274685CF9C791114756C9B8F0B1824C4A3CFA4EB172A238025EC20973992945ACC886D593DD91555C"), Bytes.fromHexString( "0xE23AC9D32A1BAD577821EB0858615BFED01C5030A5A39342DC992802679C3C1F0189049E5A5FD009FF4BE044146296CFA5AF029BF1CE5F0913D4DB477D89360E484363C8DFC8E96C6398B20440639323591C8F6337DF2A1DFE7B56DB9B2447401771FEF89700CCB6DDDCF7BA0AC80AE57374449E34F82F65FD0280306E9F62C808690390946CDFF9E9C8A5243ED7BA88E29AA7AC128DD9AE4E79497237B75B4F6D5511FDDD2E1916057ED7CC95B512299C20E90EC2134E2DDA87B7F73F3BFBFD4D68417976AA0F7C74E39BFECF677982E9EE3ED15BDEEC31D7867FA80A18A331AD0BC1C1687F9DB5AD3AB9C81B948E1EE11C1F5CFFE86B5A931CEFD2266D112458B80AA1AD6E9C7814B76CEA4B4C57B360132BFF81ACA35B9620D065F24E0000B4EB27D7B3B126DE353C6C265B391084E3CF27086FCBDF11DA364A480DD61F0899E41539F0D8DD958C95D3D09F0EB4C11136686B5EBB9D89ADFB81FAC83C5895874860F0F75CBDA479EF461CA4B75C2EE0F30FF7E0BD259E31DFE4579E334688303AB29EEAC11DBB963A34F2C09D18242F00BCF5CB5340FB7E03DE02ADEE1F3042246B0E21EF4BAF"), publicKey, peerPublicKey); assertThrows( InvalidMACException.class, () -> conn.readFrame( Bytes.fromHexString( "0x5B5B56F71CE97B8DEA5F469808441FD10675F244B062222245F5831747924934841D86A431AD89EB3C825C867F0A9820E8E4134C8438A529BE15445073C2C2C33729E6ED374BF2EEC1CA5F3D60972893"))); } @Test void partialMessages() { RLPxConnection conn = new RLPxConnection( Bytes32.fromHexString("0x401EED08125776F3A23201D09847EEBEC539FD18E9CB793A53B21F7A23CEFED4"), Bytes32.fromHexString("0x82808970451A460E89DBA968ADAA99B56BC4C6270C4285DA1CB0D40116BB02B7"), Bytes32.fromHexString("0xEDC55BCCD06BAA6A2D593D3836D7580407FB0C01A96544C63EAA05D863E05744"), Bytes.fromHexString( "0x2B72DAFCF28E915725B511BC0A73C760C785B5704EB303E961D1954D21BCC9B801F90474C2A4769AC420A0C26387A2C963264B2596CE626C679588EA733600EC4091BC7B06157E24E0CC741DDBA1E5C6645D83E1149B2CB95AF3915DC52B9485E0122EE6B4AD0B1A80D53D2CBD50BF67E22C4FCB80059CBC3EA672681350765F360F58FB934F0165B50AE928A4D8CB37F68A9B5FC845960E1D37E0869AA593E6C63ABFEB384E6512E511075F7C5D8EB16067A0C59F4882CC4F7EB415F231CDC6A0D78FC38629E38FA0A5741535378680D7E5426ED397304B83AEAEBC43F812C8172E48497DA5E52CE087267A1FEAD8221BA34398B68C3A54E9F0D18B4CECA4472C177E5BE45D631C9D5DC525E0B8D31BD926AB15465922DFD01EE9AE50D67BF78B0CCCB415D034FD89A8A1F3C4E58F1F0DC2FA87AA4E4A956CDB459102571DBB67637CE05927806D09E218EC66ADB2B6A1702C6CBC40CF33DD5FCA373E9C63570C4F4CEC523881199579447B4B557674D9428BEB035FD9807D36AF304CB05C680F8BD9752E9F347C5B9EB02DCB9E09177B5ECE2CA65E7693932B932A98798DE4B428A7D5420173BC2F5BB5AEC985D565A4BD1B7F987906D7F2D4BF51726D279850C46CF65FAF1D1EF81565630618705FF673FB8BC714991796382A07294E1100D51E5321123DB87B248CED97EEB65C3274685CF9C791114756C9B8F0B1824C4A3CFA4EB172A238025EC20973992945ACC886D593DD91555C"), Bytes.fromHexString( "0xE23AC9D32A1BAD577821EB0858615BFED01C5030A5A39342DC992802679C3C1F0189049E5A5FD009FF4BE044146296CFA5AF029BF1CE5F0913D4DB477D89360E484363C8DFC8E96C6398B20440639323591C8F6337DF2A1DFE7B56DB9B2447401771FEF89700CCB6DDDCF7BA0AC80AE57374449E34F82F65FD0280306E9F62C808690390946CDFF9E9C8A5243ED7BA88E29AA7AC128DD9AE4E79497237B75B4F6D5511FDDD2E1916057ED7CC95B512299C20E90EC2134E2DDA87B7F73F3BFBFD4D68417976AA0F7C74E39BFECF677982E9EE3ED15BDEEC31D7867FA80A18A331AD0BC1C1687F9DB5AD3AB9C81B948E1EE11C1F5CFFE86B5A931CEFD2266D112458B80AA1AD6E9C7814B76CEA4B4C57B360132BFF81ACA35B9620D065F24E0000B4EB27D7B3B126DE353C6C265B391084E3CF27086FCBDF11DA364A480DD61F0899E41539F0D8DD958C95D3D09F0EB4C11136686B5EBB9D89ADFB81FAC83C5895874860F0F75CBDA479EF461CA4B75C2EE0F30FF7E0BD259E31DFE4579E334688303AB29EEAC11DBB963A34F2C09D18242F00BCF5CB5340FB7E03DE02ADEE1F3042246B0E21EF4BAF"), publicKey, peerPublicKey); Bytes fullMessage = Bytes.fromHexString( "0x5B5B56F71CE97B8DEA5F469808441FD10675F244B062222245F5831747924934841D86A431AD89EB3C825C867F0A9820E8E4134C8438A529BE15445073C2C2C33729E6ED374BF2EEC1CA5F3D60972892"); AtomicReference messageRef = new AtomicReference<>(); Bytes message1 = fullMessage.slice(0, 16); Bytes message2 = fullMessage.slice(16, 32); Bytes message3 = fullMessage.slice(16 + 32); assertEquals(fullMessage, Bytes.concatenate(message1, message2, message3)); conn.stream(message1, messageRef::set); assertNull(messageRef.get()); conn.stream(message2, messageRef::set); assertNull(messageRef.get()); conn.stream(message3, messageRef::set); assertNotNull(messageRef.get()); } @Test void roundtripBytesEncryption() { KeyPair peerKeyPair = KeyPair.random(); Bytes encrypted = RLPxConnectionFactory.encryptMessage(Bytes.fromHexString("deadbeef"), peerKeyPair.publicKey()); Bytes decrypted = RLPxConnectionFactory.decryptMessage(encrypted, peerKeyPair.secretKey()); assertEquals(Bytes.fromHexString("deadbeef"), decrypted.slice(0, 4)); } @Test void roundtripBytesEncryptionWithGarbageAppended() { KeyPair peerKeyPair = KeyPair.random(); Bytes encrypted = RLPxConnectionFactory.encryptMessage(Bytes.fromHexString("deadbeef"), peerKeyPair.publicKey()); Bytes decrypted = RLPxConnectionFactory .decryptMessage(Bytes.concatenate(encrypted, Bytes.of(12, 2, 2, 2, 3)), peerKeyPair.secretKey()); assertEquals(Bytes.fromHexString("deadbeef"), decrypted.slice(0, 4)); } } cava-0.6.0/rlpx/src/test/java/net/consensys/cava/rlpx/vertx/000077500000000000000000000000001341750772100237735ustar00rootroot00000000000000cava-0.6.0/rlpx/src/test/java/net/consensys/cava/rlpx/vertx/VertxAcceptanceTest.java000066400000000000000000000253141341750772100305620ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.vertx; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import net.consensys.cava.concurrent.AsyncCompletion; import net.consensys.cava.concurrent.CompletableAsyncCompletion; import net.consensys.cava.crypto.SECP256K1; import net.consensys.cava.junit.BouncyCastleExtension; import net.consensys.cava.junit.VertxExtension; import net.consensys.cava.junit.VertxInstance; import net.consensys.cava.rlpx.MemoryWireConnectionsRepository; import net.consensys.cava.rlpx.RLPxService; import net.consensys.cava.rlpx.wire.SubProtocol; import net.consensys.cava.rlpx.wire.SubProtocolHandler; import net.consensys.cava.rlpx.wire.SubProtocolIdentifier; import net.consensys.cava.rlpx.wire.WireConnection; import net.consensys.cava.rlpx.wire.WireSubProtocolMessage; import java.io.BufferedWriter; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import io.vertx.core.Vertx; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.logl.Level; import org.logl.Logger; import org.logl.LoggerProvider; import org.logl.logl.SimpleLogger; @ExtendWith({VertxExtension.class, BouncyCastleExtension.class}) class VertxAcceptanceTest { private static class MyMessage implements WireSubProtocolMessage { private final SubProtocolIdentifier identifier; private final String connectionId; public MyMessage(SubProtocolIdentifier identifier, String connectionId) { this.identifier = identifier; this.connectionId = connectionId; } @Override public SubProtocolIdentifier subProtocolIdentifier() { return identifier; } @Override public String connectionId() { return connectionId; } @Override public Bytes toBytes() { return Bytes.fromHexString("deadbeef"); } @Override public int messageType() { return 0; } } private static class MyCustomSubProtocolHandler implements SubProtocolHandler { public final List messages = new ArrayList<>(); private final RLPxService rlpxService; private final SubProtocolIdentifier identifier; public MyCustomSubProtocolHandler(RLPxService rlpxService, SubProtocolIdentifier identifier) { this.rlpxService = rlpxService; this.identifier = identifier; } @Override public void handle(WireSubProtocolMessage message) { messages.add(message); } @Override public void newPeerConnection(WireConnection conn) { rlpxService.send(new MyMessage(identifier, conn.id())); } @Override public AsyncCompletion stop() { return AsyncCompletion.completed(); } } private static class MyCustomSubProtocol implements SubProtocol { private final int i; public MyCustomSubProtocol(int i) { this.i = i; } public MyCustomSubProtocolHandler handler; @Override public SubProtocolIdentifier id() { return SubProtocolIdentifier.of("cus", 1); } @Override public boolean supports(SubProtocolIdentifier subProtocolIdentifier) { return "cus".equals(subProtocolIdentifier.name()) && 1 == subProtocolIdentifier.version(); } @Override public int versionRange(int version) { return 1; } @Override public SubProtocolHandler createHandler(RLPxService service) { handler = new MyCustomSubProtocolHandler(service, id()); return handler; } } @Test void testTwoServicesSendingMessagesOfCustomSubProtocolToEachOther(@VertxInstance Vertx vertx) throws Exception { SECP256K1.KeyPair kp = SECP256K1.KeyPair.random(); SECP256K1.KeyPair secondKp = SECP256K1.KeyPair.random(); MyCustomSubProtocol sp = new MyCustomSubProtocol(1); MyCustomSubProtocol secondSp = new MyCustomSubProtocol(2); LoggerProvider logProvider = SimpleLogger.withLogLevel(Level.DEBUG).toPrintWriter( new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)))); MemoryWireConnectionsRepository repository = new MemoryWireConnectionsRepository(); VertxRLPxService service = new VertxRLPxService( vertx, logProvider, 0, "localhost", 10000, kp, Collections.singletonList(sp), "Client 1", repository); MemoryWireConnectionsRepository secondRepository = new MemoryWireConnectionsRepository(); VertxRLPxService secondService = new VertxRLPxService( vertx, logProvider, 0, "localhost", 10000, secondKp, Collections.singletonList(secondSp), "Client 2", secondRepository); service.start().join(); secondService.start().join(); try { service.connectTo(secondKp.publicKey(), new InetSocketAddress("localhost", secondService.actualPort())); Thread.sleep(3000); assertEquals(1, repository.asMap().size()); assertEquals(1, secondRepository.asMap().size()); assertEquals(1, sp.handler.messages.size()); assertEquals(1, secondSp.handler.messages.size()); AsyncCompletion completion = repository.asMap().values().iterator().next().sendPing(); completion.join(); assertTrue(completion.isDone()); } finally { AsyncCompletion.allOf(service.stop(), secondService.stop()); } } @Test void testTwoServicesSendingMessagesOfCustomSubProtocolToEachOtherSimultaneously(@VertxInstance Vertx vertx) throws Exception { SECP256K1.KeyPair kp = SECP256K1.KeyPair.random(); SECP256K1.KeyPair secondKp = SECP256K1.KeyPair.random(); MyCustomSubProtocol sp = new MyCustomSubProtocol(1); MyCustomSubProtocol secondSp = new MyCustomSubProtocol(2); MemoryWireConnectionsRepository repository = new MemoryWireConnectionsRepository(); MemoryWireConnectionsRepository secondRepository = new MemoryWireConnectionsRepository(); LoggerProvider logProvider = SimpleLogger.toPrintWriter(new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)))); VertxRLPxService service = new VertxRLPxService( vertx, logProvider, 0, "localhost", 10000, kp, Collections.singletonList(sp), "Client 1", repository); VertxRLPxService secondService = new VertxRLPxService( vertx, logProvider, 0, "localhost", 10000, secondKp, Collections.singletonList(secondSp), "Client 2", secondRepository); service.start().join(); secondService.start().join(); try { service.connectTo(secondKp.publicKey(), new InetSocketAddress("localhost", secondService.actualPort())); Thread.sleep(3000); assertEquals(1, repository.asMap().size()); assertEquals(1, secondRepository.asMap().size()); assertEquals(1, sp.handler.messages.size()); assertEquals(1, secondSp.handler.messages.size()); List completionList = new ArrayList<>(); ExecutorService threadPool = Executors.newFixedThreadPool(16); for (int i = 0; i < 128; i++) { CompletableAsyncCompletion task = AsyncCompletion.incomplete(); completionList.add(task); threadPool.submit(() -> { try { repository.asMap().values().iterator().next().sendPing(); task.complete(); } catch (Throwable t) { task.completeExceptionally(t); } }); } threadPool.shutdown(); AsyncCompletion allTasks = AsyncCompletion.allOf(completionList); allTasks.join(30, TimeUnit.SECONDS); assertTrue(allTasks.isDone()); } finally { AsyncCompletion.allOf(service.stop(), secondService.stop()); } } @Test @Disabled void connectToPeer(@VertxInstance Vertx vertx) throws Exception { LoggerProvider logProvider = SimpleLogger.withLogLevel(Level.DEBUG).toPrintWriter( new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)))); Logger logger = logProvider.getLogger("test"); SECP256K1.KeyPair kp = SECP256K1.KeyPair.fromSecretKey( SECP256K1.SecretKey .fromBytes(Bytes32.fromHexString("0x2CADB9DDEA3E675CC5349A1AF053CF2E144AF657016A6155DF4AD767F561F18E"))); logger.debug(kp.secretKey().bytes().toHexString()); logger.debug("enode://" + kp.publicKey().toHexString() + "@127.0.0.1:36000"); MemoryWireConnectionsRepository repository = new MemoryWireConnectionsRepository(); VertxRLPxService service = new VertxRLPxService( vertx, logProvider, 36000, "localhost", 36000, kp, Collections.singletonList(new SubProtocol() { @Override public SubProtocolIdentifier id() { return new SubProtocolIdentifier() { @Override public String name() { return "eth"; } @Override public int version() { return 63; } }; } @Override public boolean supports(SubProtocolIdentifier subProtocolIdentifier) { return false; } @Override public int versionRange(int version) { return 0; } @Override public SubProtocolHandler createHandler(RLPxService service) { return null; } }), "Client 1", repository); service.start().join(); AsyncCompletion completion = service.connectTo( SECP256K1.PublicKey.fromHexString( "7a8fbb31bff7c48179f8504b047313ebb7446a0233175ffda6eb4c27aaa5d2aedcef4dd9501b4f17b4f16588f0fd037f9b9416b8caca655bee3b14b4ef67441a"), new InetSocketAddress("localhost", 30303)); completion.join(); Thread.sleep(10000); service.stop().join(); } } cava-0.6.0/rlpx/src/test/java/net/consensys/cava/rlpx/vertx/VertxRLPxServiceTest.java000066400000000000000000000150141341750772100306760ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.vertx; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.concurrent.AsyncCompletion; import net.consensys.cava.crypto.SECP256K1; import net.consensys.cava.junit.BouncyCastleExtension; import net.consensys.cava.junit.VertxExtension; import net.consensys.cava.junit.VertxInstance; import net.consensys.cava.rlpx.MemoryWireConnectionsRepository; import java.io.BufferedWriter; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.InetSocketAddress; import java.util.ArrayList; import io.vertx.core.Vertx; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.logl.Level; import org.logl.LoggerProvider; import org.logl.logl.SimpleLogger; @ExtendWith({VertxExtension.class, BouncyCastleExtension.class}) class VertxRLPxServiceTest { @Test void invalidPort(@VertxInstance Vertx vertx) { assertThrows( IllegalArgumentException.class, () -> new VertxRLPxService( vertx, LoggerProvider.nullProvider(), -1, "localhost", 30, SECP256K1.KeyPair.random(), new ArrayList<>(), "a")); } @Test void invalidAdvertisedPort(@VertxInstance Vertx vertx) { assertThrows( IllegalArgumentException.class, () -> new VertxRLPxService( vertx, LoggerProvider.nullProvider(), 3, "localhost", -1, SECP256K1.KeyPair.random(), new ArrayList<>(), "a")); } @Test void invalidClientId(@VertxInstance Vertx vertx) { assertThrows( IllegalArgumentException.class, () -> new VertxRLPxService( vertx, LoggerProvider.nullProvider(), 34, "localhost", 23, SECP256K1.KeyPair.random(), new ArrayList<>(), null)); } @Test void invalidClientIdSpaces(@VertxInstance Vertx vertx) { assertThrows( IllegalArgumentException.class, () -> new VertxRLPxService( vertx, LoggerProvider.nullProvider(), 34, "localhost", 23, SECP256K1.KeyPair.random(), new ArrayList<>(), " ")); } @Test void startAndStopService(@VertxInstance Vertx vertx) throws InterruptedException { VertxRLPxService service = new VertxRLPxService( vertx, LoggerProvider.nullProvider(), 10000, "localhost", 10000, SECP256K1.KeyPair.random(), new ArrayList<>(), "a"); service.start().join(); try { assertEquals(10000, service.actualPort()); } finally { service.stop(); } } @Test void startServiceWithPortZero(@VertxInstance Vertx vertx) throws InterruptedException { VertxRLPxService service = new VertxRLPxService( vertx, LoggerProvider.nullProvider(), 0, "localhost", 0, SECP256K1.KeyPair.random(), new ArrayList<>(), "a"); service.start().join(); try { assertTrue(service.actualPort() != 0); assertEquals(service.actualPort(), service.advertisedPort()); } finally { service.stop(); } } @Test void stopServiceWithoutStartingItFirst(@VertxInstance Vertx vertx) { VertxRLPxService service = new VertxRLPxService( vertx, LoggerProvider.nullProvider(), 0, "localhost", 10000, SECP256K1.KeyPair.random(), new ArrayList<>(), "abc"); AsyncCompletion completion = service.stop(); assertTrue(completion.isDone()); } @Test void connectToOtherPeer(@VertxInstance Vertx vertx) throws Exception { SECP256K1.KeyPair ourPair = SECP256K1.KeyPair.random(); SECP256K1.KeyPair peerPair = SECP256K1.KeyPair.random(); VertxRLPxService service = new VertxRLPxService( vertx, LoggerProvider.nullProvider(), 0, "localhost", 10000, ourPair, new ArrayList<>(), "abc"); service.start().join(); VertxRLPxService peerService = new VertxRLPxService( vertx, LoggerProvider.nullProvider(), 0, "localhost", 10000, peerPair, new ArrayList<>(), "abc"); peerService.start().join(); try { service.connectTo(peerPair.publicKey(), new InetSocketAddress(peerService.actualPort())); } finally { service.stop(); peerService.stop(); } } @Test void checkWireConnectionCreated(@VertxInstance Vertx vertx) throws Exception { SECP256K1.KeyPair ourPair = SECP256K1.KeyPair.random(); SECP256K1.KeyPair peerPair = SECP256K1.KeyPair.random(); LoggerProvider logProvider = SimpleLogger.withLogLevel(Level.DEBUG).toPrintWriter( new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)))); MemoryWireConnectionsRepository repository = new MemoryWireConnectionsRepository(); VertxRLPxService service = new VertxRLPxService(vertx, logProvider, 0, "localhost", 10000, ourPair, new ArrayList<>(), "abc", repository); service.start().join(); MemoryWireConnectionsRepository peerRepository = new MemoryWireConnectionsRepository(); VertxRLPxService peerService = new VertxRLPxService( vertx, logProvider, 0, "localhost", 10000, peerPair, new ArrayList<>(), "abc", peerRepository); peerService.start().join(); try { service.connectTo(peerPair.publicKey(), new InetSocketAddress("localhost", peerService.actualPort())); Thread.sleep(3000); assertEquals(1, repository.asMap().size()); } finally { service.stop(); peerService.stop(); } } } cava-0.6.0/rlpx/src/test/java/net/consensys/cava/rlpx/wire/000077500000000000000000000000001341750772100235715ustar00rootroot00000000000000cava-0.6.0/rlpx/src/test/java/net/consensys/cava/rlpx/wire/DisconnectMessageTest.java000066400000000000000000000021031341750772100306660ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; import static org.junit.jupiter.api.Assertions.assertEquals; import net.consensys.cava.bytes.Bytes; import org.junit.jupiter.api.Test; class DisconnectMessageTest { @Test void testBytesRoundtrip() { DisconnectMessage msg = new DisconnectMessage(4); Bytes toBytes = msg.toBytes(); DisconnectMessage read = DisconnectMessage.read(toBytes); assertEquals(msg.messageType(), read.messageType()); assertEquals(msg.reason(), read.reason()); } } cava-0.6.0/rlpx/src/test/java/net/consensys/cava/rlpx/wire/HelloMessageTest.java000066400000000000000000000031371341750772100276500ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; import static org.junit.jupiter.api.Assertions.assertEquals; import net.consensys.cava.bytes.Bytes; import java.util.Collections; import org.junit.jupiter.api.Test; class HelloMessageTest { @Test void p2pVersion() { HelloMessage msg = HelloMessage.create(Bytes.fromHexString("deadbeef"), 10000, 3, "blah", Collections.emptyList()); HelloMessage msgRead = HelloMessage.read(msg.toBytes()); assertEquals(3, msgRead.p2pVersion()); } @Test void nodeId() { HelloMessage msg = HelloMessage.create(Bytes.fromHexString("deadbeef"), 10000, 3, "blah", Collections.emptyList()); HelloMessage msgRead = HelloMessage.read(msg.toBytes()); assertEquals(Bytes.fromHexString("deadbeef"), msgRead.nodeId()); } @Test void clientId() { HelloMessage msg = HelloMessage.create(Bytes.fromHexString("deadbeef"), 10000, 3, "foofoo", Collections.emptyList()); HelloMessage msgRead = HelloMessage.read(msg.toBytes()); assertEquals("foofoo", msgRead.clientId()); } } cava-0.6.0/rlpx/src/test/java/net/consensys/cava/rlpx/wire/PingPongTest.java000066400000000000000000000053501341750772100270200ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.concurrent.AsyncCompletion; import net.consensys.cava.crypto.SECP256K1; import net.consensys.cava.junit.BouncyCastleExtension; import net.consensys.cava.rlpx.RLPxMessage; import java.util.LinkedHashMap; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.logl.LoggerProvider; @ExtendWith(BouncyCastleExtension.class) class PingPongTest { private static final Bytes nodeId = SECP256K1.KeyPair.random().publicKey().bytes(); private static final Bytes peerNodeId = SECP256K1.KeyPair.random().publicKey().bytes(); @Test void pingPongRoundtrip() { AtomicReference capturedPing = new AtomicReference<>(); WireConnection conn = new WireConnection( "abc", nodeId, peerNodeId, LoggerProvider.nullProvider().getLogger("rlpx"), capturedPing::set, helloMessage -> { }, () -> { }, new LinkedHashMap<>(), 2, "abc", 10000); AsyncCompletion completion = conn.sendPing(); assertFalse(completion.isDone()); assertNotNull(capturedPing.get()); conn.messageReceived(new RLPxMessage(3, Bytes.EMPTY)); assertTrue(completion.isDone()); } @Test void pongPingRoundtrip() { AtomicReference capturedPong = new AtomicReference<>(); WireConnection conn = new WireConnection( "abc", nodeId, peerNodeId, LoggerProvider.nullProvider().getLogger("rlpx"), capturedPong::set, helloMessage -> { }, () -> { }, new LinkedHashMap<>(), 1, "abc", 10000); conn.messageReceived(new RLPxMessage(2, Bytes.EMPTY)); assertNotNull(capturedPong.get()); assertEquals(3, capturedPong.get().messageId()); } } cava-0.6.0/rlpx/src/test/java/net/consensys/cava/rlpx/wire/RLPxConnectionMessageExchangeTest.java000066400000000000000000000126211341750772100331130ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.concurrent.AsyncResult; import net.consensys.cava.crypto.SECP256K1; import net.consensys.cava.junit.BouncyCastleExtension; import net.consensys.cava.rlpx.RLPxConnection; import net.consensys.cava.rlpx.RLPxConnectionFactory; import net.consensys.cava.rlpx.RLPxMessage; import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(BouncyCastleExtension.class) class RLPxConnectionMessageExchangeTest { @Test void exchangeHello() throws Exception { SECP256K1.KeyPair keyPair = SECP256K1.KeyPair.random(); SECP256K1.KeyPair peerKeyPair = SECP256K1.KeyPair.random(); AtomicReference peerConnectionReference = new AtomicReference<>(); Function> wireBytes = (bytes) -> { AtomicReference responseReference = new AtomicReference<>(); peerConnectionReference.set(RLPxConnectionFactory.respondToHandshake(bytes, peerKeyPair, responseReference::set)); return AsyncResult.completed(responseReference.get()); }; AsyncResult futureConn = RLPxConnectionFactory.createHandshake(keyPair, peerKeyPair.publicKey(), wireBytes); RLPxConnection peerConn = peerConnectionReference.get(); RLPxConnection conn = futureConn.get(1, TimeUnit.SECONDS); assertTrue(RLPxConnection.isComplementedBy(conn, peerConn)); HelloMessage message = HelloMessage.create(Bytes.of(1, 2, 3), 30303, 28, "ClientID 1.0", Arrays.asList(new Capability("eth", 63))); RLPxMessage messageToWrite = new RLPxMessage(0, message.toBytes()); Bytes messageBytes = peerConn.write(messageToWrite); RLPxMessage readMessage = conn.readFrame(messageBytes); assertEquals(messageToWrite, readMessage); } @Test void exchangeHelloAndSomeMoreMessagesWithCompression() throws Exception { SECP256K1.KeyPair keyPair = SECP256K1.KeyPair.random(); SECP256K1.KeyPair peerKeyPair = SECP256K1.KeyPair.random(); AtomicReference peerConnectionReference = new AtomicReference<>(); Function> wireBytes = (bytes) -> { AtomicReference responseReference = new AtomicReference<>(); peerConnectionReference.set(RLPxConnectionFactory.respondToHandshake(bytes, peerKeyPair, responseReference::set)); return AsyncResult.completed(responseReference.get()); }; AsyncResult futureConn = RLPxConnectionFactory.createHandshake(keyPair, peerKeyPair.publicKey(), wireBytes); RLPxConnection peerConn = peerConnectionReference.get(); RLPxConnection conn = futureConn.get(1, TimeUnit.SECONDS); assertTrue(RLPxConnection.isComplementedBy(conn, peerConn)); HelloMessage hello = HelloMessage.create(Bytes.of(1, 2, 3), 30303, 5, "ClientID 1.0", Arrays.asList(new Capability("eth", 63))); conn.configureAfterHandshake(hello); peerConn.configureAfterHandshake(hello); Bytes message = conn.write(new RLPxMessage(23, Bytes.fromHexString("deadbeef"))); RLPxMessage readMessage = peerConn.readFrame(message); assertEquals(Bytes.fromHexString("deadbeef"), readMessage.content()); } @Test void exchangeHelloAndSomeMoreMessagesWithoutCompression() throws Exception { SECP256K1.KeyPair keyPair = SECP256K1.KeyPair.random(); SECP256K1.KeyPair peerKeyPair = SECP256K1.KeyPair.random(); AtomicReference peerConnectionReference = new AtomicReference<>(); Function> wireBytes = (bytes) -> { AtomicReference responseReference = new AtomicReference<>(); peerConnectionReference.set(RLPxConnectionFactory.respondToHandshake(bytes, peerKeyPair, responseReference::set)); return AsyncResult.completed(responseReference.get()); }; AsyncResult futureConn = RLPxConnectionFactory.createHandshake(keyPair, peerKeyPair.publicKey(), wireBytes); RLPxConnection peerConn = peerConnectionReference.get(); RLPxConnection conn = futureConn.get(1, TimeUnit.SECONDS); assertTrue(RLPxConnection.isComplementedBy(conn, peerConn)); HelloMessage hello = HelloMessage.create(Bytes.of(1, 2, 3), 30303, 4, "ClientID 1.0", Arrays.asList(new Capability("eth", 63))); conn.configureAfterHandshake(hello); peerConn.configureAfterHandshake(hello); Bytes message = conn.write(new RLPxMessage(23, Bytes.fromHexString("deadbeef"))); RLPxMessage readMessage = peerConn.readFrame(message); assertEquals(Bytes.fromHexString("deadbeef"), readMessage.content()); } } cava-0.6.0/rlpx/src/test/java/net/consensys/cava/rlpx/wire/WireConnectionTest.java000066400000000000000000000156531341750772100302340ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.rlpx.wire; import static org.junit.jupiter.api.Assertions.assertEquals; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.crypto.SECP256K1; import net.consensys.cava.junit.BouncyCastleExtension; import net.consensys.cava.rlpx.RLPxMessage; import java.util.Collections; import java.util.LinkedHashMap; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.logl.LoggerProvider; @ExtendWith(BouncyCastleExtension.class) class WireConnectionTest { private static final Bytes nodeId = SECP256K1.KeyPair.random().publicKey().bytes(); private static final Bytes peerNodeId = SECP256K1.KeyPair.random().publicKey().bytes(); @Test void disconnectIfNoHelloExchanged() { AtomicReference capturedDisconnect = new AtomicReference<>(); WireConnection conn = new WireConnection( "abc", nodeId, peerNodeId, LoggerProvider.nullProvider().getLogger("rlpx"), capturedDisconnect::set, helloMessage -> { }, () -> { }, new LinkedHashMap<>(), 3, "abc", 10000); conn.messageReceived(new RLPxMessage(45, Bytes.EMPTY)); assertEquals(1, capturedDisconnect.get().messageId()); DisconnectMessage msg = DisconnectMessage.read(capturedDisconnect.get().content()); assertEquals(2, msg.reason()); } @Test void disconnectIfNoHelloReceived() { AtomicReference capturedDisconnect = new AtomicReference<>(); WireConnection conn = new WireConnection( "abc", nodeId, peerNodeId, LoggerProvider.nullProvider().getLogger("rlpx"), capturedDisconnect::set, helloMessage -> { }, () -> { }, new LinkedHashMap<>(), 4, "abc", 10000); conn.sendHello(); conn.messageReceived(new RLPxMessage(45, Bytes.EMPTY)); assertEquals(1, capturedDisconnect.get().messageId()); DisconnectMessage msg = DisconnectMessage.read(capturedDisconnect.get().content()); assertEquals(2, msg.reason()); } @Test void disconnectIfNoMapping() { AtomicReference capturedDisconnect = new AtomicReference<>(); WireConnection conn = new WireConnection( "abc", nodeId, peerNodeId, LoggerProvider.nullProvider().getLogger("rlpx"), capturedDisconnect::set, helloMessage -> { }, () -> { }, new LinkedHashMap<>(), 28, "abc", 10000); conn.sendHello(); conn.messageReceived( new RLPxMessage( 0, HelloMessage.create(Bytes.fromHexString("deadbeef"), 30303, 3, "blah", Collections.emptyList()).toBytes())); conn.messageReceived(new RLPxMessage(45, Bytes.EMPTY)); assertEquals(1, capturedDisconnect.get().messageId()); DisconnectMessage msg = DisconnectMessage.read(capturedDisconnect.get().content()); assertEquals(2, msg.reason()); } @Test void disconnectIfNoNodeID() { AtomicReference capturedDisconnect = new AtomicReference<>(); WireConnection conn = new WireConnection( "abc", nodeId, peerNodeId, LoggerProvider.nullProvider().getLogger("rlpx"), capturedDisconnect::set, helloMessage -> { }, () -> { }, new LinkedHashMap<>(), 32, "abc", 10000); conn.sendHello(); conn.messageReceived( new RLPxMessage(0, HelloMessage.create(Bytes.EMPTY, 30303, 4, "blah", Collections.emptyList()).toBytes())); assertEquals(1, capturedDisconnect.get().messageId()); DisconnectMessage msg = DisconnectMessage.read(capturedDisconnect.get().content()); assertEquals(DisconnectReason.NULL_NODE_IDENTITY_RECEIVED.code, msg.reason()); } @Test void disconnectIfNodeIDMismatches() { AtomicReference capturedDisconnect = new AtomicReference<>(); WireConnection conn = new WireConnection( "abc", nodeId, peerNodeId, LoggerProvider.nullProvider().getLogger("rlpx"), capturedDisconnect::set, helloMessage -> { }, () -> { }, new LinkedHashMap<>(), 32, "abc", 10000); conn.sendHello(); conn.messageReceived( new RLPxMessage( 0, HelloMessage.create(Bytes.of(1, 2, 3, 4), 30303, 3, "blah", Collections.emptyList()).toBytes())); assertEquals(1, capturedDisconnect.get().messageId()); DisconnectMessage msg = DisconnectMessage.read(capturedDisconnect.get().content()); assertEquals(DisconnectReason.UNEXPECTED_IDENTITY.code, msg.reason()); } @Test void disconnectIfConnectedToSelf() { AtomicReference capturedDisconnect = new AtomicReference<>(); WireConnection conn = new WireConnection( "abc", nodeId, nodeId, LoggerProvider.nullProvider().getLogger("rlpx"), capturedDisconnect::set, helloMessage -> { }, () -> { }, new LinkedHashMap<>(), 33, "abc", 10000); conn.sendHello(); conn.messageReceived( new RLPxMessage(0, HelloMessage.create(nodeId, 30303, 1, "blah", Collections.emptyList()).toBytes())); assertEquals(1, capturedDisconnect.get().messageId()); DisconnectMessage msg = DisconnectMessage.read(capturedDisconnect.get().content()); assertEquals(DisconnectReason.CONNECTED_TO_SELF.code, msg.reason()); } @Test void disconnectIfInvalidP2PConnection() { AtomicReference capturedDisconnect = new AtomicReference<>(); WireConnection conn = new WireConnection( "abc", nodeId, peerNodeId, LoggerProvider.nullProvider().getLogger("rlpx"), capturedDisconnect::set, helloMessage -> { }, () -> { }, new LinkedHashMap<>(), 5, "abc", 10000); conn.sendHello(); conn.messageReceived( new RLPxMessage(0, HelloMessage.create(peerNodeId, 30303, 6, "blah", Collections.emptyList()).toBytes())); assertEquals(1, capturedDisconnect.get().messageId()); DisconnectMessage msg = DisconnectMessage.read(capturedDisconnect.get().content()); assertEquals(DisconnectReason.INCOMPATIBLE_DEVP2P_VERSION.code, msg.reason()); } } cava-0.6.0/settings.gradle000066400000000000000000000006001341750772100154520ustar00rootroot00000000000000rootProject.name='cava' include 'bytes' include 'concurrent' include 'concurrent-coroutines' include 'config' include 'crypto' include 'devp2p' include 'eth' include 'eth-reference-tests' include 'io' include 'junit' include 'kademlia' include 'kv' include 'merkle-trie' include 'net' include 'net-coroutines' include 'rlp' include 'rlpx' include 'ssz' include 'toml' include 'units' cava-0.6.0/ssz/000077500000000000000000000000001341750772100132555ustar00rootroot00000000000000cava-0.6.0/ssz/build.gradle000066400000000000000000000005771341750772100155450ustar00rootroot00000000000000description = 'Simple Serialize (SSZ) encoding and decoding.' dependencies { compile project(':bytes') compileOnly project(':units') compile 'org.jetbrains.kotlin:kotlin-stdlib' testCompile project(':units') testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/ssz/src/000077500000000000000000000000001341750772100140445ustar00rootroot00000000000000cava-0.6.0/ssz/src/main/000077500000000000000000000000001341750772100147705ustar00rootroot00000000000000cava-0.6.0/ssz/src/main/java/000077500000000000000000000000001341750772100157115ustar00rootroot00000000000000cava-0.6.0/ssz/src/main/java/net/000077500000000000000000000000001341750772100164775ustar00rootroot00000000000000cava-0.6.0/ssz/src/main/java/net/consensys/000077500000000000000000000000001341750772100205235ustar00rootroot00000000000000cava-0.6.0/ssz/src/main/java/net/consensys/cava/000077500000000000000000000000001341750772100214355ustar00rootroot00000000000000cava-0.6.0/ssz/src/main/java/net/consensys/cava/ssz/000077500000000000000000000000001341750772100222545ustar00rootroot00000000000000cava-0.6.0/ssz/src/main/java/net/consensys/cava/ssz/ByteBufferSSZWriter.java000066400000000000000000000017741341750772100267620ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz; import net.consensys.cava.bytes.Bytes; import java.nio.ByteBuffer; final class ByteBufferSSZWriter implements SSZWriter { private ByteBuffer buffer; ByteBufferSSZWriter(ByteBuffer buffer) { this.buffer = buffer; } @Override public void writeSSZ(Bytes value) { buffer.put(value.toArrayUnsafe()); } @Override public void writeSSZ(byte[] value) { buffer.put(value); } } cava-0.6.0/ssz/src/main/java/net/consensys/cava/ssz/BytesSSZReader.java000066400000000000000000000174001341750772100257320ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz; import static com.google.common.base.Preconditions.checkArgument; import static java.nio.charset.StandardCharsets.UTF_8; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.units.bigints.UInt256; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import java.util.function.Function; import java.util.function.LongFunction; import java.util.function.Supplier; final class BytesSSZReader implements SSZReader { private final Bytes content; private int index = 0; BytesSSZReader(Bytes content) { this.content = content; } @Override public Bytes readBytes(int limit) { int byteLength = 4; ensureBytes(byteLength, () -> "SSZ encoded data is not a byte array"); int size; try { size = content.getInt(index); } catch (IndexOutOfBoundsException e) { throw new EndOfSSZException(); } if (size < 0 || size > limit) { throw new InvalidSSZTypeException("length of bytes would exceed limit"); } index += 4; if (content.size() - index - size < 0) { throw new InvalidSSZTypeException("SSZ encoded data has insufficient bytes for decoded byte array length"); } return consumeBytes(size); } @Override public int readInt(int bitLength) { checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); int byteLength = bitLength / 8; ensureBytes(byteLength, () -> "SSZ encoded data has insufficient length to read a " + bitLength + "-bit integer"); Bytes bytes = content.slice(index, byteLength); int zeroBytes = bytes.numberOfLeadingZeroBytes(); if ((byteLength - zeroBytes) > 4) { throw new InvalidSSZTypeException("decoded integer is too large for an int"); } index += byteLength; return bytes.slice(zeroBytes).toInt(); } @Override public long readLong(int bitLength) { checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); int byteLength = bitLength / 8; ensureBytes(byteLength, () -> "SSZ encoded data has insufficient length to read a " + bitLength + "-bit integer"); Bytes bytes = content.slice(index, byteLength); int zeroBytes = bytes.numberOfLeadingZeroBytes(); if ((byteLength - zeroBytes) > 8) { throw new InvalidSSZTypeException("decoded integer is too large for an int"); } index += byteLength; return bytes.slice(zeroBytes).toLong(); } @Override public BigInteger readBigInteger(int bitLength) { checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); int byteLength = bitLength / 8; ensureBytes(byteLength, () -> "SSZ encoded data has insufficient length to read a " + bitLength + "-bit integer"); return consumeBytes(byteLength).toBigInteger(); } @Override public BigInteger readUnsignedBigInteger(int bitLength) { checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); int byteLength = bitLength / 8; ensureBytes(byteLength, () -> "SSZ encoded data has insufficient length to read a " + bitLength + "-bit integer"); return consumeBytes(byteLength).toUnsignedBigInteger(); } @Override public UInt256 readUInt256() { ensureBytes(32, () -> "SSZ encoded data has insufficient length to read a 256-bit integer"); return UInt256.fromBytes(consumeBytes(32)); } @Override public Bytes readAddress() { ensureBytes(20, () -> "SSZ encoded data has insufficient length to read a 20-byte address"); return consumeBytes(20); } @Override public Bytes readHash(int hashLength) { ensureBytes(hashLength, () -> "SSZ encoded data has insufficient length to read a " + hashLength + "-byte hash"); return consumeBytes(hashLength); } @Override public List readBytesList(int limit) { return readList(remaining -> readByteArray(limit), Bytes::wrap); } @Override public List readStringList(int limit) { return readList(remaining -> readByteArray(limit), byteArray -> new String(byteArray, UTF_8)); } @Override public List readIntList(int bitLength) { checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); return readList(bitLength / 8, () -> readInt(bitLength)); } @Override public List readLongIntList(int bitLength) { checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); return readList(bitLength / 8, () -> readLong(bitLength)); } @Override public List readBigIntegerList(int bitLength) { checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); return readList(bitLength / 8, () -> readBigInteger(bitLength)); } @Override public List readUnsignedBigIntegerList(int bitLength) { checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); return readList(bitLength / 8, () -> readUnsignedBigInteger(bitLength)); } @Override public List readUInt256List() { return readList(256 / 8, this::readUInt256); } @Override public List readAddressList() { return readList(20, this::readAddress); } @Override public List readHashList(int hashLength) { return readList(hashLength, () -> readHash(hashLength)); } @Override public List readBooleanList() { return readList(1, this::readBoolean); } @Override public boolean isComplete() { return index >= content.size(); } private void ensureBytes(int byteLength, Supplier message) { if (index == content.size()) { throw new EndOfSSZException(); } if (content.size() - index - byteLength < 0) { throw new InvalidSSZTypeException(message.get()); } } private Bytes consumeBytes(int size) { Bytes bytes = content.slice(index, size); index += size; return bytes; } private List readList(LongFunction bytesSupplier, Function converter) { ensureBytes(4, () -> "SSZ encoded data is not a list"); int originalIndex = this.index; List elements; try { // use a long to simulate reading unsigned long listSize = consumeBytes(4).toLong(); elements = new ArrayList<>(); while (listSize > 0) { byte[] bytes = bytesSupplier.apply(listSize); elements.add(converter.apply(bytes)); listSize -= bytes.length; if (listSize < 0) { throw new InvalidSSZTypeException("SSZ encoded list length does not align with lengths of its elements"); } } } catch (Exception e) { this.index = originalIndex; throw e; } return elements; } private List readList(int elementSize, Supplier elementSupplier) { ensureBytes(4, () -> "SSZ encoded data is not a list"); int originalIndex = this.index; List bytesList; try { int listSize = consumeBytes(4).toInt(); if ((listSize % elementSize) != 0) { throw new InvalidSSZTypeException("SSZ encoded list length does not align with lengths of its elements"); } int nElements = listSize / elementSize; bytesList = new ArrayList<>(nElements); for (int i = 0; i < nElements; ++i) { bytesList.add(elementSupplier.get()); } } catch (Exception e) { this.index = originalIndex; throw e; } return bytesList; } } cava-0.6.0/ssz/src/main/java/net/consensys/cava/ssz/BytesSSZWriter.java000066400000000000000000000020121341750772100257750ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz; import net.consensys.cava.bytes.Bytes; import java.util.ArrayList; import java.util.List; final class BytesSSZWriter implements SSZWriter { private final List values = new ArrayList<>(); @Override public void writeSSZ(Bytes value) { values.add(value); } Bytes toBytes() { if (values.isEmpty()) { return Bytes.EMPTY; } return Bytes.wrap(values.toArray(new Bytes[0])); } } cava-0.6.0/ssz/src/main/java/net/consensys/cava/ssz/EndOfSSZException.java000066400000000000000000000014721341750772100263750ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz; /** * Indicates the end of the SSZ source has been reached unexpectedly. */ public class EndOfSSZException extends SSZException { public EndOfSSZException() { super("End of SSZ source reached"); } } cava-0.6.0/ssz/src/main/java/net/consensys/cava/ssz/InvalidSSZTypeException.java000066400000000000000000000015021341750772100276240ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz; /** * Indicates that an unexpected type was encountered when decoding SSZ. */ public class InvalidSSZTypeException extends SSZException { public InvalidSSZTypeException(String message) { super(message); } } cava-0.6.0/ssz/src/main/java/net/consensys/cava/ssz/SSZ.java000066400000000000000000001420011341750772100235740ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz; import static com.google.common.base.Preconditions.checkArgument; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.units.bigints.UInt256; import java.math.BigInteger; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.ReadOnlyBufferException; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; /** * Simple Serialize (SSZ) encoding and decoding. */ public final class SSZ { private static final Bytes TRUE = Bytes.of((byte) 1); private static final Bytes FALSE = Bytes.of((byte) 0); private SSZ() {} // Encoding /** * Encode values to a {@link Bytes} value. * * @param fn A consumer that will be provided with a {@link SSZWriter} that can consume values. * @return The SSZ encoding in a {@link Bytes} value. */ public static Bytes encode(Consumer fn) { requireNonNull(fn); BytesSSZWriter writer = new BytesSSZWriter(); fn.accept(writer); return writer.toBytes(); } /** * Encode values to a {@link ByteBuffer}. * * @param buffer The buffer to write into, starting from its current position. * @param fn A consumer that will be provided with a {@link SSZWriter} that can consume values. * @param The type of the buffer. * @return The buffer. * @throws BufferOverflowException If the writer attempts to write more than the provided buffer can hold. * @throws ReadOnlyBufferException If the provided buffer is read-only. */ public static T encodeTo(T buffer, Consumer fn) { requireNonNull(buffer); requireNonNull(fn); ByteBufferSSZWriter writer = new ByteBufferSSZWriter(buffer); fn.accept(writer); return buffer; } /** * Encode {@link Bytes}. * * @param value The value to encode. * @return The SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeBytes(Bytes value) { Bytes lengthBytes = encodeLong(value.size(), 32); return Bytes.wrap(lengthBytes, value); } static void encodeBytesTo(Bytes value, Consumer appender) { appender.accept(encodeLong(value.size(), 32)); appender.accept(value); } /** * Encode a value to a {@link Bytes} value. * * @param value The value to encode. * @return The SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeByteArray(byte[] value) { return encodeBytes(Bytes.wrap(value)); } static void encodeByteArrayTo(byte[] value, Consumer appender) { appender.accept(encodeLongToByteArray(value.length, 32)); appender.accept(value); } /** * Encode a string to a {@link Bytes} value. * * @param str The string to encode. * @return The SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeString(String str) { return encodeByteArray(str.getBytes(UTF_8)); } static void encodeStringTo(String str, Consumer appender) { encodeByteArrayTo(str.getBytes(UTF_8), appender); } /** * Encode a two's-compliment integer to a {@link Bytes} value. * * @param value The integer to encode. * @param bitLength The bit length of the encoded integer value (must be a multiple of 8). * @return The SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If the value is too large for the specified {@code bitLength}. */ public static Bytes encodeInt(int value, int bitLength) { return encodeLong(value, bitLength); } /** * Encode a two's-compliment long integer to a {@link Bytes} value. * * @param value The long to encode. * @param bitLength The bit length of the integer value (must be a multiple of 8). * @return The SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If the value is too large for the specified {@code bitLength}. */ public static Bytes encodeLong(long value, int bitLength) { return Bytes.wrap(encodeLongToByteArray(value, bitLength)); } static byte[] encodeLongToByteArray(long value, int bitLength) { checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); int zeros = (value >= 0) ? Long.numberOfLeadingZeros(value) : Long.numberOfLeadingZeros(-1 - value) - 1; int valueBytes = 8 - (zeros / 8); int byteLength = bitLength / 8; checkArgument(valueBytes <= byteLength, "value is too large for the desired bitLength"); byte[] encoded = new byte[byteLength]; int shift = 0; for (int i = 1; i <= valueBytes; i++) { encoded[byteLength - i] = (byte) ((value >> shift) & 0xFF); shift += 8; } if (value < 0) { // Extend the two's-compliment integer by setting all leading bits to 1. int padLength = byteLength - valueBytes; for (int i = 0; i < padLength; i++) { encoded[i] = (byte) 0xFF; } } return encoded; } /** * Encode a big integer to a {@link Bytes} value. * * @param value The big integer to encode. * @param bitLength The bit length of the integer value (must be a multiple of 8). * @return The SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If the value is too large for the specified {@code bitLength}. */ public static Bytes encodeBigInteger(BigInteger value, int bitLength) { return Bytes.wrap(encodeBigIntegerToByteArray(value, bitLength)); } public static byte[] encodeBigIntegerToByteArray(BigInteger value, int bitLength) { checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); byte[] bytes = value.toByteArray(); int valueBytes = bytes.length; int offset = 0; if (value.signum() >= 0 && bytes[0] == 0) { valueBytes = bytes.length - 1; offset = 1; } int byteLength = bitLength / 8; checkArgument(valueBytes <= byteLength, "value is too large for the desired bitLength"); if (valueBytes == byteLength && offset == 0) { return bytes; } byte[] encoded = new byte[byteLength]; int padLength = byteLength - valueBytes; System.arraycopy(bytes, offset, encoded, padLength, valueBytes); if (value.signum() < 0) { // Extend the two's-compliment integer by setting all leading bits to 1. for (int i = 0; i < padLength; i++) { encoded[i] = (byte) 0xFF; } } return encoded; } /** * Encode an 8-bit two's-compliment integer to a {@link Bytes} value. * * @param value The integer to encode. * @return The SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If the value is too large for the specified {@code bitLength}. */ public static Bytes encodeInt8(int value) { return encodeInt(value, 8); } /** * Encode a 16-bit two's-compliment integer to a {@link Bytes} value. * * @param value The integer to encode. * @return The SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If the value is too large for the specified {@code bitLength}. */ public static Bytes encodeInt16(int value) { return encodeInt(value, 16); } /** * Encode a 32-bit two's-compliment integer to a {@link Bytes} value. * * @param value The integer to encode. * @return The SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeInt32(int value) { return encodeInt(value, 32); } /** * Encode a 64-bit two's-compliment integer to a {@link Bytes} value. * * @param value The integer to encode. * @return The SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeInt64(long value) { return encodeLong(value, 64); } /** * Encode an unsigned integer to a {@link Bytes} value. * * @param value The integer to encode. * @param bitLength The bit length of the encoded integer value (must be a multiple of 8). * @return The SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If the value is too large for the specified {@code bitLength}. */ public static Bytes encodeUInt(int value, int bitLength) { return encodeULong(value, bitLength); } /** * Encode an unsigned long integer to a {@link Bytes} value. * * @param value The long to encode. * @param bitLength The bit length of the integer value (must be a multiple of 8). * @return The SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If the value is too large for the specified {@code bitLength}. */ public static Bytes encodeULong(long value, int bitLength) { return Bytes.wrap(encodeULongToByteArray(value, bitLength)); } static byte[] encodeULongToByteArray(long value, int bitLength) { checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); int zeros = Long.numberOfLeadingZeros(value); int valueBytes = 8 - (zeros / 8); int byteLength = bitLength / 8; checkArgument(valueBytes <= byteLength, "value is too large for the desired bitLength"); byte[] encoded = new byte[byteLength]; int shift = 0; for (int i = 1; i <= valueBytes; i++) { encoded[byteLength - i] = (byte) ((value >> shift) & 0xFF); shift += 8; } return encoded; } /** * Encode an 8-bit unsigned integer to a {@link Bytes} value. * * @param value The integer to encode. * @return The SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If the value is too large for the specified {@code bitLength}. */ public static Bytes encodeUInt8(int value) { return encodeUInt(value, 8); } /** * Encode a 16-bit unsigned integer to a {@link Bytes} value. * * @param value The integer to encode. * @return The SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If the value is too large for the specified {@code bitLength}. */ public static Bytes encodeUInt16(int value) { return encodeUInt(value, 16); } /** * Encode a 32-bit unsigned integer to a {@link Bytes} value. * * @param value The integer to encode. * @return The SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeUInt32(long value) { return encodeULong(value, 32); } /** * Encode a 64-bit unsigned integer to a {@link Bytes} value. * * @param value The integer to encode. * @return The SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeUInt64(long value) { return encodeULong(value, 64); } /** * Encode a 256-bit unsigned integer to a {@link Bytes} value. * * @param value The integer to encode. * @return The SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeUInt256(UInt256 value) { return value.toBytes(); } /** * Encode a boolean to a {@link Bytes} value. * * @param value The boolean to encode. * @return The SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeBoolean(boolean value) { return value ? TRUE : FALSE; } /** * Encode a 20-byte address to a {@link Bytes} value. * * @param address The address (must be exactly 20 bytes). * @return The SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If {@code address.size != 20}. */ public static Bytes encodeAddress(Bytes address) { checkArgument(address.size() == 20, "address is not 20 bytes"); return address; } /** * Encode a hash to a {@link Bytes} value. * * @param hash The hash. * @return The SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeHash(Bytes hash) { return hash; } /** * Encode a list of bytes. * * @param elements The bytes to write. * @return SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeBytesList(Bytes... elements) { ArrayList encoded = new ArrayList<>(elements.length * 2 + 1); encodeBytesListTo(elements, encoded::add); return Bytes.wrap(encoded.toArray(new Bytes[0])); } static void encodeBytesListTo(Bytes[] elements, Consumer appender) { // pre-calculate the list size - relies on knowing how encodeBytesTo does its serialization, but is worth it // to avoid having to pre-serialize all the elements long listSize = 0; for (Bytes bytes : elements) { listSize += 4; listSize += bytes.size(); if (listSize > Integer.MAX_VALUE) { throw new IllegalArgumentException("Cannot serialize list: overall length is too large"); } } appender.accept(encodeUInt32(listSize)); for (Bytes bytes : elements) { encodeBytesTo(bytes, appender); } } /** * Encode a list of strings. * * @param elements The strings to write. * @return SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeStringList(String... elements) { ArrayList encoded = new ArrayList<>(elements.length * 2 + 1); encodeStringListTo(elements, b -> encoded.add(Bytes.wrap(b))); return Bytes.wrap(encoded.toArray(new Bytes[0])); } static void encodeStringListTo(String[] elements, Consumer appender) { Bytes[] elementBytes = new Bytes[elements.length]; for (int i = 0; i < elements.length; ++i) { elementBytes[i] = Bytes.wrap(elements[i].getBytes(UTF_8)); } encodeBytesListTo(elementBytes, appender); } /** * Encode a list of two's compliment integers. * * @param bitLength The bit length of the encoded integers (must be a multiple of 8). * @param elements the integers to write. * @return SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If any values are too large for the specified {@code bitLength}. */ public static Bytes encodeIntList(int bitLength, int... elements) { ArrayList encoded = new ArrayList<>(elements.length + 1); encodeIntListTo(bitLength, elements, b -> encoded.add(Bytes.wrap(b))); return Bytes.wrap(encoded.toArray(new Bytes[0])); } static void encodeIntListTo(int bitLength, int[] elements, Consumer appender) { checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); appender.accept(listLengthPrefix(elements.length, bitLength / 8)); for (int value : elements) { appender.accept(encodeLongToByteArray(value, bitLength)); } } /** * Encode a list of two's compliment long integers. * * @param bitLength The bit length of the encoded integers (must be a multiple of 8). * @param elements the integers to write. * @return SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If any values are too large for the specified {@code bitLength}. */ public static Bytes encodeLongIntList(int bitLength, long... elements) { ArrayList encoded = new ArrayList<>(elements.length + 1); encodeLongIntListTo(bitLength, elements, b -> encoded.add(Bytes.wrap(b))); return Bytes.wrap(encoded.toArray(new Bytes[0])); } static void encodeLongIntListTo(int bitLength, long[] elements, Consumer appender) { checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); appender.accept(listLengthPrefix(elements.length, bitLength / 8)); for (long value : elements) { appender.accept(encodeLongToByteArray(value, bitLength)); } } /** * Encode a list of big integers. * * @param bitLength The bit length of the encoded integers (must be a multiple of 8). * @param elements The integers to write. * @return SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If any values are too large for the specified {@code bitLength}. */ public static Bytes encodeBigIntegerList(int bitLength, BigInteger... elements) { ArrayList encoded = new ArrayList<>(elements.length + 1); encodeBigIntegerListTo(bitLength, elements, b -> encoded.add(Bytes.wrap(b))); return Bytes.wrap(encoded.toArray(new Bytes[0])); } static void encodeBigIntegerListTo(int bitLength, BigInteger[] elements, Consumer appender) { checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); appender.accept(listLengthPrefix(elements.length, bitLength / 8)); for (BigInteger value : elements) { appender.accept(encodeBigIntegerToByteArray(value, bitLength)); } } /** * Encode a list of 8-bit two's compliment integers. * * @param elements The integers to write. * @return SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeInt8List(int... elements) { return encodeIntList(8, elements); } /** * Encode a list of 16-bit two's compliment integers. * * @param elements The integers to write. * @return SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeInt16List(int... elements) { return encodeIntList(16, elements); } /** * Encode a list of 32-bit two's compliment integers. * * @param elements The integers to write. * @return SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeInt32List(int... elements) { return encodeIntList(32, elements); } /** * Encode a list of 64-bit two's compliment integers. * * @param elements The integers to write. * @return SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeInt64List(long... elements) { return encodeLongIntList(64, elements); } /** * Encode a list of unsigned integers. * * @param bitLength The bit length of the encoded integers (must be a multiple of 8). * @param elements the integers to write. * @return SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If any values are too large for the specified {@code bitLength}. */ public static Bytes encodeUIntList(int bitLength, int... elements) { ArrayList encoded = new ArrayList<>(elements.length + 1); encodeUIntListTo(bitLength, elements, b -> encoded.add(Bytes.wrap(b))); return Bytes.wrap(encoded.toArray(new Bytes[0])); } static void encodeUIntListTo(int bitLength, int[] elements, Consumer appender) { checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); appender.accept(listLengthPrefix(elements.length, bitLength / 8)); for (int value : elements) { appender.accept(encodeULongToByteArray(value, bitLength)); } } /** * Encode a list of unsigned long integers. * * @param bitLength The bit length of the encoded integers (must be a multiple of 8). * @param elements the integers to write. * @return SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If any values are too large for the specified {@code bitLength}. */ public static Bytes encodeULongIntList(int bitLength, long... elements) { ArrayList encoded = new ArrayList<>(elements.length + 1); encodeULongIntListTo(bitLength, elements, b -> encoded.add(Bytes.wrap(b))); return Bytes.wrap(encoded.toArray(new Bytes[0])); } static void encodeULongIntListTo(int bitLength, long[] elements, Consumer appender) { checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); appender.accept(listLengthPrefix(elements.length, bitLength / 8)); for (long value : elements) { appender.accept(encodeULongToByteArray(value, bitLength)); } } /** * Encode a list of 8-bit unsigned integers. * * @param elements The integers to write. * @return SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeUInt8List(int... elements) { return encodeUIntList(8, elements); } /** * Encode a list of 16-bit unsigned integers. * * @param elements The integers to write. * @return SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeUInt16List(int... elements) { return encodeUIntList(16, elements); } /** * Encode a list of 32-bit unsigned integers. * * @param elements The integers to write. * @return SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeUInt32List(long... elements) { return encodeULongIntList(32, elements); } /** * Encode a list of 64-bit unsigned integers. * * @param elements The integers to write. * @return SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeUInt64List(long... elements) { return encodeULongIntList(64, elements); } /** * Encode a list of {@link UInt256}. * * @param elements The integers to write. * @return SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeUInt256List(UInt256... elements) { ArrayList encoded = new ArrayList<>(elements.length + 1); encodeUInt256ListTo(elements, b -> encoded.add(Bytes.wrap(b))); return Bytes.wrap(encoded.toArray(new Bytes[0])); } static void encodeUInt256ListTo(UInt256[] elements, Consumer appender) { appender.accept(Bytes.wrap(listLengthPrefix(elements.length, 256 / 8))); for (UInt256 value : elements) { appender.accept(encodeUInt256(value)); } } /** * Encode a list of hashes. * * @param elements The hashes to write. * @return SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeHashList(Bytes... elements) { ArrayList encoded = new ArrayList<>(elements.length + 1); encodeHashListTo(elements, b -> encoded.add(Bytes.wrap(b))); return Bytes.wrap(encoded.toArray(new Bytes[0])); } static void encodeHashListTo(Bytes[] elements, Consumer appender) { int hashLength = 0; for (Bytes bytes : elements) { if (hashLength == 0) { hashLength = bytes.size(); } else { checkArgument(bytes.size() == hashLength, "Hashes must be all of the same size"); } } appender.accept(Bytes.wrap(listLengthPrefix(elements.length, 32))); for (Bytes bytes : elements) { appender.accept(bytes); } } /** * Encode a list of addresses. * * @param elements The addresses to write. * @return SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If any {@code address.size != 20}. */ public static Bytes encodeAddressList(Bytes... elements) { ArrayList encoded = new ArrayList<>(elements.length + 1); encodeAddressListTo(elements, b -> encoded.add(Bytes.wrap(b))); return Bytes.wrap(encoded.toArray(new Bytes[0])); } static void encodeAddressListTo(Bytes[] elements, Consumer appender) { appender.accept(Bytes.wrap(listLengthPrefix(elements.length, 20))); for (Bytes bytes : elements) { appender.accept(encodeAddress(bytes)); } } /** * Encode a list of booleans. * * @param elements The booleans to write. * @return SSZ encoding in a {@link Bytes} value. */ public static Bytes encodeBooleanList(boolean... elements) { ArrayList encoded = new ArrayList<>(elements.length + 1); encodeBooleanListTo(elements, b -> encoded.add(Bytes.wrap(b))); return Bytes.wrap(encoded.toArray(new Bytes[0])); } static void encodeBooleanListTo(boolean[] elements, Consumer appender) { appender.accept(encodeInt32(elements.length)); for (boolean value : elements) { appender.accept(encodeBoolean(value)); } } private static byte[] listLengthPrefix(long nElements, int elementBytes) { long listSize; try { listSize = Math.multiplyExact(nElements, elementBytes); } catch (ArithmeticException e) { listSize = Long.MAX_VALUE; } if (listSize > Integer.MAX_VALUE) { throw new IllegalArgumentException("Cannot serialize list: overall length is too large"); } return encodeLongToByteArray(listSize, 32); } // Decoding /** * Read and decode SSZ from a {@link Bytes} value. * * @param source The SSZ encoded bytes. * @param fn A function that will be provided a {@link SSZReader}. * @param The result type of the reading function. * @return The result from the reading function. */ public static T decode(Bytes source, Function fn) { requireNonNull(source); requireNonNull(fn); return fn.apply(new BytesSSZReader(source)); } /** * Read a SSZ encoded bytes from a {@link Bytes} value. * * Note: prefer to use {@link #decodeBytes(Bytes, int)} instead, especially when reading untrusted data. * * @param source The SSZ encoded bytes. * @return The bytes. * @throws InvalidSSZTypeException If the next SSZ value is not a byte array, or is too large (greater than 2^32 * bytes). * @throws EndOfSSZException If there are no more SSZ values to read. */ public static Bytes decodeBytes(Bytes source) { return decode(source, SSZReader::readBytes); } /** * Read a SSZ encoded bytes from a {@link Bytes} value. * * @param source The SSZ encoded bytes. * @param limit The maximum number of bytes to read. * @return The bytes. * @throws InvalidSSZTypeException If the next SSZ value is not a byte array, or would exceed the limit. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static Bytes decodeBytes(Bytes source, int limit) { return decode(source, r -> r.readBytes(limit)); } /** * Read a SSZ encoded string from a {@link Bytes} value. * * Note: prefer to use {@link #decodeString(Bytes, int)} instead, especially when reading untrusted data. * * @param source The SSZ encoded bytes. * @return A string. * @throws InvalidSSZTypeException If the next SSZ value is not a byte array, or is too large (greater than 2^32 * bytes). * @throws EndOfSSZException If there are no more SSZ values to read. */ public static String decodeString(Bytes source) { return decode(source, SSZReader::readString); } /** * Read a SSZ encoded string from a {@link Bytes} value. * * @param source The SSZ encoded bytes. * @param limit The maximum number of bytes to read. * @return A string. * @throws InvalidSSZTypeException If the next SSZ value is not a byte array, or would exceed the limit. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static String decodeString(Bytes source, int limit) { return decode(source, r -> r.readString(limit)); } /** * Read a SSZ encoded two's-compliment integer from a {@link Bytes} value. * * @param source The SSZ encoded bytes. * @param bitLength The bit length of the integer to read (a multiple of 8). * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length, or the decoded * value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static int decodeInt(Bytes source, int bitLength) { return decode(source, r -> r.readInt(bitLength)); } /** * Read a SSZ encoded two's-compliment long integer from a {@link Bytes} value. * * @param source The SSZ encoded bytes. * @param bitLength The bit length of the integer to read (a multiple of 8). * @return A long. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length, or the decoded * value was too large to fit into a long. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static long decodeLong(Bytes source, int bitLength) { return decode(source, r -> r.readLong(bitLength)); } /** * Read a SSZ encoded two's-compliment big integer from a {@link Bytes} value. * * @param source The SSZ encoded bytes. * @param bitLength The bit length of the integer to read (a multiple of 8). * @return A string. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static BigInteger decodeBigInteger(Bytes source, int bitLength) { return decode(source, r -> r.readBigInteger(bitLength)); } /** * Read an 8-bit two's-compliment integer from the SSZ source. * * @param source The SSZ encoded bytes. * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for an 8-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static int decodeInt8(Bytes source) { return decodeInt(source, 8); } /** * Read a 16-bit two's-compliment integer from the SSZ source. * * @param source The SSZ encoded bytes. * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for an 16-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static int decodeInt16(Bytes source) { return decodeInt(source, 16); } /** * Read a 32-bit two's-compliment integer from the SSZ source. * * @param source The SSZ encoded bytes. * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for an 32-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static int decodeInt32(Bytes source) { return decodeInt(source, 32); } /** * Read a 64-bit two's-compliment integer from the SSZ source. * * @param source The SSZ encoded bytes. * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for an 64-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static long decodeInt64(Bytes source) { return decodeLong(source, 64); } /** * Read a SSZ encoded unsigned integer from a {@link Bytes} value. * * @param source The SSZ encoded bytes. * @param bitLength The bit length of the integer to read (a multiple of 8). * @return An unsigned int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length, or the decoded * value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static int decodeUInt(Bytes source, int bitLength) { return decode(source, r -> r.readUInt(bitLength)); } /** * Read a SSZ encoded unsigned long integer from a {@link Bytes} value. * * @param source The SSZ encoded bytes. * @param bitLength The bit length of the integer to read (a multiple of 8). * @return An unsigned long. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length, or the decoded * value was too large to fit into a long. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static long decodeULong(Bytes source, int bitLength) { return decode(source, r -> r.readULong(bitLength)); } /** * Read a SSZ encoded unsigned big integer from a {@link Bytes} value. * * @param source The SSZ encoded bytes. * @param bitLength The bit length of the integer to read (a multiple of 8). * @return A string. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static BigInteger decodeUnsignedBigInteger(Bytes source, int bitLength) { return decode(source, r -> r.readBigInteger(bitLength)); } /** * Read an 8-bit unsigned integer from the SSZ source. * * @param source The SSZ encoded bytes. * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for an 8-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static int decodeUInt8(Bytes source) { return decodeUInt(source, 8); } /** * Read a 16-bit unsigned integer from the SSZ source. * * @param source The SSZ encoded bytes. * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for an 16-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static int decodeUInt16(Bytes source) { return decodeUInt(source, 16); } /** * Read a 32-bit unsigned integer from the SSZ source. * * @param source The SSZ encoded bytes. * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for an 32-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static long decodeUInt32(Bytes source) { return decodeULong(source, 32); } /** * Read a 64-bit unsigned integer from the SSZ source. * * @param source The SSZ encoded bytes. * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for an 64-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static long decodeUInt64(Bytes source) { return decodeLong(source, 64); } /** * Read a 256-bit unsigned integer from the SSZ source. * * @param source The SSZ encoded bytes. * @return A {@link UInt256}. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for an 256-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static UInt256 decodeUInt256(Bytes source) { return decode(source, SSZReader::readUInt256); } /** * Read a boolean value from the SSZ source. * * @param source The SSZ encoded bytes. * @return A boolean. * @throws InvalidSSZTypeException If the decoded value is not a boolean. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static boolean decodeBoolean(Bytes source) { return decode(source, SSZReader::readBoolean); } /** * Read a 20-byte address from the SSZ source. * * @param source The SSZ encoded bytes. * @return The bytes of the Address. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 20-byte address. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static Bytes decodeAddress(Bytes source) { return decode(source, SSZReader::readAddress); } /** * Read a 32-byte hash from the SSZ source. * * @param source The SSZ encoded bytes. * @param hashLength The length of the hash (in bytes). * @return The bytes of the hash. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 32-byte hash. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static Bytes decodeHash(Bytes source, int hashLength) { return decode(source, r -> r.readHash(hashLength)); } /** * Read a list of {@link Bytes} from the SSZ source. * * Note: prefer to use {@link #decodeBytesList(Bytes, int)} instead, especially when reading untrusted data. * * @param source The SSZ encoded bytes. * @return A list of {@link Bytes}. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a byte array, or * any byte array is too large (greater than 2^32 bytes). * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeBytesList(Bytes source) { return decode(source, SSZReader::readBytesList); } /** * Read a list of {@link Bytes} from the SSZ source. * * @param source The SSZ encoded bytes. * @param limit The maximum number of bytes to read for each list element. * @return A list of {@link Bytes}. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a byte array, or * any byte array is too large (greater than 2^32 bytes). * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeBytesList(Bytes source, int limit) { return decode(source, r -> r.readBytesList(limit)); } /** * Read a list of byte arrays from the SSZ source. * * Note: prefer to use {@link #decodeByteArrayList(Bytes, int)} instead, especially when reading untrusted data. * * @param source The SSZ encoded bytes. * @return A list of byte arrays. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a byte array, or * any byte array is too large (greater than 2^32 bytes). * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeByteArrayList(Bytes source) { return decode(source, SSZReader::readByteArrayList); } /** * Read a list of byte arrays from the SSZ source. * * @param source The SSZ encoded bytes. * @param limit The maximum number of bytes to read for each list element. * @return A list of byte arrays. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a byte array, or * any byte array is too large (greater than 2^32 bytes). * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeByteArrayList(Bytes source, int limit) { return decode(source, r -> r.readByteArrayList(limit)); } /** * Read a list of strings from the SSZ source. * * Note: prefer to use {@link #decodeStringList(Bytes, int)} instead, especially when reading untrusted data. * * @param source The SSZ encoded bytes. * @return A list of strings. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a string, or any * string is too large (greater than 2^32 bytes). * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeStringList(Bytes source) { return decode(source, SSZReader::readStringList); } /** * Read a list of strings from the SSZ source. * * @param source The SSZ encoded bytes. * @param limit The maximum number of bytes to read for each list element. * @return A list of strings. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a string, or any * string is too large (greater than 2^32 bytes). * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeStringList(Bytes source, int limit) { return decode(source, r -> r.readStringList(limit)); } /** * Read a list of two's-compliment int values from the SSZ source. * * @param source The SSZ encoded bytes. * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeIntList(Bytes source, int bitLength) { return decode(source, r -> r.readIntList(bitLength)); } /** * Read a list of two's-compliment long int values from the SSZ source. * * @param source The SSZ encoded bytes. * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of longs. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into a long. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeLongIntList(Bytes source, int bitLength) { return decode(source, r -> r.readLongIntList(bitLength)); } /** * Read a list of two's-compliment big integer values from the SSZ source. * * @param source The SSZ encoded bytes. * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, or there are insufficient encoded bytes for * the desired bit length or any value in the list. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeBigIntegerList(Bytes source, int bitLength) { return decode(source, r -> r.readBigIntegerList(bitLength)); } /** * Read a list of 8-bit two's-compliment int values from the SSZ source. * * @param source The SSZ encoded bytes. * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeInt8List(Bytes source) { return decode(source, SSZReader::readInt8List); } /** * Read a list of 16-bit two's-compliment int values from the SSZ source. * * @param source The SSZ encoded bytes. * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeInt16List(Bytes source) { return decode(source, SSZReader::readInt16List); } /** * Read a list of 32-bit two's-compliment int values from the SSZ source. * * @param source The SSZ encoded bytes. * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeInt32List(Bytes source) { return decode(source, SSZReader::readInt32List); } /** * Read a list of 64-bit two's-compliment int values from the SSZ source. * * @param source The SSZ encoded bytes. * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeInt64List(Bytes source) { return decode(source, SSZReader::readInt64List); } /** * Read a list of unsigned int values from the SSZ source. * * @param source The SSZ encoded bytes. * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeUIntList(Bytes source, int bitLength) { return decode(source, r -> r.readUIntList(bitLength)); } /** * Read a list of unsigned long int values from the SSZ source. * * @param source The SSZ encoded bytes. * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of longs. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into a long. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeULongIntList(Bytes source, int bitLength) { return decode(source, r -> r.readULongIntList(bitLength)); } /** * Read a list of unsigned big integer values from the SSZ source. * * @param source The SSZ encoded bytes. * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, or there are insufficient encoded bytes for * the desired bit length or any value in the list. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeUnsignedBigIntegerList(Bytes source, int bitLength) { return decode(source, r -> r.readUnsignedBigIntegerList(bitLength)); } /** * Read a list of 8-bit unsigned int values from the SSZ source. * * @param source The SSZ encoded bytes. * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeUInt8List(Bytes source) { return decode(source, SSZReader::readUInt8List); } /** * Read a list of 16-bit unsigned int values from the SSZ source. * * @param source The SSZ encoded bytes. * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeUInt16List(Bytes source) { return decode(source, SSZReader::readUInt16List); } /** * Read a list of 32-bit unsigned int values from the SSZ source. * * @param source The SSZ encoded bytes. * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeUInt32List(Bytes source) { return decode(source, SSZReader::readUInt32List); } /** * Read a list of 64-bit unsigned int values from the SSZ source. * * @param source The SSZ encoded bytes. * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeUInt64List(Bytes source) { return decode(source, SSZReader::readUInt64List); } /** * Read a list of 256-bit unsigned int values from the SSZ source. * * @param source The SSZ encoded bytes. * @return A list of {@link UInt256}. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeUInt256List(Bytes source) { return decode(source, SSZReader::readUInt256List); } /** * Read a list of 20-byte addresses from the SSZ source. * * @param source The SSZ encoded bytes. * @return A list of 20-byte addresses. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for any * address in the list. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeAddressList(Bytes source) { return decode(source, SSZReader::readAddressList); } /** * Read a list of 32-byte hashes from the SSZ source. * * @param source The SSZ encoded bytes. * @param hashLength The length of the hash (in bytes). * @return A list of 32-byte hashes. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for any * hash in the list. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeHashList(Bytes source, int hashLength) { return decode(source, r -> r.readHashList(hashLength)); } /** * Read a list of booleans from the SSZ source. * * @param source The SSZ encoded bytes. * @return A list of booleans. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for all * the booleans in the list. * @throws EndOfSSZException If there are no more SSZ values to read. */ public static List decodeBooleanList(Bytes source) { return decode(source, SSZReader::readBooleanList); } } cava-0.6.0/ssz/src/main/java/net/consensys/cava/ssz/SSZException.java000066400000000000000000000016741341750772100254650ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz; /** * Base type for all SSZ encoding and decoding exceptions. */ public class SSZException extends RuntimeException { public SSZException(String message) { super(message); } public SSZException(Throwable cause) { super(cause); } public SSZException(String message, Throwable cause) { super(message, cause); } } cava-0.6.0/ssz/src/main/java/net/consensys/cava/ssz/SSZReader.java000066400000000000000000000545211341750772100247300ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz; import static java.nio.charset.StandardCharsets.UTF_8; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.units.bigints.UInt256; import java.math.BigInteger; import java.util.List; import java.util.stream.Collectors; /** * A reader for consuming values from an SSZ encoded source. */ public interface SSZReader { /** * Read bytes from the SSZ source. * * Note: prefer to use {@link #readBytes(int)} instead, especially when reading untrusted data. * * @return The bytes for the next value. * @throws InvalidSSZTypeException If the next SSZ value is not a byte array, or is too large (greater than 2^32 * bytes). * @throws EndOfSSZException If there are no more SSZ values to read. */ default Bytes readBytes() { return readBytes(Integer.MAX_VALUE); } /** * Read bytes from the SSZ source. * * @param limit The maximum number of bytes to read. * @return The bytes for the next value. * @throws InvalidSSZTypeException If the next SSZ value is not a byte array, or would exceed the limit. * @throws EndOfSSZException If there are no more SSZ values to read. */ Bytes readBytes(int limit); /** * Read a byte array from the SSZ source. * * Note: prefer to use {@link #readByteArray(int)} instead, especially when reading untrusted data. * * @return The byte array for the next value. * @throws InvalidSSZTypeException If the next SSZ value is not a byte array, or is too large (greater than 2^32 * bytes). * @throws EndOfSSZException If there are no more SSZ values to read. */ default byte[] readByteArray() { return readByteArray(Integer.MAX_VALUE); } /** * Read a byte array from the SSZ source. * * @param limit The maximum number of bytes to read. * @return The byte array for the next value. * @throws InvalidSSZTypeException If the next SSZ value is not a byte array, or would exceed the limit. * @throws EndOfSSZException If there are no more SSZ values to read. */ default byte[] readByteArray(int limit) { return readBytes(limit).toArrayUnsafe(); } /** * Read a string value from the SSZ source. * * Note: prefer to use {@link #readString(int)} instead, especially when reading untrusted data. * * @return A string. * @throws InvalidSSZTypeException If the next SSZ value is not a byte array, or is too large (greater than 2^32 * bytes). * @throws EndOfSSZException If there are no more SSZ values to read. */ default String readString() { return new String(readByteArray(), UTF_8); } /** * Read a string value from the SSZ source. * * @param limit The maximum number of bytes to read. * @return A string. * @throws InvalidSSZTypeException If the next SSZ value is not a byte array, or would exceed the limit. * @throws EndOfSSZException If there are no more SSZ values to read. */ default String readString(int limit) { return new String(readByteArray(limit), UTF_8); } /** * Read a two's-compliment int value from the SSZ source. * * @param bitLength The bit length of the integer to read (a multiple of 8). * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length, or the decoded * value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ int readInt(int bitLength); /** * Read a two's-compliment long value from the SSZ source. * * @param bitLength The bit length of the integer to read (a multiple of 8). * @return A long. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length, or the decoded * value was too large to fit into a long. * @throws EndOfSSZException If there are no more SSZ values to read. */ long readLong(int bitLength); /** * Read a big integer value from the SSZ source. * * @param bitLength The bit length of the integer to read (a multiple of 8). * @return A big integer. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length. * @throws EndOfSSZException If there are no more SSZ values to read. */ BigInteger readBigInteger(int bitLength); /** * Read an 8-bit two's-compliment integer from the SSZ source. * * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for an 8-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default int readInt8() { return readInt(8); } /** * Read a 16-bit two's-compliment integer from the SSZ source. * * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 16-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default int readInt16() { return readInt(16); } /** * Read a 32-bit two's-compliment integer from the SSZ source. * * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 32-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default int readInt32() { return readInt(32); } /** * Read an 64-bit two's-compliment integer from the SSZ source. * * @return A long. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 64-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default long readInt64() { return readLong(64); } /** * Read an unsigned int value from the SSZ source. * * @param bitLength The bit length of the integer to read (a multiple of 8). * @return An unsigned int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length, or the decoded * value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default int readUInt(int bitLength) { // encoding is the same for unsigned return readInt(bitLength); } /** * Read an unsigned long value from the SSZ source. * * @param bitLength The bit length of the integer to read (a multiple of 8). * @return An unsigned long. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length, or the decoded * value was too large to fit into a long. * @throws EndOfSSZException If there are no more SSZ values to read. */ default long readULong(int bitLength) { // encoding is the same for unsigned return readLong(bitLength); } /** * Read an unsigned big integer value from the SSZ source. * * @param bitLength The bit length of the integer to read (a multiple of 8). * @return A big integer. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length. * @throws EndOfSSZException If there are no more SSZ values to read. */ BigInteger readUnsignedBigInteger(int bitLength); /** * Read an 8-bit unsigned integer from the SSZ source. * * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for an 8-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default int readUInt8() { return readUInt(8); } /** * Read a 16-bit unsigned integer from the SSZ source. * * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 16-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default int readUInt16() { return readUInt(16); } /** * Read a 32-bit unsigned integer from the SSZ source. * * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 32-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default long readUInt32() { return readULong(32); } /** * Read an 64-bit unsigned integer from the SSZ source. * * @return A long. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 64-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default long readUInt64() { return readULong(64); } /** * Read a {@link UInt256} from the SSZ source. * * @return A {@link UInt256}. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 256-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ UInt256 readUInt256(); /** * Read a boolean from the SSZ source. * * @return A boolean. * @throws InvalidSSZTypeException If the decoded value is not a boolean. * @throws EndOfSSZException If there are no more SSZ values to read. */ default boolean readBoolean() { int value = readInt(8); if (value == 0) { return false; } else if (value == 1) { return true; } else { throw new InvalidSSZTypeException("decoded value is not a boolean"); } } /** * Read a 20-byte address from the SSZ source. * * @return The bytes of the Address. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 20-byte address. * @throws EndOfSSZException If there are no more SSZ values to read. */ Bytes readAddress(); /** * Read a hash from the SSZ source. * * @param hashLength The length of the hash (in bytes). * @return The bytes of the hash. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 32-byte hash. * @throws EndOfSSZException If there are no more SSZ values to read. */ Bytes readHash(int hashLength); /** * Read a list of {@link Bytes} from the SSZ source. * * Note: prefer to use {@link #readBytesList(int)} instead, especially when reading untrusted data. * * @return A list of {@link Bytes}. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a byte array, or * any byte array is too large (greater than 2^32 bytes). * @throws EndOfSSZException If there are no more SSZ values to read. */ default List readBytesList() { return readBytesList(Integer.MAX_VALUE); } /** * Read a list of {@link Bytes} from the SSZ source. * * @param limit The maximum number of bytes to read for each list element. * @return A list of {@link Bytes}. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a byte array, or * the size of any byte array would exceed the limit. * @throws EndOfSSZException If there are no more SSZ values to read. */ List readBytesList(int limit); /** * Read a list of byte arrays from the SSZ source. * * Note: prefer to use {@link #readByteArrayList(int)} instead, especially when reading untrusted data. * * @return A list of byte arrays. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a byte array, or * any byte array is too large (greater than 2^32 bytes). * @throws EndOfSSZException If there are no more SSZ values to read. */ default List readByteArrayList() { return readByteArrayList(Integer.MAX_VALUE); } /** * Read a list of byte arrays from the SSZ source. * * @param limit The maximum number of bytes to read for each list element. * @return A list of byte arrays. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a byte array, or * the size of any byte array would exceed the limit. * @throws EndOfSSZException If there are no more SSZ values to read. */ default List readByteArrayList(int limit) { return readBytesList(limit).stream().map(Bytes::toArrayUnsafe).collect(Collectors.toList()); } /** * Read a list of strings from the SSZ source. * * Note: prefer to use {@link #readStringList(int)} instead, especially when reading untrusted data. * * @return A list of strings. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a string, or any * string is too large (greater than 2^32 bytes). * @throws EndOfSSZException If there are no more SSZ values to read. */ default List readStringList() { return readStringList(Integer.MAX_VALUE); } /** * Read a list of strings from the SSZ source. * * @param limit The maximum number of bytes to read for each list element. * @return A list of strings. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a string, or the * size of any string would exceed the limit. * @throws EndOfSSZException If there are no more SSZ values to read. */ List readStringList(int limit); /** * Read a list of two's-compliment int values from the SSZ source. * * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ List readIntList(int bitLength); /** * Read a list of two's-compliment long int values from the SSZ source. * * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of longs. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into a long. * @throws EndOfSSZException If there are no more SSZ values to read. */ List readLongIntList(int bitLength); /** * Read a list of two's-compliment big integer values from the SSZ source. * * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of big integers. * @throws InvalidSSZTypeException If the next SSZ value is not a list, or there are insufficient encoded bytes for * the desired bit length or any value in the list. * @throws EndOfSSZException If there are no more SSZ values to read. */ List readBigIntegerList(int bitLength); /** * Read a list of 8-bit two's-compliment int values from the SSZ source. * * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default List readInt8List() { return readIntList(8); } /** * Read a list of 16-bit two's-compliment int values from the SSZ source. * * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default List readInt16List() { return readIntList(16); } /** * Read a list of 32-bit two's-compliment int values from the SSZ source. * * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default List readInt32List() { return readIntList(32); } /** * Read a list of 64-bit two's-compliment int values from the SSZ source. * * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default List readInt64List() { return readLongIntList(64); } /** * Read a list of unsigned int values from the SSZ source. * * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default List readUIntList(int bitLength) { // encoding is the same for unsigned return readIntList(bitLength); } /** * Read a list of unsigned long int values from the SSZ source. * * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of longs. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into a long. * @throws EndOfSSZException If there are no more SSZ values to read. */ default List readULongIntList(int bitLength) { // encoding is the same for unsigned return readLongIntList(bitLength); } /** * Read a list of unsigned big integer values from the SSZ source. * * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of big integers. * @throws InvalidSSZTypeException If the next SSZ value is not a list, or there are insufficient encoded bytes for * the desired bit length or any value in the list. * @throws EndOfSSZException If there are no more SSZ values to read. */ List readUnsignedBigIntegerList(int bitLength); /** * Read a list of 8-bit unsigned int values from the SSZ source. * * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default List readUInt8List() { return readUIntList(8); } /** * Read a list of 16-bit unsigned int values from the SSZ source. * * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default List readUInt16List() { return readUIntList(16); } /** * Read a list of 32-bit unsigned int values from the SSZ source. * * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default List readUInt32List() { return readULongIntList(32); } /** * Read a list of 64-bit unsigned int values from the SSZ source. * * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ default List readUInt64List() { return readULongIntList(64); } /** * Read a list of 256-bit unsigned int values from the SSZ source. * * @return A list of {@link UInt256}. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ List readUInt256List(); /** * Read a list of 20-byte addresses from the SSZ source. * * @return A list of 20-byte addresses. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for any * address in the list. * @throws EndOfSSZException If there are no more SSZ values to read. */ List readAddressList(); /** * Read a list of hashes from the SSZ source. * * @param hashLength The length of the hash (in bytes). * @return A list of 32-byte hashes. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for any * hash in the list. * @throws EndOfSSZException If there are no more SSZ values to read. */ List readHashList(int hashLength); /** * Read a list of booleans from the SSZ source. * * @return A list of booleans. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for all * the booleans in the list. * @throws EndOfSSZException If there are no more SSZ values to read. */ List readBooleanList(); /** * Check if all values have been read. * * @return {@code true} if all values have been read. */ boolean isComplete(); } cava-0.6.0/ssz/src/main/java/net/consensys/cava/ssz/SSZWriter.java000066400000000000000000000305721341750772100250020ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.units.bigints.UInt256; import java.math.BigInteger; /** * A writer for encoding values to SSZ. */ public interface SSZWriter { /** * Append an already SSZ encoded value. * * Note that this method may not validate that {@code value} is a valid SSZ sequence. Appending an invalid SSZ * sequence will cause the entire SSZ encoding produced by this writer to also be invalid. * * @param value The SSZ encoded bytes to append. */ void writeSSZ(Bytes value); /** * Append an already SSZ encoded value. * * Note that this method may not validate that {@code value} is a valid SSZ sequence. Appending an invalid SSZ * sequence will cause the entire SSZ encoding produced by this writer to also be invalid. * * @param value The SSZ encoded bytes to append. */ default void writeSSZ(byte[] value) { writeSSZ(Bytes.wrap(value)); } /** * Encode a {@link Bytes} value to SSZ. * * @param value The byte array to encode. */ default void writeBytes(Bytes value) { SSZ.encodeBytesTo(value, this::writeSSZ); } /** * Encode a byte array to SSZ. * * @param value The byte array to encode. */ default void writeBytes(byte[] value) { SSZ.encodeByteArrayTo(value, this::writeSSZ); } /** * Write a string to the output. * * @param str The string to write. */ default void writeString(String str) { SSZ.encodeStringTo(str, this::writeSSZ); } /** * Write a two's-compliment integer to the output. * * @param value The integer to write. * @param bitLength The bit length of the integer value. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ default void writeInt(int value, int bitLength) { writeSSZ(SSZ.encodeLongToByteArray(value, bitLength)); } /** * Write a two's-compliment long to the output. * * @param value The long value to write. * @param bitLength The bit length of the integer value. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ default void writeLong(long value, int bitLength) { writeSSZ(SSZ.encodeLongToByteArray(value, bitLength)); } /** * Write a big integer to the output. * * @param value The integer to write. * @param bitLength The bit length of the integer value. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ default void writeBigInteger(BigInteger value, int bitLength) { writeSSZ(SSZ.encodeBigIntegerToByteArray(value, bitLength)); } /** * Write an 8-bit two's-compliment integer to the output. * * @param value The integer to write. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ default void writeInt8(int value) { writeInt(value, 8); } /** * Write a 16-bit two's-compliment integer to the output. * * @param value The integer to write. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ default void writeInt16(int value) { writeInt(value, 16); } /** * Write a 32-bit two's-compliment integer to the output. * * @param value The integer to write. */ default void writeInt32(int value) { writeInt(value, 32); } /** * Write a 64-bit two's-compliment integer to the output. * * @param value The long to write. */ default void writeInt64(long value) { writeLong(value, 64); } /** * Write an unsigned integer to the output. * * @param value The integer to write. * @param bitLength The bit length of the integer value. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ default void writeUInt(int value, int bitLength) { writeSSZ(SSZ.encodeULongToByteArray(value, bitLength)); } /** * Write an unsigned long to the output. * * @param value The long value to write. * @param bitLength The bit length of the integer value. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ default void writeULong(long value, int bitLength) { writeSSZ(SSZ.encodeULongToByteArray(value, bitLength)); } /** * Write an 8-bit unsigned integer to the output. * * @param value The integer to write. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ default void writeUInt8(int value) { writeUInt(value, 8); } /** * Write a 16-bit unsigned integer to the output. * * @param value The integer to write. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ default void writeUInt16(int value) { writeUInt(value, 16); } /** * Write a 32-bit unsigned integer to the output. * * @param value The integer to write. */ default void writeUInt32(long value) { writeULong(value, 32); } /** * Write a 64-bit unsigned integer to the output. * * @param value The long to write. */ default void writeUInt64(long value) { writeULong(value, 64); } /** * Write a {@link UInt256} to the output. * * @param value The {@link UInt256} to write. */ default void writeUInt256(UInt256 value) { writeSSZ(SSZ.encodeUInt256(value)); } /** * Write a boolean to the output. * * @param value The boolean value. */ default void writeBoolean(boolean value) { writeSSZ(SSZ.encodeBoolean(value)); } /** * Write an address. * * @param address The address (must be exactly 20 bytes). * @throws IllegalArgumentException If {@code address.size != 20}. */ default void writeAddress(Bytes address) { writeSSZ(SSZ.encodeAddress(address)); } /** * Write a hash. * * @param hash The hash. */ default void writeHash(Bytes hash) { writeSSZ(SSZ.encodeHash(hash)); } /** * Write a list of bytes. * * @param elements The bytes to write as a list. */ default void writeBytesList(Bytes... elements) { SSZ.encodeBytesListTo(elements, this::writeSSZ); } /** * Write a list of strings, which must be of the same length * * @param elements The strings to write as a list. */ default void writeStringList(String... elements) { SSZ.encodeStringListTo(elements, this::writeSSZ); } /** * Write a list of two's compliment integers. * * @param bitLength The bit length of the encoded integers (must be a multiple of 8). * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ default void writeIntList(int bitLength, int... elements) { SSZ.encodeIntListTo(bitLength, elements, this::writeSSZ); } /** * Write a list of two's compliment long integers. * * @param bitLength The bit length of the encoded integers (must be a multiple of 8). * @param elements The long integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ default void writeLongIntList(int bitLength, long... elements) { SSZ.encodeLongIntListTo(bitLength, elements, this::writeSSZ); } /** * Write a list of big integers. * * @param bitLength The bit length of each integer. * @param elements The integers to write as a list. * @throws IllegalArgumentException if an integer cannot be stored in the number of bytes provided */ default void writeBigIntegerList(int bitLength, BigInteger... elements) { SSZ.encodeBigIntegerListTo(bitLength, elements, this::writeSSZ); } /** * Write a list of 8-bit two's compliment integers. * * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ default void writeInt8List(int... elements) { writeIntList(8, elements); } /** * Write a list of 16-bit two's compliment integers. * * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ default void writeInt16List(int... elements) { writeIntList(16, elements); } /** * Write a list of 32-bit two's compliment integers. * * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ default void writeInt32List(int... elements) { writeIntList(32, elements); } /** * Write a list of 64-bit two's compliment integers. * * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ default void writeInt64List(int... elements) { writeIntList(64, elements); } /** * Write a list of unsigned integers. * * @param bitLength The bit length of the encoded integers (must be a multiple of 8). * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ default void writeUIntList(int bitLength, int... elements) { SSZ.encodeUIntListTo(bitLength, elements, this::writeSSZ); } /** * Write a list of unsigned long integers. * * @param bitLength The bit length of the encoded integers (must be a multiple of 8). * @param elements The long integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ default void writeULongIntList(int bitLength, long... elements) { SSZ.encodeULongIntListTo(bitLength, elements, this::writeSSZ); } /** * Write a list of 8-bit unsigned integers. * * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ default void writeUInt8List(int... elements) { writeUIntList(8, elements); } /** * Write a list of 16-bit unsigned integers. * * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ default void writeUInt16List(int... elements) { writeUIntList(16, elements); } /** * Write a list of 32-bit unsigned integers. * * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ default void writeUInt32List(long... elements) { writeULongIntList(32, elements); } /** * Write a list of 64-bit unsigned integers. * * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ default void writeUInt64List(long... elements) { writeULongIntList(64, elements); } /** * Write a list of unsigned 256-bit integers. * * @param elements The integers to write as a list. */ default void writeUInt256List(UInt256... elements) { SSZ.encodeUInt256ListTo(elements, this::writeSSZ); } /** * Write a list of hashes. * * @param elements The hashes to write as a list. */ default void writeHashList(Bytes... elements) { SSZ.encodeHashListTo(elements, this::writeSSZ); } /** * Write a list of addresses. * * @param elements The addresses to write as a list. * @throws IllegalArgumentException If any {@code address.size != 20}. */ default void writeAddressList(Bytes... elements) { SSZ.encodeAddressListTo(elements, this::writeSSZ); } /** * Write a list of booleans. * * @param elements The booleans to write as a list. */ default void writeBooleanList(boolean... elements) { SSZ.encodeBooleanListTo(elements, this::writeSSZ); } } cava-0.6.0/ssz/src/main/java/net/consensys/cava/ssz/package-info.java000066400000000000000000000011471341750772100254460ustar00rootroot00000000000000/** * Simple Serialize (SSZ) encoding and decoding. *

* An implementation of the Ethereum Simple Serialize (SSZ) algorithm, as described at https://github.com/ethereum/eth2.0-specs/blob/master/specs/simple-serialize.md. *

* These classes are included in the standard Cava distribution, or separately when using the gradle dependency * 'net.consensys.cava:cava-ssz' (cava-ssz.jar). */ @ParametersAreNonnullByDefault package net.consensys.cava.ssz; import javax.annotation.ParametersAreNonnullByDefault; cava-0.6.0/ssz/src/main/kotlin/000077500000000000000000000000001341750772100162705ustar00rootroot00000000000000cava-0.6.0/ssz/src/main/kotlin/net/000077500000000000000000000000001341750772100170565ustar00rootroot00000000000000cava-0.6.0/ssz/src/main/kotlin/net/consensys/000077500000000000000000000000001341750772100211025ustar00rootroot00000000000000cava-0.6.0/ssz/src/main/kotlin/net/consensys/cava/000077500000000000000000000000001341750772100220145ustar00rootroot00000000000000cava-0.6.0/ssz/src/main/kotlin/net/consensys/cava/ssz/000077500000000000000000000000001341750772100226335ustar00rootroot00000000000000cava-0.6.0/ssz/src/main/kotlin/net/consensys/cava/ssz/experimental/000077500000000000000000000000001341750772100253305ustar00rootroot00000000000000cava-0.6.0/ssz/src/main/kotlin/net/consensys/cava/ssz/experimental/BytesSSZReader.kt000066400000000000000000000051731341750772100305070ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz.experimental import net.consensys.cava.bytes.Bytes import net.consensys.cava.units.bigints.UInt256 import java.math.BigInteger @ExperimentalUnsignedTypes internal class BytesSSZReader(private val delegate: net.consensys.cava.ssz.SSZReader) : SSZReader { override fun readBytes(limit: Int): Bytes = delegate.readBytes(limit) override fun readInt(bitLength: Int): Int = delegate.readInt(bitLength) override fun readLong(bitLength: Int): Long = delegate.readLong(bitLength) override fun readBigInteger(bitLength: Int): BigInteger = delegate.readBigInteger(bitLength) override fun readUnsignedBigInteger(bitLength: Int): BigInteger = delegate.readUnsignedBigInteger(bitLength) override fun readUInt256(): UInt256 = delegate.readUInt256() override fun readAddress(): Bytes = delegate.readAddress() override fun readHash(hashLength: Int): Bytes = delegate.readHash(hashLength) override fun readBytesList(limit: Int): List = delegate.readBytesList(limit) override fun readByteArrayList(limit: Int): List = delegate.readByteArrayList(limit) override fun readStringList(limit: Int): List = delegate.readStringList(limit) override fun readIntList(bitLength: Int): List = delegate.readIntList(bitLength) override fun readLongIntList(bitLength: Int): List = delegate.readLongIntList(bitLength) override fun readBigIntegerList(bitLength: Int): List = delegate.readBigIntegerList(bitLength) override fun readUnsignedBigIntegerList(bitLength: Int): List = delegate.readUnsignedBigIntegerList(bitLength) override fun readUInt256List(): List = delegate.readUInt256List() override fun readAddressList(): List = delegate.readAddressList() override fun readHashList(hashLength: Int): List = delegate.readHashList(hashLength) override fun readBooleanList(): List = delegate.readBooleanList() override val isComplete: Boolean get() = delegate.isComplete } cava-0.6.0/ssz/src/main/kotlin/net/consensys/cava/ssz/experimental/BytesSSZWriter.kt000066400000000000000000000054751341750772100305660ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz.experimental import net.consensys.cava.bytes.Bytes import net.consensys.cava.units.bigints.UInt256 import java.math.BigInteger @ExperimentalUnsignedTypes internal class BytesSSZWriter(private val delegate: net.consensys.cava.ssz.SSZWriter) : SSZWriter { override fun writeSSZ(value: Bytes) = delegate.writeSSZ(value) override fun writeBytes(value: Bytes) = delegate.writeBytes(value) override fun writeBytes(value: ByteArray) = delegate.writeBytes(value) override fun writeString(str: String) = delegate.writeString(str) override fun writeInt(value: Int, bitLength: Int) = delegate.writeInt(value, bitLength) override fun writeLong(value: Long, bitLength: Int) = delegate.writeLong(value, bitLength) override fun writeUInt(value: UInt, bitLength: Int) = delegate.writeUInt(value.toInt(), bitLength) override fun writeULong(value: ULong, bitLength: Int) = delegate.writeULong(value.toLong(), bitLength) override fun writeBytesList(vararg elements: Bytes) = delegate.writeBytesList(*elements) override fun writeStringList(vararg elements: String) = delegate.writeStringList(*elements) override fun writeIntList(bitLength: Int, vararg elements: Int) = delegate.writeIntList(bitLength, *elements) override fun writeLongIntList(bitLength: Int, vararg elements: Long) = delegate.writeLongIntList(bitLength, *elements) override fun writeBigIntegerList(bitLength: Int, vararg elements: BigInteger) = delegate.writeBigIntegerList(bitLength, *elements) override fun writeUIntList(bitLength: Int, vararg elements: UInt) = delegate.writeUIntList(bitLength, *(elements.map { i -> i.toInt() }.toIntArray())) override fun writeULongIntList(bitLength: Int, vararg elements: ULong) = delegate.writeULongIntList(bitLength, *(elements.map { i -> i.toLong() }.toLongArray())) override fun writeUInt256List(vararg elements: UInt256) = delegate.writeUInt256List(*elements) override fun writeHashList(vararg elements: Bytes) = delegate.writeHashList(*elements) override fun writeAddressList(vararg elements: Bytes) = delegate.writeAddressList(*elements) override fun writeBooleanList(vararg elements: Boolean) = delegate.writeBooleanList(*elements) } cava-0.6.0/ssz/src/main/kotlin/net/consensys/cava/ssz/experimental/SSZ.kt000066400000000000000000000164621341750772100263600ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz.experimental import net.consensys.cava.bytes.Bytes import net.consensys.cava.ssz.EndOfSSZException import net.consensys.cava.ssz.InvalidSSZTypeException import java.nio.BufferOverflowException import java.nio.ByteBuffer import java.nio.ReadOnlyBufferException /** * Simple Serialize (SSZ) encoding and decoding. */ @ExperimentalUnsignedTypes object SSZ { // Encoding /** * Encode values to a {@link Bytes} value. * * @param fn A consumer that will be provided with a {@link SSZWriter} that can consume values. * @return The SSZ encoding in a {@link Bytes} value. */ fun encode(fn: (SSZWriter) -> Unit): Bytes = net.consensys.cava.ssz.SSZ.encode { w -> fn(BytesSSZWriter(w)) } /** * Encode values to a {@link ByteBuffer}. * * @param buffer The buffer to write into, starting from its current position. * @param fn A consumer that will be provided with a {@link SSZWriter} that can consume values. * @param The type of the buffer. * @return The buffer. * @throws BufferOverflowException If the writer attempts to write more than the provided buffer can hold. * @throws ReadOnlyBufferException If the provided buffer is read-only. */ fun encodeTo(buffer: T, fn: (SSZWriter) -> Unit): T = net.consensys.cava.ssz.SSZ.encodeTo(buffer) { w -> fn(BytesSSZWriter(w)) } /** * Encode an unsigned integer to a {@link Bytes} value. * * @param value The integer to encode. * @param bitLength The bit length of the encoded integer value (must be a multiple of 8). * @return The SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If the value is too large for the specified {@code bitLength}. */ fun encodeUInt(value: UInt, bitLength: Int): Bytes = net.consensys.cava.ssz.SSZ.encodeULong(value.toLong(), bitLength) /** * Encode an unsigned long integer to a {@link Bytes} value. * * @param value The long to encode. * @param bitLength The bit length of the integer value (must be a multiple of 8). * @return The SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If the value is too large for the specified {@code bitLength}. */ fun encodeULong(value: ULong, bitLength: Int): Bytes = net.consensys.cava.ssz.SSZ.encodeULong(value.toLong(), bitLength) /** * Encode an 8-bit unsigned integer to a {@link Bytes} value. * * @param value The integer to encode. * @return The SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If the value is too large for the specified {@code bitLength}. */ fun encodeUInt8(value: UInt): Bytes = encodeUInt(value, 8) /** * Encode a 16-bit unsigned integer to a {@link Bytes} value. * * @param value The integer to encode. * @return The SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If the value is too large for the specified {@code bitLength}. */ fun encodeUInt16(value: UInt): Bytes = encodeUInt(value, 16) /** * Encode a 32-bit unsigned integer to a {@link Bytes} value. * * @param value The integer to encode. * @return The SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If the value is too large for the specified {@code bitLength}. */ fun encodeUInt32(value: UInt): Bytes = encodeUInt(value, 32) /** * Encode a 64-bit unsigned integer to a {@link Bytes} value. * * @param value The integer to encode. * @return The SSZ encoding in a {@link Bytes} value. * @throws IllegalArgumentException If the value is too large for the specified {@code bitLength}. */ fun encodeUInt64(value: ULong): Bytes = encodeULong(value, 64) // Decoding /** * Read and decode SSZ from a {@link Bytes} value. * * @param source The SSZ encoded bytes. * @param fn A function that will be provided a {@link SSZReader}. * @param The result type of the reading function. * @return The result from the reading function. */ fun decode(source: Bytes, fn: (SSZReader) -> T): T = net.consensys.cava.ssz.SSZ.decode(source) { r -> fn(BytesSSZReader(r)) } /** * Read a SSZ encoded unsigned integer from a {@link Bytes} value. * * @param source The SSZ encoded bytes. * @param bitLength The bit length of the integer to read (a multiple of 8). * @return An unsigned int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length, or the decoded * value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun decodeUInt(source: Bytes, bitLength: Int): UInt = net.consensys.cava.ssz.SSZ.decodeUInt(source, bitLength).toUInt() /** * Read a SSZ encoded unsigned long integer from a {@link Bytes} value. * * @param source The SSZ encoded bytes. * @param bitLength The bit length of the integer to read (a multiple of 8). * @return An unsigned long. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length, or the decoded * value was too large to fit into a long. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun decodeULong(source: Bytes, bitLength: Int): ULong = net.consensys.cava.ssz.SSZ.decodeULong(source, bitLength).toULong() /** * Read an 8-bit unsigned integer from the SSZ source. * * @param source The SSZ encoded bytes. * @return An unsigned int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for an 8-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun decodeUInt8(source: Bytes) = decodeUInt(source, 8) /** * Read a 16-bit unsigned integer from the SSZ source. * * @param source The SSZ encoded bytes. * @return An unsigned int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for an 8-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun decodeUInt16(source: Bytes) = decodeUInt(source, 16) /** * Read a 32-bit unsigned integer from the SSZ source. * * @param source The SSZ encoded bytes. * @return An unsigned int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for an 8-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun decodeUInt32(source: Bytes) = decodeUInt(source, 32) /** * Read a 64-bit unsigned integer from the SSZ source. * * @param source The SSZ encoded bytes. * @return An unsigned long. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for an 8-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun decodeUInt64(source: Bytes) = decodeULong(source, 64) } cava-0.6.0/ssz/src/main/kotlin/net/consensys/cava/ssz/experimental/SSZReader.kt000066400000000000000000000503511341750772100274760ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz.experimental import net.consensys.cava.bytes.Bytes import net.consensys.cava.ssz.EndOfSSZException import net.consensys.cava.ssz.InvalidSSZTypeException import net.consensys.cava.units.bigints.UInt256 import java.math.BigInteger import java.nio.charset.StandardCharsets.UTF_8 /** * A reader for consuming values from an SSZ encoded source. */ // Does not extend net.consensys.cava.ssz.SSZReader (unlike SSZWriter) as the return types vary for the UInt methods. @ExperimentalUnsignedTypes interface SSZReader { /** * Read bytes from the SSZ source. * * @param limit The maximum number of bytes to read. * @return The bytes for the next value. * @throws InvalidSSZTypeException If the next SSZ value is not a byte array, or would exceed the limit. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readBytes(limit: Int): Bytes /** * Read a byte array from the SSZ source. * * @param limit The maximum number of bytes to read. * @return The byte array for the next value. * @throws InvalidSSZTypeException If the next SSZ value is not a byte array, or would exceed the limit. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readByteArray(limit: Int = Integer.MAX_VALUE): ByteArray = readBytes(limit).toArrayUnsafe() /** * Read a string value from the SSZ source. * * @param limit The maximum number of bytes to read. * @return A string. * @throws InvalidSSZTypeException If the next SSZ value is not a byte array, or would exceed the limit. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readString(limit: Int): String = String(readByteArray(limit), UTF_8) /** * Read a two's-compliment int value from the SSZ source. * * @param bitLength The bit length of the integer to read (a multiple of 8). * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length, or the decoded * value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readInt(bitLength: Int): Int /** * Read a two's-compliment long value from the SSZ source. * * @param bitLength The bit length of the integer to read (a multiple of 8). * @return A long. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length, or the decoded * value was too large to fit into a long. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readLong(bitLength: Int): Long /** * Read a big integer value from the SSZ source. * * @param bitLength The bit length of the integer to read (a multiple of 8). * @return A big integer. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readBigInteger(bitLength: Int): BigInteger /** * Read an 8-bit two's-compliment integer from the SSZ source. * * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for an 8-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readInt8(): Int = readInt(8) /** * Read a 16-bit two's-compliment integer from the SSZ source. * * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 16-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readInt16(): Int = readInt(16) /** * Read a 32-bit two's-compliment integer from the SSZ source. * * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 32-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readInt32(): Int = readInt(32) /** * Read an 64-bit two's-compliment integer from the SSZ source. * * @return A long. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 64-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readInt64(): Long = readLong(64) /** * Read an unsigned int value from the SSZ source. * * @param bitLength The bit length of the integer to read (a multiple of 8). * @return An unsigned int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length, or the decoded * value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readUInt(bitLength: Int): UInt = readInt(bitLength).toUInt() /** * Read an unsigned long value from the SSZ source. * * @param bitLength The bit length of the integer to read (a multiple of 8). * @return An unsigned long. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length, or the decoded * value was too large to fit into a long. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readULong(bitLength: Int): ULong = readLong(bitLength).toULong() /** * Read an unsigned big integer value from the SSZ source. * * @param bitLength The bit length of the integer to read (a multiple of 8). * @return A big integer. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for the desired bit length. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readUnsignedBigInteger(bitLength: Int): BigInteger /** * Read an 8-bit unsigned integer from the SSZ source. * * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for an 8-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readUInt8(): UInt = readUInt(8) /** * Read a 16-bit unsigned integer from the SSZ source. * * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 16-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readUInt16(): UInt = readUInt(16) /** * Read a 32-bit unsigned integer from the SSZ source. * * @return An int. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 32-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readUInt32(): ULong = readULong(32) /** * Read an 64-bit unsigned integer from the SSZ source. * * @return A long. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 64-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readUInt64(): ULong = readULong(64) /** * Read a [UInt256] from the SSZ source. * * @return A [UInt256]. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 256-bit int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readUInt256(): UInt256 /** * Read a boolean from the SSZ source. * * @return A boolean. * @throws InvalidSSZTypeException If the decoded value is not a boolean. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readBoolean(): Boolean = when (readInt(8)) { 0 -> false 1 -> true else -> throw InvalidSSZTypeException("decoded value is not a boolean") } /** * Read a 20-byte address from the SSZ source. * * @return The bytes of the Address. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 20-byte address. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readAddress(): Bytes /** * Read a hash from the SSZ source. * * @param hashLength The length of the hash (in bytes). * @return The bytes of the hash. * @throws InvalidSSZTypeException If there are insufficient encoded bytes for a 32-byte hash. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readHash(hashLength: Int): Bytes /** * Read a list of [Bytes] from the SSZ source. * * Note: prefer to use [.readBytesList] instead, especially when reading untrusted data. * * @return A list of [Bytes]. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a byte array, or * any byte array is too large (greater than 2^32 bytes). * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readBytesList(): List = readBytesList(Integer.MAX_VALUE) /** * Read a list of [Bytes] from the SSZ source. * * @param limit The maximum number of bytes to read for each list element. * @return A list of [Bytes]. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a byte array, or * the size of any byte array would exceed the limit. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readBytesList(limit: Int): List /** * Read a list of byte arrays from the SSZ source. * * Note: prefer to use [.readByteArrayList] instead, especially when reading untrusted data. * * @return A list of byte arrays. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a byte array, or * any byte array is too large (greater than 2^32 bytes). * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readByteArrayList(): List = readByteArrayList(Integer.MAX_VALUE) /** * Read a list of byte arrays from the SSZ source. * * @param limit The maximum number of bytes to read for each list element. * @return A list of byte arrays. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a byte array, or * the size of any byte array would exceed the limit. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readByteArrayList(limit: Int): List /** * Read a list of strings from the SSZ source. * * Note: prefer to use [.readStringList] instead, especially when reading untrusted data. * * @return A list of strings. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a string, or any * string is too large (greater than 2^32 bytes). * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readStringList(): List = readStringList(Integer.MAX_VALUE) /** * Read a list of strings from the SSZ source. * * @param limit The maximum number of bytes to read for each list element. * @return A list of strings. * @throws InvalidSSZTypeException If the next SSZ value is not a list, any value in the list is not a string, or the * size of any string would exceed the limit. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readStringList(limit: Int): List /** * Read a list of two's-compliment int values from the SSZ source. * * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readIntList(bitLength: Int): List /** * Read a list of two's-compliment long int values from the SSZ source. * * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of longs. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into a long. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readLongIntList(bitLength: Int): List /** * Read a list of two's-compliment big integer values from the SSZ source. * * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of big integers. * @throws InvalidSSZTypeException If the next SSZ value is not a list, or there are insufficient encoded bytes for * the desired bit length or any value in the list. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readBigIntegerList(bitLength: Int): List /** * Read a list of 8-bit two's-compliment int values from the SSZ source. * * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readInt8List(): List = readIntList(8) /** * Read a list of 16-bit two's-compliment int values from the SSZ source. * * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readInt16List(): List = readIntList(16) /** * Read a list of 32-bit two's-compliment int values from the SSZ source. * * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readInt32List(): List = readIntList(32) /** * Read a list of 64-bit two's-compliment int values from the SSZ source. * * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readInt64List(): List = readLongIntList(64) /** * Read a list of unsigned int values from the SSZ source. * * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readUIntList(bitLength: Int): List { // encoding is the same for unsigned return readIntList(bitLength).map { i -> i.toUInt() } } /** * Read a list of unsigned long int values from the SSZ source. * * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of longs. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into a long. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readULongIntList(bitLength: Int): List { // encoding is the same for unsigned return readLongIntList(bitLength).map { i -> i.toULong() } } /** * Read a list of unsigned big integer values from the SSZ source. * * @param bitLength The bit length of the integers to read (a multiple of 8). * @return A list of big integers. * @throws InvalidSSZTypeException If the next SSZ value is not a list, or there are insufficient encoded bytes for * the desired bit length or any value in the list. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readUnsignedBigIntegerList(bitLength: Int): List /** * Read a list of 8-bit unsigned int values from the SSZ source. * * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readUInt8List(): List = readUIntList(8) /** * Read a list of 16-bit unsigned int values from the SSZ source. * * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readUInt16List(): List = readUIntList(16) /** * Read a list of 32-bit unsigned int values from the SSZ source. * * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readUInt32List(): List = readULongIntList(32) /** * Read a list of 64-bit unsigned int values from the SSZ source. * * @return A list of ints. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readUInt64List(): List = readULongIntList(64) /** * Read a list of 256-bit unsigned int values from the SSZ source. * * @return A list of [UInt256]. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for the * desired bit length or any value in the list, or any decoded value was too large to fit into an int. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readUInt256List(): List /** * Read a list of 20-byte addresses from the SSZ source. * * @return A list of 20-byte addresses. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for any * address in the list. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readAddressList(): List /** * Read a list of hashes from the SSZ source. * * @param hashLength The length of the hash (in bytes). * @return A list of 32-byte hashes. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for any * hash in the list. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readHashList(hashLength: Int): List /** * Read a list of booleans from the SSZ source. * * @return A list of booleans. * @throws InvalidSSZTypeException If the next SSZ value is not a list, there are insufficient encoded bytes for all * the booleans in the list. * @throws EndOfSSZException If there are no more SSZ values to read. */ fun readBooleanList(): List /** * Check if all values have been read. * * @return `true` if all values have been read. */ val isComplete: Boolean } cava-0.6.0/ssz/src/main/kotlin/net/consensys/cava/ssz/experimental/SSZWriter.kt000066400000000000000000000257651341750772100275630ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz.experimental import net.consensys.cava.bytes.Bytes import net.consensys.cava.ssz.SSZ import net.consensys.cava.units.bigints.UInt256 import java.math.BigInteger @ExperimentalUnsignedTypes interface SSZWriter { /** * Append an already SSZ encoded value. * * Note that this method **may not** validate that `value` is a valid SSZ sequence. Appending an invalid SSZ * sequence will cause the entire SSZ encoding produced by this writer to also be invalid. * * @param value The SSZ encoded bytes to append. */ fun writeSSZ(value: Bytes) /** * Append an already SSZ encoded value. * * Note that this method **may not** validate that `value` is a valid SSZ sequence. Appending an invalid SSZ * sequence will cause the entire SSZ encoding produced by this writer to also be invalid. * * @param value The SSZ encoded bytes to append. */ fun writeSSZ(value: ByteArray) = writeSSZ(Bytes.wrap(value)) /** * Encode a [Bytes] value to SSZ. * * @param value The byte array to encode. */ fun writeBytes(value: Bytes) /** * Encode a byte array to SSZ. * * @param value The byte array to encode. */ fun writeBytes(value: ByteArray) /** * Write a string to the output. * * @param str The string to write. */ fun writeString(str: String) /** * Write a two's-compliment integer to the output. * * @param value The integer to write. * @param bitLength The bit length of the integer value. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ fun writeInt(value: Int, bitLength: Int) /** * Write a two's-compliment long to the output. * * @param value The long value to write. * @param bitLength The bit length of the integer value. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ fun writeLong(value: Long, bitLength: Int) /** * Write a big integer to the output. * * @param value The integer to write. * @param bitLength The bit length of the integer value. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ fun writeBigInteger(value: BigInteger, bitLength: Int) { writeSSZ(SSZ.encodeBigIntegerToByteArray(value, bitLength)) } /** * Write an 8-bit two's-compliment integer to the output. * * @param value The integer to write. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ fun writeInt8(value: Int) { writeInt(value, 8) } /** * Write a 16-bit two's-compliment integer to the output. * * @param value The integer to write. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ fun writeInt16(value: Int) { writeInt(value, 16) } /** * Write a 32-bit two's-compliment integer to the output. * * @param value The integer to write. */ fun writeInt32(value: Int) { writeInt(value, 32) } /** * Write a 64-bit two's-compliment integer to the output. * * @param value The long to write. */ fun writeInt64(value: Long) { writeLong(value, 64) } /** * Write an unsigned integer to the output. * * @param value The integer to write. * @param bitLength The bit length of the integer value. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ fun writeUInt(value: UInt, bitLength: Int) /** * Write an unsigned long to the output. * * @param value The long value to write. * @param bitLength The bit length of the integer value. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ fun writeULong(value: ULong, bitLength: Int) /** * Write an 8-bit unsigned integer to the output. * * @param value The integer to write. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ fun writeUInt8(value: UInt) { writeUInt(value, 8) } /** * Write a 16-bit unsigned integer to the output. * * @param value The integer to write. * @throws IllegalArgumentException If the value is too large for the specified bit length. */ fun writeUInt16(value: UInt) { writeUInt(value, 16) } /** * Write a 32-bit unsigned integer to the output. * * @param value The integer to write. */ fun writeUInt32(value: UInt) { writeUInt(value, 32) } /** * Write a 64-bit unsigned integer to the output. * * @param value The long to write. */ fun writeUInt64(value: ULong) { writeULong(value, 64) } /** * Write a [UInt256] to the output. * * @param value The [UInt256] to write. */ fun writeUInt256(value: UInt256) { writeSSZ(SSZ.encodeUInt256(value)) } /** * Write a boolean to the output. * * @param value The boolean value. */ fun writeBoolean(value: Boolean) { writeSSZ(SSZ.encodeBoolean(value)) } /** * Write an address. * * @param address The address (must be exactly 20 bytes). * @throws IllegalArgumentException If `address.size != 20`. */ fun writeAddress(address: Bytes) { writeSSZ(SSZ.encodeAddress(address)) } /** * Write a hash. * * @param hash The hash. */ fun writeHash(hash: Bytes) { writeSSZ(SSZ.encodeHash(hash)) } /** * Write a list of bytes. * * @param elements The bytes to write as a list. */ fun writeBytesList(vararg elements: Bytes) /** * Write a list of strings, which must be of the same length * * @param elements The strings to write as a list. */ fun writeStringList(vararg elements: String) /** * Write a list of two's compliment integers. * * @param bitLength The bit length of the encoded integers (must be a multiple of 8). * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ fun writeIntList(bitLength: Int, vararg elements: Int) /** * Write a list of two's compliment long integers. * * @param bitLength The bit length of the encoded integers (must be a multiple of 8). * @param elements The long integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ fun writeLongIntList(bitLength: Int, vararg elements: Long) /** * Write a list of big integers. * * @param bitLength The bit length of each integer. * @param elements The integers to write as a list. * @throws IllegalArgumentException if an integer cannot be stored in the number of bytes provided */ fun writeBigIntegerList(bitLength: Int, vararg elements: BigInteger) /** * Write a list of 8-bit two's compliment integers. * * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ fun writeInt8List(vararg elements: Int) { writeIntList(8, *elements) } /** * Write a list of 16-bit two's compliment integers. * * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ fun writeInt16List(vararg elements: Int) { writeIntList(16, *elements) } /** * Write a list of 32-bit two's compliment integers. * * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ fun writeInt32List(vararg elements: Int) { writeIntList(32, *elements) } /** * Write a list of 64-bit two's compliment integers. * * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ fun writeInt64List(vararg elements: Int) { writeIntList(64, *elements) } /** * Write a list of unsigned integers. * * @param bitLength The bit length of the encoded integers (must be a multiple of 8). * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ fun writeUIntList(bitLength: Int, vararg elements: UInt) /** * Write a list of unsigned long integers. * * @param bitLength The bit length of the encoded integers (must be a multiple of 8). * @param elements The long integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ fun writeULongIntList(bitLength: Int, vararg elements: ULong) /** * Write a list of 8-bit unsigned integers. * * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ fun writeUInt8List(vararg elements: UInt) { writeUIntList(8, *elements) } /** * Write a list of 16-bit unsigned integers. * * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ fun writeUInt16List(vararg elements: UInt) { writeUIntList(16, *elements) } /** * Write a list of 32-bit unsigned integers. * * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ fun writeUInt32List(vararg elements: ULong) { writeULongIntList(32, *elements) } /** * Write a list of 64-bit unsigned integers. * * @param elements The integers to write as a list. * @throws IllegalArgumentException If any values are too large for the specified bit length. */ fun writeUInt64List(vararg elements: ULong) { writeULongIntList(64, *elements) } /** * Write a list of unsigned 256-bit integers. * * @param elements The integers to write as a list. */ fun writeUInt256List(vararg elements: UInt256) /** * Write a list of hashes. * * @param elements The hashes to write as a list. */ fun writeHashList(vararg elements: Bytes) /** * Write a list of addresses. * * @param elements The addresses to write as a list. * @throws IllegalArgumentException If any `address.size != 20`. */ fun writeAddressList(vararg elements: Bytes) /** * Write a list of booleans. * * @param elements The booleans to write as a list. */ fun writeBooleanList(vararg elements: Boolean) } cava-0.6.0/ssz/src/test/000077500000000000000000000000001341750772100150235ustar00rootroot00000000000000cava-0.6.0/ssz/src/test/java/000077500000000000000000000000001341750772100157445ustar00rootroot00000000000000cava-0.6.0/ssz/src/test/java/net/000077500000000000000000000000001341750772100165325ustar00rootroot00000000000000cava-0.6.0/ssz/src/test/java/net/consensys/000077500000000000000000000000001341750772100205565ustar00rootroot00000000000000cava-0.6.0/ssz/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100214705ustar00rootroot00000000000000cava-0.6.0/ssz/src/test/java/net/consensys/cava/ssz/000077500000000000000000000000001341750772100223075ustar00rootroot00000000000000cava-0.6.0/ssz/src/test/java/net/consensys/cava/ssz/ByteBufferWriterTest.java000066400000000000000000000107761341750772100272570ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz; import static java.nio.charset.StandardCharsets.UTF_8; import static net.consensys.cava.bytes.Bytes.fromHexString; import static org.junit.jupiter.api.Assertions.assertEquals; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.units.bigints.UInt256; import java.math.BigInteger; import java.nio.ByteBuffer; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; class ByteBufferWriterTest { @ParameterizedTest @CsvSource({"000003e8, 1000", "000186a0, 100000"}) void shouldWriteSmallIntegers(String expectedHex, int value) { ByteBuffer buffer = ByteBuffer.allocate(64); SSZ.encodeTo(buffer, writer -> writer.writeInt(value, 32)); buffer.flip(); assertEquals(fromHexString(expectedHex), Bytes.wrapByteBuffer(buffer)); } @Test void shouldWriteLongIntegers() { ByteBuffer buffer = ByteBuffer.allocate(64); SSZ.encodeTo(buffer, writer -> writer.writeLong(100000L, 24)); buffer.flip(); assertEquals(fromHexString("0186a0"), Bytes.wrapByteBuffer(buffer)); } @Test void shouldWriteUInt256Integers() { ByteBuffer buffer = ByteBuffer.allocate(64); SSZ.encodeTo(buffer, writer -> writer.writeUInt256(UInt256.valueOf(100000L))); buffer.flip(); assertEquals( fromHexString("00000000000000000000000000000000000000000000000000000000000186A0"), Bytes.wrapByteBuffer(buffer)); buffer.clear(); SSZ.encodeTo( buffer, writer -> writer .writeUInt256(UInt256.fromHexString("0x0400000000000000000000000000000000000000000000000000f100000000ab"))); buffer.flip(); assertEquals( fromHexString("0400000000000000000000000000000000000000000000000000f100000000ab"), Bytes.wrapByteBuffer(buffer)); } @Test void shouldWriteBigIntegers() { ByteBuffer buffer = ByteBuffer.allocate(64); SSZ.encodeTo(buffer, writer -> writer.writeBigInteger(BigInteger.valueOf(100000), 24)); buffer.flip(); assertEquals(fromHexString("0186a0"), Bytes.wrapByteBuffer(buffer)); buffer.clear(); SSZ.encodeTo(buffer, writer -> writer.writeBigInteger(BigInteger.valueOf(127).pow(16), 112)); buffer.flip(); assertEquals(fromHexString("e1ceefa5bbd9ed1c97f17a1df801"), Bytes.wrapByteBuffer(buffer)); } @Test void shouldWriteEmptyStrings() { ByteBuffer buffer = ByteBuffer.allocate(64); SSZ.encodeTo(buffer, writer -> writer.writeString("")); buffer.flip(); assertEquals(fromHexString("00000000"), Bytes.wrapByteBuffer(buffer)); } @Test void shouldWriteOneCharactersStrings() { ByteBuffer buffer = ByteBuffer.allocate(64); SSZ.encodeTo(buffer, writer -> writer.writeString("d")); buffer.flip(); assertEquals(fromHexString("0000000164"), Bytes.wrapByteBuffer(buffer)); } @Test void shouldWriteStrings() { ByteBuffer buffer = ByteBuffer.allocate(64); SSZ.encodeTo(buffer, writer -> writer.writeString("dog")); buffer.flip(); assertEquals(fromHexString("00000003646f67"), Bytes.wrapByteBuffer(buffer)); } @Test void shouldWriteShortLists() { String[] strings = new String[] {"asdf", "qwer", "zxcv", "asdf", "qwer", "zxcv", "asdf", "qwer", "zxcv", "asdf", "qwer"}; ByteBuffer buffer = ByteBuffer.allocate(256); SSZ.encodeTo(buffer, w -> w.writeStringList(strings)); buffer.flip(); assertEquals( fromHexString( "0000005800000004617364660000000471776572000000047A78637600000004617364660000000471776572000000047A78637600000004617364660000000471776572000000047A78637600000004617364660000000471776572"), Bytes.wrapByteBuffer(buffer)); } @Test void shouldWritePreviouslyEncodedValues() { ByteBuffer buffer = ByteBuffer.allocate(64); SSZ.encodeTo(buffer, writer -> writer.writeSSZ(SSZ.encodeByteArray("abc".getBytes(UTF_8)))); buffer.flip(); assertEquals("abc", SSZ.decodeString(Bytes.wrapByteBuffer(buffer))); } } cava-0.6.0/ssz/src/test/java/net/consensys/cava/ssz/BytesSSZReaderTest.java000066400000000000000000000200561341750772100266260ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz; import static net.consensys.cava.bytes.Bytes.fromHexString; import static org.junit.jupiter.api.Assertions.*; import net.consensys.cava.bytes.Bytes; import java.math.BigInteger; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; class BytesSSZReaderTest { private static final Bytes SHORT_LIST = fromHexString( "0000002c00000004617364660000000471776572000000047A78637600000004617364660000000471776572000000047A78637600000004617364660000000471776572000000047A78637600000004617364660000000471776572"); private static class SomeObject { private final String name; private final int number; private final BigInteger longNumber; SomeObject(String name, int number, BigInteger longNumber) { this.name = name; this.number = number; this.longNumber = longNumber; } } private static class AnotherObject { private final long number1; private final long number2; public AnotherObject(long number1, long number2) { this.number1 = number1; this.number2 = number2; } } @Test void shouldParseFullObjects() { Bytes bytes = fromHexString("0x00000003426F62040000000000000000000000000000000000000000000000000000011F71B70768"); SomeObject readObject = SSZ.decode(bytes, r -> new SomeObject(r.readString(), r.readInt8(), r.readBigInteger(256))); assertEquals("Bob", readObject.name); assertEquals(4, readObject.number); assertEquals(BigInteger.valueOf(1234563434344L), readObject.longNumber); } @ParameterizedTest @CsvSource({ "00, 0", "01, 1", "10, 16", "4f, 79", "7f, 127", "0080, 128", "03e8, 1000", "000186a0, 100000", "0000000186a0, 100000"}) void shouldReadIntegers(String hex, int value) { assertTrue(SSZ.decode(fromHexString(hex), reader -> { assertEquals(value, reader.readInt(hex.length() * 4)); return true; })); } /** * Related to the bug when {@link BytesSSZReader#readLong(int)} calculates lead zeroes from beginning of whole content * instead of the current value */ @Test void shouldCorrectlyParseLongs() { Bytes bytes = fromHexString("000000000000007b" + "00007fffffffffff"); AnotherObject readObject = SSZ.decode(bytes, r -> new AnotherObject(r.readLong(64), r.readLong(64))); assertEquals(123, readObject.number1); assertEquals(140737488355327L, readObject.number2); } @Test void shouldThrowWhenReadingOversizedInt() { InvalidSSZTypeException ex = assertThrows(InvalidSSZTypeException.class, () -> { SSZ.decode(fromHexString("1122334455667788"), r -> r.readInt(64)); }); assertEquals("decoded integer is too large for an int", ex.getMessage()); } @ParameterizedTest // @formatter:off @CsvSource({ "00000000, ''", "0000000100, '\u0000'", "0000000101, '\u0001'", "000000017f, '\u007F'", "00000003646f67, dog", "000000374c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c69" + ", 'Lorem ipsum dolor sit amet, consectetur adipisicing eli'", "000000384c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c6974" + ", 'Lorem ipsum dolor sit amet, consectetur adipisicing elit'", "000004004c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20437572616269747572206d6175726973206d61676e612c20737573636970697420736564207665686963756c61206e6f6e2c20696163756c697320666175636962757320746f72746f722e2050726f696e20737573636970697420756c74726963696573206d616c6573756164612e204475697320746f72746f7220656c69742c2064696374756d2071756973207472697374697175652065752c20756c7472696365732061742072697375732e204d6f72626920612065737420696d70657264696574206d6920756c6c616d636f7270657220616c6971756574207375736369706974206e6563206c6f72656d2e2041656e65616e2071756973206c656f206d6f6c6c69732c2076756c70757461746520656c6974207661726975732c20636f6e73657175617420656e696d2e204e756c6c6120756c74726963657320747572706973206a7573746f2c20657420706f73756572652075726e6120636f6e7365637465747572206e65632e2050726f696e206e6f6e20636f6e76616c6c6973206d657475732e20446f6e65632074656d706f7220697073756d20696e206d617572697320636f6e67756520736f6c6c696369747564696e2e20566573746962756c756d20616e746520697073756d207072696d697320696e206661756369627573206f726369206c756374757320657420756c74726963657320706f737565726520637562696c69612043757261653b2053757370656e646973736520636f6e76616c6c69732073656d2076656c206d617373612066617563696275732c2065676574206c6163696e6961206c616375732074656d706f722e204e756c6c61207175697320756c747269636965732070757275732e2050726f696e20617563746f722072686f6e637573206e69626820636f6e64696d656e74756d206d6f6c6c69732e20416c697175616d20636f6e73657175617420656e696d206174206d65747573206c75637475732c206120656c656966656e6420707572757320656765737461732e20437572616269747572206174206e696268206d657475732e204e616d20626962656e64756d2c206e6571756520617420617563746f72207472697374697175652c206c6f72656d206c696265726f20616c697175657420617263752c206e6f6e20696e74657264756d2074656c6c7573206c65637475732073697420616d65742065726f732e20437261732072686f6e6375732c206d65747573206163206f726e617265206375727375732c20646f6c6f72206a7573746f20756c747269636573206d657475732c20617420756c6c616d636f7270657220766f6c7574706174" + ", 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur mauris magna, suscipit sed vehicula non, iaculis faucibus tortor. Proin suscipit ultricies malesuada. Duis tortor elit, dictum quis tristique eu, ultrices at risus. Morbi a est imperdiet mi ullamcorper aliquet suscipit nec lorem. Aenean quis leo mollis, vulputate elit varius, consequat enim. Nulla ultrices turpis justo, et posuere urna consectetur nec. Proin non convallis metus. Donec tempor ipsum in mauris congue sollicitudin. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse convallis sem vel massa faucibus, eget lacinia lacus tempor. Nulla quis ultricies purus. Proin auctor rhoncus nibh condimentum mollis. Aliquam consequat enim at metus luctus, a eleifend purus egestas. Curabitur at nibh metus. Nam bibendum, neque at auctor tristique, lorem libero aliquet arcu, non interdum tellus lectus sit amet eros. Cras rhoncus, metus ac ornare cursus, dolor justo ultrices metus, at ullamcorper volutpat'" }) // @formatter:on void shouldReadStrings(String hex, String value) { assertTrue(SSZ.decode(fromHexString(hex), reader -> { assertEquals(value, reader.readString()); return true; })); } @Test void shouldThrowWhenInputExhausted() { EndOfSSZException ex = assertThrows(EndOfSSZException.class, () -> SSZ.decode(Bytes.EMPTY, reader -> reader.readInt(16))); assertEquals("End of SSZ source reached", ex.getMessage()); } @Test void shouldThrowWheSourceIsTruncated() { InvalidSSZTypeException ex = assertThrows( InvalidSSZTypeException.class, () -> SSZ.decode(fromHexString("0000000f830186"), SSZReader::readBytes)); assertEquals("SSZ encoded data has insufficient bytes for decoded byte array length", ex.getMessage()); } @Test void shouldReadShortList() { List expected = Arrays.asList("asdf", "qwer", "zxcv", "asdf", "qwer", "zxcv", "asdf", "qwer", "zxcv", "asdf", "qwer"); List result = SSZ.decodeStringList(SHORT_LIST); assertEquals(expected, result); } } cava-0.6.0/ssz/src/test/java/net/consensys/cava/ssz/BytesSSZWriterTest.java000066400000000000000000000306371341750772100267060ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz; import static java.nio.charset.StandardCharsets.UTF_8; import static net.consensys.cava.bytes.Bytes.fromHexString; import static org.junit.jupiter.api.Assertions.*; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import net.consensys.cava.units.bigints.UInt256; import java.math.BigInteger; import com.google.common.base.Charsets; import org.junit.jupiter.api.Test; class BytesSSZWriterTest { private static class SomeObject { private final String name; private final int number; private final BigInteger longNumber; SomeObject(String name, int number, BigInteger longNumber) { this.name = name; this.number = number; this.longNumber = longNumber; } } @Test void shouldWriteFullObjects() { SomeObject bob = new SomeObject("Bob", 4, BigInteger.valueOf(1234563434344L)); Bytes bytes = SSZ.encode(writer -> { writer.writeString(bob.name); writer.writeInt(bob.number, 8); writer.writeBigInteger(bob.longNumber, 256); }); assertTrue(SSZ.decode(bytes, reader -> { assertEquals("Bob", reader.readString()); assertEquals(4, reader.readInt(8)); assertEquals(BigInteger.valueOf(1234563434344L), reader.readBigInteger(256)); return true; })); } @Test void shouldWriteEmptyStrings() { assertEquals(fromHexString("00000000"), SSZ.encode(writer -> writer.writeString(""))); } @Test void shouldWriteOneCharactersStrings() { assertEquals(fromHexString("0000000164"), SSZ.encode(writer -> writer.writeString("d"))); } @Test void shouldWriteStrings() { assertEquals(fromHexString("00000003646f67"), SSZ.encode(writer -> writer.writeString("dog"))); } @Test void shouldWriteSignedIntegers() { assertEquals(fromHexString("00"), SSZ.encode(writer -> writer.writeInt(0, 8))); assertEquals(fromHexString("00"), SSZ.encode(writer -> writer.writeInt8(0))); assertEquals(fromHexString("0000"), SSZ.encode(writer -> writer.writeInt(0, 16))); assertEquals(fromHexString("0000"), SSZ.encode(writer -> writer.writeInt16(0))); assertEquals(fromHexString("000000"), SSZ.encode(writer -> writer.writeInt(0, 24))); assertEquals(fromHexString("00000000"), SSZ.encode(writer -> writer.writeInt(0, 32))); assertEquals(fromHexString("00000000"), SSZ.encode(writer -> writer.writeInt32(0))); assertEquals(fromHexString("01"), SSZ.encode(writer -> writer.writeInt(1, 8))); assertEquals(fromHexString("01"), SSZ.encode(writer -> writer.writeInt8(1))); assertEquals(fromHexString("0001"), SSZ.encode(writer -> writer.writeInt(1, 16))); assertEquals(fromHexString("0001"), SSZ.encode(writer -> writer.writeInt16(1))); assertEquals(fromHexString("00000001"), SSZ.encode(writer -> writer.writeInt(1, 32))); assertEquals(fromHexString("00000001"), SSZ.encode(writer -> writer.writeInt32(1))); assertEquals(fromHexString("0f"), SSZ.encode(writer -> writer.writeInt8(15))); assertEquals(fromHexString("000f"), SSZ.encode(writer -> writer.writeInt16(15))); assertEquals(fromHexString("03e8"), SSZ.encode(writer -> writer.writeInt16(1000))); assertEquals(fromHexString("0400"), SSZ.encode(writer -> writer.writeInt16(1024))); assertEquals(fromHexString("0186A0"), SSZ.encode(writer -> writer.writeInt(100000, 24))); assertEquals(fromHexString("FF"), SSZ.encode(writer -> writer.writeInt(-1, 8))); assertEquals(fromHexString("FFFF"), SSZ.encode(writer -> writer.writeInt(-1, 16))); assertEquals(fromHexString("80"), SSZ.encode(writer -> writer.writeInt(-128, 8))); assertEquals(fromHexString("80"), SSZ.encode(writer -> writer.writeInt8(-128))); assertEquals(fromHexString("8000"), SSZ.encode(writer -> writer.writeInt(-32768, 16))); assertEquals(fromHexString("8000"), SSZ.encode(writer -> writer.writeInt16(-32768))); } @Test void shouldWriteSignedLongs() { assertEquals(fromHexString("00"), SSZ.encode(writer -> writer.writeLong(0, 8))); assertEquals(fromHexString("01"), SSZ.encode(writer -> writer.writeLong(1, 8))); assertEquals(fromHexString("0f"), SSZ.encode(writer -> writer.writeLong(15, 8))); assertEquals(fromHexString("03e8"), SSZ.encode(writer -> writer.writeLong(1000, 16))); assertEquals(fromHexString("0400"), SSZ.encode(writer -> writer.writeLong(1024, 16))); assertEquals(fromHexString("0186A0"), SSZ.encode(writer -> writer.writeLong(100000L, 24))); assertEquals(fromHexString("000186A0"), SSZ.encode(writer -> writer.writeLong(100000L, 32))); assertEquals(fromHexString("00000000000186A0"), SSZ.encode(writer -> writer.writeLong(100000L, 64))); assertEquals(fromHexString("00000000000186A0"), SSZ.encode(writer -> writer.writeInt64(100000L))); assertEquals(fromHexString("FF"), SSZ.encode(writer -> writer.writeLong(-1, 8))); assertEquals(fromHexString("FFFF"), SSZ.encode(writer -> writer.writeLong(-1, 16))); assertEquals(fromHexString("80"), SSZ.encode(writer -> writer.writeLong(-128, 8))); assertEquals(fromHexString("8000"), SSZ.encode(writer -> writer.writeLong(-32768, 16))); assertEquals(fromHexString("8000000000000000"), SSZ.encode(writer -> writer.writeInt64(-9223372036854775808L))); } @Test void shouldWriteSignedBigIntegers() { assertEquals(fromHexString("0186A0"), SSZ.encode(writer -> writer.writeBigInteger(BigInteger.valueOf(100000), 24))); assertEquals(fromHexString("EB16"), SSZ.encode(writer -> writer.writeBigInteger(BigInteger.valueOf(-5354), 16))); assertEquals(fromHexString("8000"), SSZ.encode(writer -> writer.writeBigInteger(BigInteger.valueOf(-32768), 16))); assertEquals( fromHexString("E1CEEFA5BBD9ED1C97F17A1DF801"), SSZ.encode(writer -> writer.writeBigInteger(BigInteger.valueOf(127).pow(16), 112))); } @Test void shouldWriteUnsignedIntegers() { assertEquals(fromHexString("00"), SSZ.encode(writer -> writer.writeUInt(0, 8))); assertEquals(fromHexString("00"), SSZ.encode(writer -> writer.writeUInt8(0))); assertEquals(fromHexString("0000"), SSZ.encode(writer -> writer.writeUInt(0, 16))); assertEquals(fromHexString("0000"), SSZ.encode(writer -> writer.writeUInt16(0))); assertEquals(fromHexString("000000"), SSZ.encode(writer -> writer.writeUInt(0, 24))); assertEquals(fromHexString("00000000"), SSZ.encode(writer -> writer.writeUInt(0, 32))); assertEquals(fromHexString("00000000"), SSZ.encode(writer -> writer.writeUInt32(0))); assertEquals(fromHexString("01"), SSZ.encode(writer -> writer.writeUInt(1, 8))); assertEquals(fromHexString("01"), SSZ.encode(writer -> writer.writeUInt8(1))); assertEquals(fromHexString("0001"), SSZ.encode(writer -> writer.writeUInt(1, 16))); assertEquals(fromHexString("0001"), SSZ.encode(writer -> writer.writeUInt16(1))); assertEquals(fromHexString("00000001"), SSZ.encode(writer -> writer.writeUInt(1, 32))); assertEquals(fromHexString("00000001"), SSZ.encode(writer -> writer.writeUInt32(1))); assertEquals(fromHexString("0f"), SSZ.encode(writer -> writer.writeUInt8(15))); assertEquals(fromHexString("000f"), SSZ.encode(writer -> writer.writeUInt16(15))); assertEquals(fromHexString("03e8"), SSZ.encode(writer -> writer.writeUInt16(1000))); assertEquals(fromHexString("0400"), SSZ.encode(writer -> writer.writeUInt16(1024))); assertEquals(fromHexString("0186A0"), SSZ.encode(writer -> writer.writeUInt(100000, 24))); assertEquals(fromHexString("FF"), SSZ.encode(writer -> writer.writeUInt(255, 8))); assertEquals(fromHexString("FFFF"), SSZ.encode(writer -> writer.writeUInt(65535, 16))); assertEquals(fromHexString("80"), SSZ.encode(writer -> writer.writeUInt(128, 8))); assertEquals(fromHexString("80"), SSZ.encode(writer -> writer.writeUInt8(128))); assertEquals(fromHexString("8000"), SSZ.encode(writer -> writer.writeUInt(32768, 16))); assertEquals(fromHexString("8000"), SSZ.encode(writer -> writer.writeUInt16(32768))); } @Test void shouldWriteUnsignedLongs() { assertEquals(fromHexString("00"), SSZ.encode(writer -> writer.writeULong(0, 8))); assertEquals(fromHexString("01"), SSZ.encode(writer -> writer.writeULong(1, 8))); assertEquals(fromHexString("0f"), SSZ.encode(writer -> writer.writeULong(15, 8))); assertEquals(fromHexString("03e8"), SSZ.encode(writer -> writer.writeULong(1000, 16))); assertEquals(fromHexString("0400"), SSZ.encode(writer -> writer.writeULong(1024, 16))); assertEquals(fromHexString("0186A0"), SSZ.encode(writer -> writer.writeULong(100000L, 24))); assertEquals(fromHexString("000186A0"), SSZ.encode(writer -> writer.writeULong(100000L, 32))); assertEquals(fromHexString("00000000000186A0"), SSZ.encode(writer -> writer.writeULong(100000L, 64))); assertEquals(fromHexString("00000000000186A0"), SSZ.encode(writer -> writer.writeUInt64(100000L))); assertEquals(fromHexString("FF"), SSZ.encode(writer -> writer.writeULong(255, 8))); assertEquals(fromHexString("FFFF"), SSZ.encode(writer -> writer.writeULong(65535, 16))); assertEquals(fromHexString("80"), SSZ.encode(writer -> writer.writeULong(128, 8))); assertEquals(fromHexString("8000"), SSZ.encode(writer -> writer.writeULong(32768, 16))); assertEquals(fromHexString("8000000000000000"), SSZ.encode(writer -> { writer.writeUInt64(Long.parseUnsignedLong("9223372036854775808")); })); } @Test void shouldWriteUInt256Integers() { assertEquals( fromHexString("0000000000000000000000000000000000000000000000000000000000000000"), SSZ.encode(writer -> writer.writeUInt256(UInt256.valueOf(0L)))); assertEquals( fromHexString("00000000000000000000000000000000000000000000000000000000000186a0"), SSZ.encode(writer -> writer.writeUInt256(UInt256.valueOf(100000L)))); assertEquals( fromHexString("0400000000000000000000000000000000000000000000000000f100000000ab"), SSZ.encode( writer -> writer.writeUInt256( UInt256.fromHexString("0x0400000000000000000000000000000000000000000000000000f100000000ab")))); } @Test void shouldWriteBooleans() { assertEquals(fromHexString("00"), SSZ.encode(writer -> writer.writeBoolean(false))); assertEquals(fromHexString("01"), SSZ.encode(writer -> writer.writeBoolean(true))); } @Test void shouldWriteAddresses() { assertEquals( fromHexString("8EE1CEEFA5BBD9ED1C978EE1CEEFA5BBD9ED1C97"), SSZ.encode(writer -> writer.writeAddress(Bytes.fromHexString("8EE1CEEFA5BBD9ED1C978EE1CEEFA5BBD9ED1C97")))); assertThrows( IllegalArgumentException.class, () -> SSZ.encode(writer -> writer.writeAddress(Bytes.fromHexString("beef")))); } @Test void shouldWriteHashes() { assertEquals( fromHexString("ED1C978EE1CEEFA5BBD9ED1C8EE1CEEFA5BBD9ED1C978EE1CEEFA5BBD9ED1C97"), SSZ.encode( writer -> writer .writeHash(Bytes32.fromHexString("ED1C978EE1CEEFA5BBD9ED1C8EE1CEEFA5BBD9ED1C978EE1CEEFA5BBD9ED1C97")))); assertThrows( IllegalArgumentException.class, () -> SSZ.encode(writer -> writer.writeAddress(Bytes.fromHexString("beef")))); } @Test void shouldWriteLists() { assertEquals(fromHexString("00000003030405"), SSZ.encodeIntList(8, 3, 4, 5)); } @Test void shouldWriteListsOfStrings() { assertEquals( fromHexString("0000001800000003626F62000000046A616E65000000056A616E6574"), SSZ.encodeStringList("bob", "jane", "janet")); } @Test void shouldWriteListsOfBytes() { assertEquals( fromHexString("0000001800000003626F62000000046A616E65000000056A616E6574"), SSZ.encodeBytesList( Bytes.wrap("bob".getBytes(Charsets.UTF_8)), Bytes.wrap("jane".getBytes(Charsets.UTF_8)), Bytes.wrap("janet".getBytes(Charsets.UTF_8)))); } @Test void shouldWritePreviouslyEncodedValues() { Bytes output = SSZ.encode(writer -> writer.writeSSZ(SSZ.encodeByteArray("abc".getBytes(UTF_8)))); assertEquals("abc", SSZ.decodeString(output)); } } cava-0.6.0/ssz/src/test/kotlin/000077500000000000000000000000001341750772100163235ustar00rootroot00000000000000cava-0.6.0/ssz/src/test/kotlin/net/000077500000000000000000000000001341750772100171115ustar00rootroot00000000000000cava-0.6.0/ssz/src/test/kotlin/net/consensys/000077500000000000000000000000001341750772100211355ustar00rootroot00000000000000cava-0.6.0/ssz/src/test/kotlin/net/consensys/cava/000077500000000000000000000000001341750772100220475ustar00rootroot00000000000000cava-0.6.0/ssz/src/test/kotlin/net/consensys/cava/ssz/000077500000000000000000000000001341750772100226665ustar00rootroot00000000000000cava-0.6.0/ssz/src/test/kotlin/net/consensys/cava/ssz/experimental/000077500000000000000000000000001341750772100253635ustar00rootroot00000000000000cava-0.6.0/ssz/src/test/kotlin/net/consensys/cava/ssz/experimental/SSZTest.kt000066400000000000000000000030741341750772100272460ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.ssz.experimental import net.consensys.cava.bytes.Bytes.fromHexString import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @ExperimentalUnsignedTypes class SSZTest { @Test fun shouldEncodeUnsigned() { assertEquals(fromHexString("0000"), SSZ.encodeUInt16(0.toUInt())) assertEquals(fromHexString("00000000"), SSZ.encodeUInt32(0.toUInt())) assertEquals(fromHexString("0000000000000000"), SSZ.encodeUInt64(0L.toULong())) assertEquals(fromHexString("0000000000000000"), SSZ.encodeUInt64(0L.toULong())) assertEquals(fromHexString("FFFF"), SSZ.encodeUInt16(65535.toUInt())) assertEquals(fromHexString("0000FFFF"), SSZ.encodeUInt32(65535.toUInt())) } @Test fun shouldWriteUnsigned() { assertEquals(fromHexString("FFFF"), SSZ.encode { w -> w.writeUInt16(65535.toUInt()) }) assertEquals(fromHexString("0000FFFF"), SSZ.encode { w -> w.writeUInt32(65535.toUInt()) }) } } cava-0.6.0/toml/000077500000000000000000000000001341750772100134115ustar00rootroot00000000000000cava-0.6.0/toml/README.md000066400000000000000000000035361341750772100146770ustar00rootroot00000000000000# Cava-Toml: TOML parser for Java/Kotlin (by ConsenSys) Cava-Toml is a complete TOML parser with the following attributes: * Supports the latest TOML specification version (0.5.0). * Provides detailed error reporting, including error position. * Performs error recovery, allowing parsing to continue after an error. It uses the [ANTLR](https://github.com/antlr/antlr4/) parser-generator and runtime library. ## Usage Parsing is straightforward: ``` Path source = Paths.get("/path/to/file.toml"); TomlParseResult result = Toml.parse(source); result.errors().forEach(error -> System.err.println(error.toString())); String value = result.getString("a. dotted . key"); ``` ## Getting Cava-Toml Cava-Toml is part of the [ConsenSys Cava](https://github.com/ConsenSys/cava) project, and is available whenever cava is included in a project. It can also be included independently, using the cava-toml jar, which is published to [ConsenSys bintray repository](https://bintray.com/consensys/consensys/cava) and linked to JCenter. With Maven: ``` net.consensys.cava cava-toml 0.5.0 ``` With Gradle: `compile 'net.consensys.cava:cava-toml:0.5.0'` ## Links - [GitHub project](https://github.com/consensys/cava) - [Online Java documentation](https://consensys.github.io/cava/docs/java/latest/net/consensys/cava/toml/package-summary.html) - [Online Kotlin documentation](https://consensys.github.io/cava/docs/kotlin/latest/cava/net.consensys.cava.toml/index.html) - [Issue tracker: Report a defect or feature request](https://github.com/google/cava/issues/new) - [StackOverflow: Ask "how-to" and "why-didn't-it-work" questions](https://stackoverflow.com/questions/ask?tags=cava+toml+java) - [cava-discuss: For open-ended questions and discussion](http://groups.google.com/group/cava-discuss) cava-0.6.0/toml/build.gradle000066400000000000000000000013441341750772100156720ustar00rootroot00000000000000description = 'A parser for Tom\'s Obvious, Minimal Language (TOML).' apply plugin: 'antlr' generateGrammarSource { outputDirectory file("${project.buildDir}/generated-src/antlr/main/net/consensys/cava/toml/internal") arguments << "-visitor" << "-long-messages" arguments << "-Xexact-output-dir" } javadoc { exclude '**/internal/**' } configurations { compile { extendsFrom = extendsFrom.findAll { it != configurations.antlr } } } dependencies { antlr 'org.antlr:antlr4' compile 'org.antlr:antlr4-runtime' compile 'com.google.code.findbugs:jsr305' testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/toml/src/000077500000000000000000000000001341750772100142005ustar00rootroot00000000000000cava-0.6.0/toml/src/main/000077500000000000000000000000001341750772100151245ustar00rootroot00000000000000cava-0.6.0/toml/src/main/antlr/000077500000000000000000000000001341750772100162445ustar00rootroot00000000000000cava-0.6.0/toml/src/main/antlr/net/000077500000000000000000000000001341750772100170325ustar00rootroot00000000000000cava-0.6.0/toml/src/main/antlr/net/consensys/000077500000000000000000000000001341750772100210565ustar00rootroot00000000000000cava-0.6.0/toml/src/main/antlr/net/consensys/cava/000077500000000000000000000000001341750772100217705ustar00rootroot00000000000000cava-0.6.0/toml/src/main/antlr/net/consensys/cava/toml/000077500000000000000000000000001341750772100227435ustar00rootroot00000000000000cava-0.6.0/toml/src/main/antlr/net/consensys/cava/toml/internal/000077500000000000000000000000001341750772100245575ustar00rootroot00000000000000cava-0.6.0/toml/src/main/antlr/net/consensys/cava/toml/internal/TomlLexer.g4000066400000000000000000000135471341750772100267400ustar00rootroot00000000000000lexer grammar TomlLexer; channels { COMMENTS, WHITESPACE } tokens { TripleQuotationMark, TripleApostrophe, StringChar, Comma } @header { package net.consensys.cava.toml.internal; } @members { private final IntegerStack arrayDepthStack = new IntegerStack(); private int arrayDepth = 0; private void resetArrayDepth() { arrayDepthStack.clear(); arrayDepth = 0; } private void pushArrayDepth() { arrayDepthStack.push(arrayDepth); arrayDepth = 0; } private void popArrayDepth() { arrayDepth = arrayDepthStack.pop(); } } fragment WSChar : [ \t]; fragment NL : '\r'? '\n'; fragment COMMENT : '#' (~'\n')*; fragment Alpha : [A-Za-z]; fragment Digit : [0-9]; fragment Digit1_9 : [1-9]; fragment Digit0_7 : [0-7]; fragment Digit0_1 : [0-1]; fragment HexDig : Digit | [A-Fa-f]; fragment UNQUOTED_KEY : (Alpha | Digit | '-' | '_')+; Dot : '.'; Equals : '=' { resetArrayDepth(); } -> pushMode(ValueMode); QuotationMark : '"' -> pushMode(BasicStringMode); Apostrophe : '\'' -> pushMode(LiteralStringMode); TableKeyStart : '['; TableKeyEnd : ']'; ArrayTableKeyStart : '[['; ArrayTableKeyEnd : ']]'; UnquotedKey : UNQUOTED_KEY; WS : WSChar+ -> channel(WHITESPACE); Comment : COMMENT -> channel(COMMENTS); NewLine : NL { setText(System.lineSeparator()); }; Error : .; mode KeyMode; KeyDot : '.' -> type(Dot); KeyQuotationMark : '"' -> type(QuotationMark), pushMode(BasicStringMode); KeyApostrophe : '\'' -> type(Apostrophe), pushMode(LiteralStringMode); KeyUnquotedKey : UNQUOTED_KEY -> type(UnquotedKey); KeyWS : WSChar+ -> type(WS), channel(WHITESPACE); KeyError : . -> type(Error); mode ValueMode; // Strings ValueQuotationMark : '"' -> type(QuotationMark), mode(BasicStringMode); ValueTripleQuotationMark : '"""' NL? -> type(TripleQuotationMark), mode(MLBasicStringMode); ValueApostrophe : '\'' -> type(Apostrophe), mode(LiteralStringMode); ValueTripleApostrophe : '\'\'\'' NL? -> type(TripleApostrophe), mode(MLLiteralStringMode); // Integers fragment DecInt : [-+]? (Digit | Digit1_9 ('_'? Digit)+); DecimalInteger : DecInt { "-:".indexOf(_input.LA(1)) < 0 }? -> popMode; HexInteger : '0x' HexDig ('_'? HexDig)* -> popMode; OctalInteger : '0o' Digit0_7 ('_'? Digit0_7)* -> popMode; BinaryInteger : '0b' Digit0_1 ('_'? Digit0_1)* -> popMode; // Float fragment Exp : [eE] DecInt; fragment Frac : '.' Digit ('_'? Digit)*; FloatingPoint : DecInt (Exp | Frac Exp?) -> popMode; FloatingPointInf: [-+]? 'inf' -> popMode; FloatingPointNaN : [-+]? 'nan' -> popMode; // Boolean TrueBoolean : 'true' -> popMode; FalseBoolean : 'false' -> popMode; // Date and Time ValueDateStart : Digit+ { "-:".indexOf(_input.LA(1)) >= 0 }? -> type(DateDigits), mode(DateMode); // Array ArrayStart : '[' { arrayDepth++; } -> pushMode(ValueMode); ArrayEnd : ']' { arrayDepth--; } -> popMode; ArrayComma : ',' { arrayDepth > 0 }? -> type(Comma), pushMode(ValueMode); // Table InlineTableStart : '{' { pushArrayDepth(); } -> mode(InlineTableMode); ValueWS : WSChar+ -> type(WS), channel(WHITESPACE); ValueComment : COMMENT -> type(Comment), channel(COMMENTS); ArrayNewLine: NL { arrayDepth > 0}? -> type(NewLine); ValueNewLine: NL { arrayDepth == 0}? -> type(NewLine), popMode; ValueError : . -> type(Error), popMode; mode BasicStringMode; BasicStringEnd : '"' -> type(QuotationMark), popMode; BasicStringUnescaped : ~[\u0000-\u001F"\\\u007F] -> type(StringChar); EscapeSequence : '\\' ~[\n] | '\\u' HexDig HexDig HexDig HexDig | '\\U' HexDig HexDig HexDig HexDig HexDig HexDig HexDig HexDig; BasicStringNewLine: NL { setText(System.lineSeparator()); } -> type(NewLine), popMode; BasicStringError : . -> type(Error), popMode; mode MLBasicStringMode; MLBasicStringEnd : '"""' -> type(TripleQuotationMark), popMode; MLBasicStringLineEnd : '\\' [ \t]* NL { setText(System.lineSeparator()); } -> type(NewLine); MLBasicStringUnescaped : ~[\u0000-\u001F\\\u007F] -> type(StringChar); MLBasicStringEscape : ('\\u' HexDig HexDig HexDig HexDig | '\\U' HexDig HexDig HexDig HexDig HexDig HexDig HexDig HexDig | '\\' .) -> type(EscapeSequence); MLBasicStringNewLine: NL { setText(System.lineSeparator()); } -> type(NewLine); MLBasicStringError : . -> type(Error), popMode; mode LiteralStringMode; LiteralStringEnd : '\'' -> type(Apostrophe), popMode; LiteralStringChar : ~[\u0000-\u0008\u000A-\u001F'\u007F] -> type(StringChar); LiteralStringNewLine: NL { setText(System.lineSeparator()); } -> type(NewLine), popMode; LiteralStringError : . -> type(Error), popMode; mode MLLiteralStringMode; MLLiteralStringEnd : '\'\'\'' -> type(TripleApostrophe), popMode; MLLiteralStringChar : ~[\u0000-\u0008\u000A-\u001F\u007F] -> type(StringChar); MLLiteralStringNewLine: NL { setText(System.lineSeparator()); } -> type(NewLine); MLLiteralStringError : . -> type(Error), popMode; mode DateMode; Dash : '-'; Plus : '+'; Colon : ':'; DateDot : '.' -> type(Dot); Z : 'Z'; TimeDelimiter : [Tt] | (' ' { _input.LA(1) >= '0' && _input.LA(1) <= '9' }?); DateDigits : Digit+; DateWS : WSChar+ -> type(WS), channel(WHITESPACE), popMode; DateComment : COMMENT -> type(Comment), channel(COMMENTS), popMode; DateNewLine: NL { setText(System.lineSeparator()); } -> type(NewLine), popMode; DateComma: ',' -> type(Comma), popMode; DateError : . -> type(Error), popMode; mode InlineTableMode; InlineTableEnd : '}' { popArrayDepth(); } -> popMode; InlineTableDot : '.' -> type(Dot); InlineTableEquals : '=' -> type(Equals), pushMode(ValueMode); InlineTableComma : ',' -> type(Comma); InlineTableQuotationMark : '"' -> type(QuotationMark), pushMode(BasicStringMode); InlineTableApostrophe : '\'' -> type(Apostrophe), pushMode(LiteralStringMode); InlineTableUnquotedKey : UNQUOTED_KEY -> type(UnquotedKey); InlineTableWS : WSChar+ -> type(WS), channel(WHITESPACE); InlineTableComment : COMMENT -> type(Comment), channel(COMMENTS), popMode; InlineTableNewLine : NL { setText(System.lineSeparator()); } -> type(NewLine), popMode; InlineTableError : . -> type(Error), popMode; cava-0.6.0/toml/src/main/antlr/net/consensys/cava/toml/internal/TomlParser.g4000066400000000000000000000053511341750772100271070ustar00rootroot00000000000000parser grammar TomlParser; options { tokenVocab=TomlLexer; } @header { package net.consensys.cava.toml.internal; } // Document parser toml : NewLine* (expression (NewLine+ expression)* NewLine*)? EOF; expression : keyval | table ; // Key string parser tomlKey : key EOF; // Key-Value pairs keyval : key Equals val; key : simpleKey (Dot simpleKey)*; simpleKey : quotedKey | unquotedKey ; unquotedKey : UnquotedKey; quotedKey : basicString | literalString ; val : string | integer | floatValue | booleanValue | dateTime | array | inlineTable ; // String string : mlBasicString | basicString | mlLiteralString | literalString ; // Basic String basicString : QuotationMark basicChar* QuotationMark; basicChar : basicUnescaped | escaped ; basicUnescaped : StringChar; escaped : EscapeSequence; // Multiline Basic String mlBasicString : TripleQuotationMark mlBasicChar* TripleQuotationMark; mlBasicChar : mlBasicUnescaped | escaped; mlBasicUnescaped : StringChar | NewLine; // Literal String literalString : Apostrophe literalBody Apostrophe; literalBody : StringChar*; // Multiline Literal String mlLiteralString : TripleApostrophe mlLiteralBody TripleApostrophe; mlLiteralBody : (StringChar | NewLine)*; // Integer integer : decInt | hexInt | octInt | binInt ; decInt : DecimalInteger; hexInt : HexInteger; octInt : OctalInteger; binInt : BinaryInteger; // Float floatValue : regularFloat | regularFloatInf | regularFloatNaN ; regularFloat : FloatingPoint; regularFloatInf : FloatingPointInf; regularFloatNaN : FloatingPointNaN; // Boolean booleanValue : trueBool | falseBool ; trueBool : TrueBoolean; falseBool : FalseBoolean; // Date and Time dateTime : offsetDateTime | localDateTime | localDate | localTime ; offsetDateTime : date TimeDelimiter time timeOffset; localDateTime : date TimeDelimiter time; localDate : date; localTime : time; date : year Dash month Dash day; time : hour Colon minute Colon second (Dot secondFraction)?; timeOffset : Z | hourOffset Colon minuteOffset ; hourOffset : (Dash | Plus) hour; minuteOffset : DateDigits; secondFraction : DateDigits; year : DateDigits; month : DateDigits; day : DateDigits; hour : DateDigits; minute : DateDigits; second : DateDigits; // Array array : ArrayStart (arrayValues Comma?)? NewLine* ArrayEnd; arrayValues : arrayValue (Comma arrayValue)*; arrayValue : NewLine* val; // Table table : standardTable | arrayTable ; // Standard Table standardTable : TableKeyStart key? TableKeyEnd; // Inline Table inlineTable : InlineTableStart inlineTableValues? InlineTableEnd; inlineTableValues : keyval (Comma keyval)*; // Array Table arrayTable : ArrayTableKeyStart key? ArrayTableKeyEnd; cava-0.6.0/toml/src/main/java/000077500000000000000000000000001341750772100160455ustar00rootroot00000000000000cava-0.6.0/toml/src/main/java/net/000077500000000000000000000000001341750772100166335ustar00rootroot00000000000000cava-0.6.0/toml/src/main/java/net/consensys/000077500000000000000000000000001341750772100206575ustar00rootroot00000000000000cava-0.6.0/toml/src/main/java/net/consensys/cava/000077500000000000000000000000001341750772100215715ustar00rootroot00000000000000cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/000077500000000000000000000000001341750772100225445ustar00rootroot00000000000000cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/AccumulatingErrorListener.java000066400000000000000000000073571341750772100305570ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import static net.consensys.cava.toml.TomlPosition.positionAt; import net.consensys.cava.toml.internal.TomlLexer; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.InputMismatchException; import org.antlr.v4.runtime.NoViableAltException; import org.antlr.v4.runtime.Parser; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.misc.IntervalSet; final class AccumulatingErrorListener extends BaseErrorListener implements ErrorReporter { private final List errors = new ArrayList<>(); @Override public void syntaxError( Recognizer recognizer, Object offendingSymbol, int line, int charPosition, String msg, RecognitionException e) { TomlPosition position = positionAt(line, charPosition + 1); if (e instanceof InputMismatchException || e instanceof NoViableAltException) { String message = getMessage(e.getOffendingToken(), getExpected(e)); reportError(message, position); return; } if (offendingSymbol instanceof Token && recognizer instanceof Parser) { String message = getMessage((Token) offendingSymbol, getExpected(((Parser) recognizer).getExpectedTokens())); reportError(message, position); return; } reportError(msg, position); } @Override public void reportError(TomlParseError error) { errors.add(error); } private void reportError(String message, TomlPosition position) { reportError(new TomlParseError(message, position)); } List errors() { return errors; } private String getMessage(Token token, String expected) { return "Unexpected " + getTokenName(token) + ", expected " + expected; } private static String getTokenName(Token token) { int tokenType = token.getType(); switch (tokenType) { case TomlLexer.NewLine: return "end of line"; case TomlLexer.EOF: return "end of input"; default: return "'" + Toml.tomlEscape(token.getText()) + '\''; } } private static String getExpected(RecognitionException e) { IntervalSet expectedTokens = e.getExpectedTokens(); return getExpected(expectedTokens); } private static String getExpected(IntervalSet expectedTokens) { List sortedNames = expectedTokens .getIntervals() .stream() .flatMap(i -> IntStream.rangeClosed(i.a, i.b).boxed()) .flatMap(TokenName::namesForToken) .sorted() .distinct() .map(TokenName::displayName) .collect(Collectors.toList()); StringBuilder builder = new StringBuilder(); int count = sortedNames.size(); for (int i = 0; i < count; ++i) { builder.append(sortedNames.get(i)); if (i < (count - 2)) { builder.append(", "); } else if (i == (count - 2)) { if (count >= 3) { builder.append(','); } builder.append(" or "); } } return builder.toString(); } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/ArrayVisitor.java000066400000000000000000000032641341750772100260520ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import net.consensys.cava.toml.internal.TomlParser.ArrayValueContext; import net.consensys.cava.toml.internal.TomlParser.ValContext; import net.consensys.cava.toml.internal.TomlParserBaseVisitor; final class ArrayVisitor extends TomlParserBaseVisitor { private final MutableTomlArray array = new MutableTomlArray(true); @Override public MutableTomlArray visitArrayValue(ArrayValueContext ctx) { ValContext valContext = ctx.val(); if (valContext != null) { Object value = valContext.accept(new ValueVisitor()); if (value != null) { TomlPosition position = new TomlPosition(ctx); try { array.append(value, position); } catch (TomlInvalidTypeException e) { throw new TomlParseError(e.getMessage(), position); } } } return array; } @Override protected MutableTomlArray aggregateResult(MutableTomlArray aggregate, MutableTomlArray nextResult) { return aggregate == null ? null : nextResult; } @Override protected MutableTomlArray defaultResult() { return array; } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/ErrorReporter.java000066400000000000000000000012601341750772100262220ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; interface ErrorReporter { void reportError(TomlParseError error); } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/InlineTableVisitor.java000066400000000000000000000033541341750772100271620ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import net.consensys.cava.toml.internal.TomlParser.KeyContext; import net.consensys.cava.toml.internal.TomlParser.KeyvalContext; import net.consensys.cava.toml.internal.TomlParser.ValContext; import net.consensys.cava.toml.internal.TomlParserBaseVisitor; import java.util.List; final class InlineTableVisitor extends TomlParserBaseVisitor { private final MutableTomlTable table = new MutableTomlTable(); @Override public MutableTomlTable visitKeyval(KeyvalContext ctx) { KeyContext keyContext = ctx.key(); ValContext valContext = ctx.val(); if (keyContext != null && valContext != null) { List path = keyContext.accept(new KeyVisitor()); if (path != null && !path.isEmpty()) { Object value = valContext.accept(new ValueVisitor()); if (value != null) { table.set(path, value, new TomlPosition(ctx)); } } } return table; } @Override protected MutableTomlTable aggregateResult(MutableTomlTable aggregate, MutableTomlTable nextResult) { return table; } @Override protected MutableTomlTable defaultResult() { return table; } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/JsonSerializer.java000066400000000000000000000122751341750772100263610ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import static java.util.Objects.requireNonNull; import static net.consensys.cava.toml.TomlType.typeFor; import java.io.IOException; import java.util.Collections; import java.util.Iterator; import java.util.Optional; final class JsonSerializer { private JsonSerializer() {} static void toJson(TomlTable table, Appendable appendable) throws IOException { requireNonNull(table); requireNonNull(appendable); toJson(table, appendable, 0); appendable.append(System.lineSeparator()); } private static void toJson(TomlTable table, Appendable appendable, int indent) throws IOException { if (table.isEmpty()) { appendable.append("{}"); return; } appendLine(appendable, "{"); for (Iterator iterator = table.keySet().stream().sorted().iterator(); iterator.hasNext();) { String key = iterator.next(); append(appendable, indent + 2, "\"" + escape(key) + "\" : "); Object value = table.get(Collections.singletonList(key)); assert value != null; appendTomlValue(value, appendable, indent); if (iterator.hasNext()) { appendable.append(","); appendable.append(System.lineSeparator()); } } appendable.append(System.lineSeparator()); append(appendable, indent, "}"); } static void toJson(TomlArray array, Appendable appendable) throws IOException { toJson(array, appendable, 0); appendable.append(System.lineSeparator()); } private static void toJson(TomlArray array, Appendable appendable, int indent) throws IOException { if (array.isEmpty()) { appendable.append("[]"); return; } if (array.containsTables()) { append(appendable, 0, "["); for (Iterator iterator = array.toList().iterator(); iterator.hasNext();) { toJson((TomlTable) iterator.next(), appendable, indent); if (iterator.hasNext()) { appendable.append(","); } } append(appendable, 0, "]"); } else { appendLine(appendable, "["); for (Iterator iterator = array.toList().iterator(); iterator.hasNext();) { indentLine(appendable, indent + 2); appendTomlValue(iterator.next(), appendable, indent); if (iterator.hasNext()) { appendable.append(","); appendable.append(System.lineSeparator()); } } appendable.append(System.lineSeparator()); append(appendable, indent, "]"); } } private static void appendTomlValue(Object value, Appendable appendable, int indent) throws IOException { Optional tomlType = typeFor(value); assert tomlType.isPresent(); switch (tomlType.get()) { case STRING: append(appendable, 0, "\"" + escape((String) value) + "\""); break; case INTEGER: case FLOAT: append(appendable, 0, value.toString()); break; case BOOLEAN: append(appendable, 0, ((Boolean) value) ? "true" : "false"); break; case OFFSET_DATE_TIME: case LOCAL_DATE_TIME: case LOCAL_DATE: case LOCAL_TIME: append(appendable, 0, "\"" + value.toString() + "\""); break; case ARRAY: toJson((TomlArray) value, appendable, indent + 2); break; case TABLE: toJson((TomlTable) value, appendable, indent + 2); break; } } private static void append(Appendable appendable, int indent, String line) throws IOException { indentLine(appendable, indent); appendable.append(line); } private static void appendLine(Appendable appendable, String line) throws IOException { appendable.append(line); appendable.append(System.lineSeparator()); } private static void indentLine(Appendable appendable, int indent) throws IOException { for (int i = 0; i < indent; ++i) { appendable.append(' '); } } private static StringBuilder escape(String text) { StringBuilder out = new StringBuilder(text.length()); for (int i = 0; i < text.length(); i++) { char ch = text.charAt(i); if (ch == '\'') { out.append("\\'"); continue; } if (ch >= 0x20) { out.append(ch); continue; } switch (ch) { case '\t': out.append("\\t"); break; case '\b': out.append("\\b"); break; case '\n': out.append("\\n"); break; case '\r': out.append("\\r"); break; case '\f': out.append("\\f"); break; default: out.append("\\u").append(String.format("%04x", text.codePointAt(i))); } } return out; } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/KeyVisitor.java000066400000000000000000000030311341750772100255140ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import net.consensys.cava.toml.internal.TomlParser.QuotedKeyContext; import net.consensys.cava.toml.internal.TomlParser.UnquotedKeyContext; import net.consensys.cava.toml.internal.TomlParserBaseVisitor; import java.util.ArrayList; import java.util.List; final class KeyVisitor extends TomlParserBaseVisitor> { private final List keys = new ArrayList<>(); @Override public List visitUnquotedKey(UnquotedKeyContext ctx) { keys.add(ctx.getText()); return keys; } @Override public List visitQuotedKey(QuotedKeyContext ctx) { StringBuilder builder = ctx.accept(new QuotedStringVisitor()); keys.add(builder.toString()); return keys; } @Override protected List aggregateResult(List aggregate, List nextResult) { return aggregate == null ? null : nextResult; } @Override protected List defaultResult() { return keys; } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/LineVisitor.java000066400000000000000000000072451341750772100256660ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import static net.consensys.cava.toml.TomlVersion.V0_4_0; import net.consensys.cava.toml.internal.TomlParser.ArrayTableContext; import net.consensys.cava.toml.internal.TomlParser.KeyContext; import net.consensys.cava.toml.internal.TomlParser.KeyvalContext; import net.consensys.cava.toml.internal.TomlParser.StandardTableContext; import net.consensys.cava.toml.internal.TomlParser.ValContext; import net.consensys.cava.toml.internal.TomlParserBaseVisitor; import java.util.List; final class LineVisitor extends TomlParserBaseVisitor { private final MutableTomlTable table = new MutableTomlTable(); private final ErrorReporter errorReporter; private final TomlVersion version; private MutableTomlTable currentTable = table; LineVisitor(ErrorReporter errorReporter, TomlVersion version) { this.errorReporter = errorReporter; this.version = version; } @Override public MutableTomlTable visitKeyval(KeyvalContext ctx) { KeyContext keyContext = ctx.key(); ValContext valContext = ctx.val(); if (keyContext == null || valContext == null) { return table; } try { List path = keyContext.accept(new KeyVisitor()); if (path == null || path.isEmpty()) { return table; } // TOML 0.4.0 doesn't support dotted keys if (!version.after(V0_4_0) && path.size() > 1) { throw new TomlParseError("Dotted keys are not supported", new TomlPosition(keyContext)); } Object value = valContext.accept(new ValueVisitor()); if (value != null) { currentTable.set(path, value, new TomlPosition(ctx)); } return table; } catch (TomlParseError e) { errorReporter.reportError(e); return table; } } @Override public MutableTomlTable visitStandardTable(StandardTableContext ctx) { KeyContext keyContext = ctx.key(); if (keyContext == null) { errorReporter.reportError(new TomlParseError("Empty table key", new TomlPosition(ctx))); return table; } List path = keyContext.accept(new KeyVisitor()); if (path == null) { return table; } try { currentTable = table.createTable(path, new TomlPosition(ctx)); } catch (TomlParseError e) { errorReporter.reportError(e); } return table; } @Override public MutableTomlTable visitArrayTable(ArrayTableContext ctx) { KeyContext keyContext = ctx.key(); if (keyContext == null) { errorReporter.reportError(new TomlParseError("Empty table key", new TomlPosition(ctx))); return table; } List path = keyContext.accept(new KeyVisitor()); if (path == null) { return table; } try { currentTable = table.createArrayTable(path, new TomlPosition(ctx)); } catch (TomlParseError e) { errorReporter.reportError(e); } return table; } @Override protected MutableTomlTable aggregateResult(MutableTomlTable aggregate, MutableTomlTable nextResult) { return aggregate == null ? null : nextResult; } @Override protected MutableTomlTable defaultResult() { return table; } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/LocalDateVisitor.java000066400000000000000000000062601341750772100266230ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import net.consensys.cava.toml.internal.TomlParser.DayContext; import net.consensys.cava.toml.internal.TomlParser.MonthContext; import net.consensys.cava.toml.internal.TomlParser.YearContext; import net.consensys.cava.toml.internal.TomlParserBaseVisitor; import java.time.DateTimeException; import java.time.LocalDate; import org.antlr.v4.runtime.tree.ErrorNode; final class LocalDateVisitor extends TomlParserBaseVisitor { private static LocalDate INITIAL = LocalDate.parse("1900-01-01"); private LocalDate date = INITIAL; @Override public LocalDate visitYear(YearContext ctx) { String text = ctx.getText(); if (text.length() != 4) { throw new TomlParseError("Invalid year (valid range 0000..9999)", new TomlPosition(ctx)); } int year; try { year = Integer.parseInt(text); } catch (NumberFormatException e) { throw new TomlParseError("Invalid year", new TomlPosition(ctx), e); } date = date.withYear(year); return date; } @Override public LocalDate visitMonth(MonthContext ctx) { String text = ctx.getText(); if (text.length() != 2) { throw new TomlParseError("Invalid month (valid range 01..12)", new TomlPosition(ctx)); } int month; try { month = Integer.parseInt(text); } catch (NumberFormatException e) { throw new TomlParseError("Invalid month", new TomlPosition(ctx), e); } if (month < 1 || month > 12) { throw new TomlParseError("Invalid month (valid range 01..12)", new TomlPosition(ctx)); } date = date.withMonth(month); return date; } @Override public LocalDate visitDay(DayContext ctx) { String text = ctx.getText(); if (text.length() != 2) { throw new TomlParseError("Invalid day (valid range 01..28/31)", new TomlPosition(ctx)); } int day; try { day = Integer.parseInt(text); } catch (NumberFormatException e) { throw new TomlParseError("Invalid day", new TomlPosition(ctx), e); } if (day < 1 || day > 31) { throw new TomlParseError("Invalid day (valid range 01..28/31)", new TomlPosition(ctx)); } try { date = date.withDayOfMonth(day); } catch (DateTimeException e) { throw new TomlParseError(e.getMessage(), new TomlPosition(ctx), e); } return date; } @Override public LocalDate visitErrorNode(ErrorNode node) { return null; } @Override protected LocalDate aggregateResult(LocalDate aggregate, LocalDate nextResult) { return aggregate == null ? null : nextResult; } @Override protected LocalDate defaultResult() { return date; } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/LocalTimeVisitor.java000066400000000000000000000074361341750772100266520ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import net.consensys.cava.toml.internal.TomlParser.HourContext; import net.consensys.cava.toml.internal.TomlParser.MinuteContext; import net.consensys.cava.toml.internal.TomlParser.SecondContext; import net.consensys.cava.toml.internal.TomlParser.SecondFractionContext; import net.consensys.cava.toml.internal.TomlParserBaseVisitor; import java.time.LocalTime; import org.antlr.v4.runtime.tree.ErrorNode; final class LocalTimeVisitor extends TomlParserBaseVisitor { private LocalTime time = LocalTime.MIN; @Override public LocalTime visitHour(HourContext ctx) { String text = ctx.getText(); if (text.length() != 2) { throw new TomlParseError("Invalid hour (valid range 00..23)", new TomlPosition(ctx)); } int hour; try { hour = Integer.parseInt(text); } catch (NumberFormatException e) { throw new TomlParseError("Invalid hour", new TomlPosition(ctx), e); } if (hour < 0 || hour > 23) { throw new TomlParseError("Invalid hour (valid range 00..23)", new TomlPosition(ctx)); } time = time.withHour(hour); return time; } @Override public LocalTime visitMinute(MinuteContext ctx) { String text = ctx.getText(); if (text.length() != 2) { throw new TomlParseError("Invalid minutes (valid range 00..59)", new TomlPosition(ctx)); } int minute; try { minute = Integer.parseInt(text); } catch (NumberFormatException e) { throw new TomlParseError("Invalid minutes", new TomlPosition(ctx), e); } if (minute < 0 || minute > 59) { throw new TomlParseError("Invalid minutes (valid range 00..59)", new TomlPosition(ctx)); } time = time.withMinute(minute); return time; } @Override public LocalTime visitSecond(SecondContext ctx) { String text = ctx.getText(); if (text.length() != 2) { throw new TomlParseError("Invalid seconds (valid range 00..59)", new TomlPosition(ctx)); } int second; try { second = Integer.parseInt(text); } catch (NumberFormatException e) { throw new TomlParseError("Invalid seconds", new TomlPosition(ctx), e); } if (second < 0 || second > 59) { throw new TomlParseError("Invalid seconds (valid range 00..59)", new TomlPosition(ctx)); } time = time.withSecond(second); return time; } @Override public LocalTime visitSecondFraction(SecondFractionContext ctx) { String text = ctx.getText(); if (text.isEmpty() || text.length() > 9) { throw new TomlParseError("Invalid nanoseconds (valid range 0..999999999)", new TomlPosition(ctx)); } if (text.length() < 9) { text = text + "000000000".substring(text.length()); } int nano; try { nano = Integer.parseInt(text); } catch (NumberFormatException e) { throw new TomlParseError("Invalid nanoseconds", new TomlPosition(ctx), e); } time = time.withNano(nano); return time; } @Override public LocalTime visitErrorNode(ErrorNode node) { return null; } @Override protected LocalTime aggregateResult(LocalTime aggregate, LocalTime nextResult) { return aggregate == null ? null : nextResult; } @Override protected LocalTime defaultResult() { return time; } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/MutableTomlArray.java000066400000000000000000000075741341750772100266500ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import static java.util.Objects.requireNonNull; import static net.consensys.cava.toml.TomlType.typeFor; import static net.consensys.cava.toml.TomlType.typeNameFor; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; final class MutableTomlArray implements TomlArray { private static class Element { final Object value; final TomlPosition position; private Element(Object value, TomlPosition position) { this.value = value; this.position = position; } } static final TomlArray EMPTY = new MutableTomlArray(true); private final List elements = new ArrayList<>(); private final boolean definedAsLiteral; private TomlType type = null; MutableTomlArray() { this(false); } MutableTomlArray(boolean definedAsLiteral) { this.definedAsLiteral = definedAsLiteral; } boolean wasDefinedAsLiteral() { return definedAsLiteral; } @Override public int size() { return elements.size(); } @Override public boolean isEmpty() { return type == null; } @Override public boolean containsStrings() { return type == null || type == TomlType.STRING; } @Override public boolean containsLongs() { return type == null || type == TomlType.INTEGER; } @Override public boolean containsDoubles() { return type == null || type == TomlType.FLOAT; } @Override public boolean containsBooleans() { return type == null || type == TomlType.BOOLEAN; } @Override public boolean containsOffsetDateTimes() { return type == null || type == TomlType.OFFSET_DATE_TIME; } @Override public boolean containsLocalDateTimes() { return type == null || type == TomlType.LOCAL_DATE_TIME; } @Override public boolean containsLocalDates() { return type == null || type == TomlType.LOCAL_DATE; } @Override public boolean containsLocalTimes() { return type == null || type == TomlType.LOCAL_TIME; } @Override public boolean containsArrays() { return type == null || type == TomlType.ARRAY; } @Override public boolean containsTables() { return type == null || type == TomlType.TABLE; } @Override public Object get(int index) { return elements.get(index).value; } @Override public TomlPosition inputPositionOf(int index) { return elements.get(index).position; } MutableTomlArray append(Object value, TomlPosition position) { requireNonNull(value); if (value instanceof Integer) { value = ((Integer) value).longValue(); } TomlType origType = type; Optional valueType = typeFor(value); if (!valueType.isPresent()) { throw new IllegalArgumentException("Unsupported type " + value.getClass().getSimpleName()); } if (type != null) { if (valueType.get() != type) { throw new TomlInvalidTypeException( "Cannot add a " + typeNameFor(value) + " to an array containing " + type.typeName() + "s"); } } else { type = valueType.get(); } try { elements.add(new Element(value, position)); } catch (Throwable e) { type = origType; throw e; } return this; } @Override public List toList() { return elements.stream().map(e -> e.value).collect(Collectors.toList()); } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/MutableTomlTable.java000066400000000000000000000172251341750772100266130ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import static net.consensys.cava.toml.Parser.parseDottedKey; import static net.consensys.cava.toml.TomlPosition.positionAt; import static net.consensys.cava.toml.TomlType.typeFor; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nullable; final class MutableTomlTable implements TomlTable { private static class Element { final Object value; final TomlPosition position; private Element(Object value, TomlPosition position) { this.value = value; this.position = position; } } static final TomlTable EMPTY = new MutableTomlTable(true); private Map properties = new HashMap<>(); private boolean implicitlyDefined; MutableTomlTable() { this(false); } private MutableTomlTable(boolean implicitlyDefined) { this.implicitlyDefined = implicitlyDefined; } @Override public int size() { return properties.size(); } @Override public boolean isEmpty() { return properties.isEmpty(); } @Override public Set keySet() { return properties.keySet(); } @Override public Set> keyPathSet(boolean includeTables) { return properties.entrySet().stream().flatMap(entry -> { String key = entry.getKey(); List basePath = Collections.singletonList(key); Element element = entry.getValue(); if (!(element.value instanceof TomlTable)) { return Stream.of(basePath); } Stream> subKeys = ((TomlTable) element.value).keyPathSet(includeTables).stream().map(subPath -> { List path = new ArrayList<>(subPath.size() + 1); path.add(key); path.addAll(subPath); return path; }); if (includeTables) { return Stream.concat(Stream.of(basePath), subKeys); } else { return subKeys; } }).collect(Collectors.toSet()); } @Override @Nullable @SuppressWarnings("unchecked") public Object get(List path) { if (path.isEmpty()) { return this; } Element element = getElement(path); return (element != null) ? element.value : null; } @Override @Nullable public TomlPosition inputPositionOf(List path) { if (path.isEmpty()) { return positionAt(1, 1); } Element element = getElement(path); return (element != null) ? element.position : null; } private Element getElement(List path) { MutableTomlTable table = this; int depth = path.size(); assert depth > 0; for (int i = 0; i < (depth - 1); ++i) { Element element = table.properties.get(path.get(i)); if (element == null) { return null; } if (element.value instanceof MutableTomlTable) { table = (MutableTomlTable) element.value; continue; } return null; } return table.properties.get(path.get(depth - 1)); } @Override public Map toMap() { return properties.entrySet().stream().collect(Collectors.toMap(Entry::getKey, e -> e.getValue().value)); } MutableTomlTable createTable(List path, TomlPosition position) { if (path.isEmpty()) { return this; } int depth = path.size(); MutableTomlTable table = ensureTable(path.subList(0, depth - 1), position, true); String key = path.get(depth - 1); Element element = table.properties.get(key); if (element == null) { MutableTomlTable newTable = new MutableTomlTable(); table.properties.put(key, new Element(newTable, position)); return newTable; } if (element.value instanceof MutableTomlTable) { table = (MutableTomlTable) element.value; if (table.implicitlyDefined) { table.implicitlyDefined = false; table.properties.put(key, new Element(table, position)); return table; } } String message = Toml.joinKeyPath(path) + " previously defined at " + element.position; throw new TomlParseError(message, position); } MutableTomlTable createArrayTable(List path, TomlPosition position) { if (path.isEmpty()) { throw new IllegalArgumentException("empty path"); } int depth = path.size(); MutableTomlTable table = ensureTable(path.subList(0, depth - 1), position, true); String key = path.get(depth - 1); Element element = table.properties.computeIfAbsent(key, k -> new Element(new MutableTomlArray(), position)); if (!(element.value instanceof MutableTomlArray)) { String message = Toml.joinKeyPath(path) + " is not an array (previously defined at " + element.position + ")"; throw new TomlParseError(message, position); } MutableTomlArray array = (MutableTomlArray) element.value; if (array.wasDefinedAsLiteral()) { String message = Toml.joinKeyPath(path) + " previously defined as a literal array at " + element.position; throw new TomlParseError(message, position); } MutableTomlTable newTable = new MutableTomlTable(); array.append(newTable, position); return newTable; } MutableTomlTable set(String keyPath, Object value, TomlPosition position) { return set(parseDottedKey(keyPath), value, position); } MutableTomlTable set(List path, Object value, TomlPosition position) { int depth = path.size(); assert (depth > 0); assert (value != null); if (value instanceof Integer) { value = ((Integer) value).longValue(); } assert (typeFor(value).isPresent()) : "Unexpected value of type " + value.getClass(); MutableTomlTable table = ensureTable(path.subList(0, depth - 1), position, false); Element prevElem = table.properties.putIfAbsent(path.get(depth - 1), new Element(value, position)); if (prevElem != null) { String pathString = Toml.joinKeyPath(path); String message = pathString + " previously defined at " + prevElem.position; throw new TomlParseError(message, position); } return this; } private MutableTomlTable ensureTable(List path, TomlPosition position, boolean followArrayTables) { MutableTomlTable table = this; int depth = path.size(); for (int i = 0; i < depth; ++i) { Element element = table.properties.computeIfAbsent(path.get(i), k -> new Element(new MutableTomlTable(true), position)); if (element.value instanceof MutableTomlTable) { table = (MutableTomlTable) element.value; continue; } if (followArrayTables && element.value instanceof MutableTomlArray) { MutableTomlArray array = (MutableTomlArray) element.value; if (!array.wasDefinedAsLiteral() && !array.isEmpty() && array.containsTables()) { table = (MutableTomlTable) array.get(array.size() - 1); continue; } } String message = Toml.joinKeyPath(path.subList(0, i + 1)) + " is not a table (previously defined at " + element.position + ")"; throw new TomlParseError(message, position); } return table; } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/Parser.java000066400000000000000000000060611341750772100246460ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import net.consensys.cava.toml.internal.TomlLexer; import net.consensys.cava.toml.internal.TomlParser; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTree; final class Parser { private Parser() {} static TomlParseResult parse(CharStream stream, TomlVersion version) { TomlLexer lexer = new TomlLexer(stream); TomlParser parser = new TomlParser(new CommonTokenStream(lexer)); parser.removeErrorListeners(); AccumulatingErrorListener errorListener = new AccumulatingErrorListener(); parser.addErrorListener(errorListener); ParseTree tree = parser.toml(); TomlTable table = tree.accept(new LineVisitor(errorListener, version)); return new TomlParseResult() { @Override public int size() { return table.size(); } @Override public boolean isEmpty() { return table.isEmpty(); } @Override public Set keySet() { return table.keySet(); } @Override public Set> keyPathSet(boolean includeTables) { return table.keyPathSet(includeTables); } @Override @Nullable public Object get(List path) { return table.get(path); } @Override @Nullable public TomlPosition inputPositionOf(List path) { return table.inputPositionOf(path); } @Override public Map toMap() { return table.toMap(); } @Override public List errors() { return errorListener.errors(); } }; } static List parseDottedKey(String dottedKey) { TomlLexer lexer = new TomlLexer(CharStreams.fromString(dottedKey)); lexer.mode(TomlLexer.KeyMode); TomlParser parser = new TomlParser(new CommonTokenStream(lexer)); parser.removeErrorListeners(); AccumulatingErrorListener errorListener = new AccumulatingErrorListener(); parser.addErrorListener(errorListener); List keyList = parser.tomlKey().accept(new KeyVisitor()); List errors = errorListener.errors(); if (!errors.isEmpty()) { TomlParseError e = errors.get(0); throw new IllegalArgumentException("Invalid key: " + e.getMessage(), e); } return keyList; } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/QuotedStringVisitor.java000066400000000000000000000063401341750772100274220ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import net.consensys.cava.toml.internal.TomlParser.BasicUnescapedContext; import net.consensys.cava.toml.internal.TomlParser.EscapedContext; import net.consensys.cava.toml.internal.TomlParser.LiteralBodyContext; import net.consensys.cava.toml.internal.TomlParser.MlBasicUnescapedContext; import net.consensys.cava.toml.internal.TomlParser.MlLiteralBodyContext; import net.consensys.cava.toml.internal.TomlParserBaseVisitor; final class QuotedStringVisitor extends TomlParserBaseVisitor { private final StringBuilder builder = new StringBuilder(); @Override public StringBuilder visitLiteralBody(LiteralBodyContext ctx) { return builder.append(ctx.getText()); } @Override public StringBuilder visitMlLiteralBody(MlLiteralBodyContext ctx) { return builder.append(ctx.getText()); } @Override public StringBuilder visitBasicUnescaped(BasicUnescapedContext ctx) { return builder.append(ctx.getText()); } @Override public StringBuilder visitMlBasicUnescaped(MlBasicUnescapedContext ctx) { return builder.append(ctx.getText()); } @Override public StringBuilder visitEscaped(EscapedContext ctx) { String text = ctx.getText(); if (text.isEmpty()) { return builder; } assert (text.charAt(0) == '\\'); if (text.length() == 1) { return builder.append('\\'); } switch (text.charAt(1)) { case '"': return builder.append('"'); case '\\': return builder.append('\\'); case 'b': return builder.append('\b'); case 'f': return builder.append('\f'); case 'n': return builder.append('\n'); case 'r': return builder.append('\r'); case 't': return builder.append('\t'); case 'u': assert (text.length() == 6); return builder.append(convertUnicodeEscape(text.substring(2), ctx)); case 'U': assert (text.length() == 10); return builder.append(convertUnicodeEscape(text.substring(2), ctx)); default: throw new TomlParseError("Invalid escape sequence '" + text + "'", new TomlPosition(ctx)); } } private char[] convertUnicodeEscape(String hexChars, EscapedContext ctx) { try { return Character.toChars(Integer.parseInt(hexChars, 16)); } catch (IllegalArgumentException e) { throw new TomlParseError("Invalid unicode escape sequence", new TomlPosition(ctx)); } } @Override protected StringBuilder aggregateResult(StringBuilder aggregate, StringBuilder nextResult) { return aggregate == null ? null : nextResult; } @Override protected StringBuilder defaultResult() { return builder; } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/TokenName.java000066400000000000000000000056671341750772100253060ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import net.consensys.cava.toml.internal.TomlLexer; import java.util.Arrays; import java.util.BitSet; import java.util.stream.Stream; enum TokenName { // Ordered by display preference LOWER_ALPHA("a-z", TomlLexer.UnquotedKey), UPPER_ALPHA("A-Z", TomlLexer.UnquotedKey), DIGITS("0-9", TomlLexer.UnquotedKey), ARRAY_END("]", TomlLexer.ArrayEnd, TomlLexer.TableKeyEnd), ARRAY_TABLE_END("]]", TomlLexer.ArrayTableKeyEnd), INLINE_TABLE_END("}", TomlLexer.InlineTableEnd), DOT(".", TomlLexer.Dot), DASH("-", TomlLexer.Dash), PLUS("+", TomlLexer.Plus), COLON(":", TomlLexer.Colon), EQUALS("=", TomlLexer.Equals), COMMA("a comma", TomlLexer.Comma), Z("Z", TomlLexer.Z), APOSTROPHE("'", TomlLexer.Apostrophe, TomlLexer.MLLiteralStringEnd), QUOTATION_MARK("\"", TomlLexer.QuotationMark, TomlLexer.MLBasicStringEnd), TRIPLE_APOSTROPHE("'''", TomlLexer.TripleApostrophe), TRIPLE_QUOTATION_MARK("\"\"\"", TomlLexer.TripleQuotationMark), CHARACTER("a character", TomlLexer.EscapeSequence, TomlLexer.StringChar), NUMBER("a number", TomlLexer.DecimalInteger, TomlLexer.BinaryInteger, TomlLexer.OctalInteger, TomlLexer.HexInteger, TomlLexer.FloatingPoint, TomlLexer.FloatingPointInf, TomlLexer.FloatingPointNaN), BOOLEAN("a boolean", TomlLexer.TrueBoolean, TomlLexer.FalseBoolean), DATETIME("a date/time", TomlLexer.DateDigits), TIME("a time", TomlLexer.TimeDelimiter), ARRAY("an array", TomlLexer.ArrayStart), INLINE_TABLE("a table", TomlLexer.InlineTableStart), TABLE("a table key", TomlLexer.TableKeyStart, TomlLexer.ArrayTableKeyStart), NEWLINE("a newline", TomlLexer.NewLine), EOF("end-of-input", TomlLexer.EOF), NULL("NULL", 0, TomlLexer.WS, TomlLexer.Comment, TomlLexer.Error); private final String displayName; @SuppressWarnings("ImmutableEnumChecker") private final BitSet tokenTypes; TokenName(String displayName, int... tokenTypes) { this.displayName = displayName; // offset by 1 to account for EOF being -1 (moves it to zero) this.tokenTypes = new BitSet(TomlLexer.VOCABULARY.getMaxTokenType() + 1); for (int type : tokenTypes) { this.tokenTypes.set(type + 1); } } static Stream namesForToken(int tokenType) { return Arrays.stream(TokenName.values()).filter(n -> n.tokenTypes.get(tokenType + 1)); } public String displayName() { return displayName; } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/Toml.java000066400000000000000000000162251341750772100243300ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import static java.util.Objects.requireNonNull; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.nio.channels.ReadableByteChannel; import java.nio.file.Path; import java.util.List; import java.util.StringJoiner; import java.util.regex.Pattern; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CharStreams; /** * Methods for parsing data stored in Tom's Obvious, Minimal Language (TOML). */ public final class Toml { private static final Pattern simpleKeyPattern = Pattern.compile("^[A-Za-z0-9_-]+$"); private Toml() {} /** * Parse a TOML string. * * @param input The input to parse. * @return The parse result. */ public static TomlParseResult parse(String input) { return parse(input, TomlVersion.LATEST); } /** * Parse a TOML string. * * @param input The input to parse. * @param version The version level to parse at. * @return The parse result. */ public static TomlParseResult parse(String input, TomlVersion version) { CharStream stream = CharStreams.fromString(input); return Parser.parse(stream, version.canonical); } /** * Parse a TOML file. * * @param file The input file to parse. * @return The parse result. * @throws IOException If an IO error occurs. */ public static TomlParseResult parse(Path file) throws IOException { return parse(file, TomlVersion.LATEST); } /** * Parse a TOML file. * * @param file The input file to parse. * @param version The version level to parse at. * @return The parse result. * @throws IOException If an IO error occurs. */ public static TomlParseResult parse(Path file, TomlVersion version) throws IOException { CharStream stream = CharStreams.fromPath(file); return Parser.parse(stream, version.canonical); } /** * Parse a TOML input stream. * * @param is The input stream to read the TOML document from. * @return The parse result. * @throws IOException If an IO error occurs. */ public static TomlParseResult parse(InputStream is) throws IOException { return parse(is, TomlVersion.LATEST); } /** * Parse a TOML input stream. * * @param is The input stream to read the TOML document from. * @param version The version level to parse at. * @return The parse result. * @throws IOException If an IO error occurs. */ public static TomlParseResult parse(InputStream is, TomlVersion version) throws IOException { CharStream stream = CharStreams.fromStream(is); return Parser.parse(stream, version.canonical); } /** * Parse a TOML reader. * * @param reader The reader to obtain the TOML document from. * @return The parse result. * @throws IOException If an IO error occurs. */ public static TomlParseResult parse(Reader reader) throws IOException { return parse(reader, TomlVersion.LATEST); } /** * Parse a TOML input stream. * * @param reader The reader to obtain the TOML document from. * @param version The version level to parse at. * @return The parse result. * @throws IOException If an IO error occurs. */ public static TomlParseResult parse(Reader reader, TomlVersion version) throws IOException { CharStream stream = CharStreams.fromReader(reader); return Parser.parse(stream, version.canonical); } /** * Parse a TOML reader. * * @param channel The channel to read the TOML document from. * @return The parse result. * @throws IOException If an IO error occurs. */ public static TomlParseResult parse(ReadableByteChannel channel) throws IOException { return parse(channel, TomlVersion.LATEST); } /** * Parse a TOML input stream. * * @param channel The channel to read the TOML document from. * @param version The version level to parse at. * @return The parse result. * @throws IOException If an IO error occurs. */ public static TomlParseResult parse(ReadableByteChannel channel, TomlVersion version) throws IOException { CharStream stream = CharStreams.fromChannel(channel); return Parser.parse(stream, version.canonical); } /** * Parse a dotted key into individual parts. * * @param dottedKey A dotted key (e.g. {@code server.address.port}). * @return A list of individual keys in the path. * @throws IllegalArgumentException If the dotted key cannot be parsed. */ public static List parseDottedKey(String dottedKey) { requireNonNull(dottedKey); return Parser.parseDottedKey(dottedKey); } /** * Join a list of keys into a single dotted key string. * * @param path The list of keys that form the path. * @return The path string. */ public static String joinKeyPath(List path) { requireNonNull(path); StringJoiner joiner = new StringJoiner("."); for (String key : path) { if (simpleKeyPattern.matcher(key).matches()) { joiner.add(key); } else { joiner.add("\"" + tomlEscape(key) + '\"'); } } return joiner.toString(); } /** * Get the canonical form of the dotted key. * * @param dottedKey A dotted key (e.g. {@code server.address.port}). * @return The canonical form of the dotted key. * @throws IllegalArgumentException If the dotted key cannot be parsed. */ public static String canonicalDottedKey(String dottedKey) { return joinKeyPath(parseDottedKey(dottedKey)); } /** * Escape a text string using the TOML escape sequences. * * @param text The text string to escape. * @return A {@link StringBuilder} holding the results of escaping the text. */ public static StringBuilder tomlEscape(String text) { final StringBuilder out = new StringBuilder(); for (int i = 0; i < text.length(); i++) { int codepoint = text.codePointAt(i); if (Character.charCount(codepoint) > 1) { out.append("\\U").append(String.format("%08x", codepoint)); ++i; continue; } char ch = Character.toChars(codepoint)[0]; if (ch == '\'') { out.append("\\'"); continue; } if (ch >= 0x20 && ch < 0x7F) { out.append(ch); continue; } switch (ch) { case '\t': out.append("\\t"); break; case '\b': out.append("\\b"); break; case '\n': out.append("\\n"); break; case '\r': out.append("\\r"); break; case '\f': out.append("\\f"); break; default: out.append("\\u").append(String.format("%04x", codepoint)); } } return out; } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/TomlArray.java000066400000000000000000000211641341750772100253250ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import static net.consensys.cava.toml.TomlType.typeNameFor; import java.io.IOException; import java.io.UncheckedIOException; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.util.List; /** * An array of TOML values. */ public interface TomlArray { /** * @return The size of the array. */ int size(); /** * @return {@code true} if the array is empty. */ boolean isEmpty(); /** * @return {@code true} if the array contains strings. */ boolean containsStrings(); /** * @return {@code true} if the array contains longs. */ boolean containsLongs(); /** * @return {@code true} if the array contains doubles. */ boolean containsDoubles(); /** * @return {@code true} if the array contains booleans. */ boolean containsBooleans(); /** * @return {@code true} if the array contains {@link OffsetDateTime}s. */ boolean containsOffsetDateTimes(); /** * @return {@code true} if the array contains {@link LocalDateTime}s. */ boolean containsLocalDateTimes(); /** * @return {@code true} if the array contains {@link LocalDate}s. */ boolean containsLocalDates(); /** * @return {@code true} if the array contains {@link LocalTime}s. */ boolean containsLocalTimes(); /** * @return {@code true} if the array contains arrays. */ boolean containsArrays(); /** * @return {@code true} if the array contains tables. */ boolean containsTables(); /** * Get a value at a specified index. * * @param index The array index. * @return The value. * @throws IndexOutOfBoundsException If the index is out of bounds. */ Object get(int index); /** * Get the position where a value is defined in the TOML document. * * @param index The array index. * @return The input position. * @throws IndexOutOfBoundsException If the index is out of bounds. */ TomlPosition inputPositionOf(int index); /** * Get a string at a specified index. * * @param index The array index. * @return The value. * @throws IndexOutOfBoundsException If the index is out of bounds. * @throws TomlInvalidTypeException If the value is not a long. */ default String getString(int index) { Object value = get(index); if (!(value instanceof String)) { throw new TomlInvalidTypeException("key at index " + index + " is a " + typeNameFor(value)); } return (String) value; } /** * Get a long at a specified index. * * @param index The array index. * @return The value. * @throws IndexOutOfBoundsException If the index is out of bounds. * @throws TomlInvalidTypeException If the value is not a long. */ default long getLong(int index) { Object value = get(index); if (!(value instanceof Long)) { throw new TomlInvalidTypeException("key at index " + index + " is a " + typeNameFor(value)); } return (Long) value; } /** * Get a double at a specified index. * * @param index The array index. * @return The value. * @throws IndexOutOfBoundsException If the index is out of bounds. * @throws TomlInvalidTypeException If the value is not a long. */ default double getDouble(int index) { Object value = get(index); if (!(value instanceof Double)) { throw new TomlInvalidTypeException("key at index " + index + " is a " + typeNameFor(value)); } return (Double) value; } /** * Get a boolean at a specified index. * * @param index The array index. * @return The value. * @throws IndexOutOfBoundsException If the index is out of bounds. * @throws TomlInvalidTypeException If the value is not a long. */ default boolean getBoolean(int index) { Object value = get(index); if (!(value instanceof Boolean)) { throw new TomlInvalidTypeException("key at index " + index + " is a " + typeNameFor(value)); } return (Boolean) value; } /** * Get an offset date time at a specified index. * * @param index The array index. * @return The value. * @throws IndexOutOfBoundsException If the index is out of bounds. * @throws TomlInvalidTypeException If the value is not an {@link OffsetDateTime}. */ default OffsetDateTime getOffsetDateTime(int index) { Object value = get(index); if (!(value instanceof OffsetDateTime)) { throw new TomlInvalidTypeException("key at index " + index + " is a " + typeNameFor(value)); } return (OffsetDateTime) value; } /** * Get a local date time at a specified index. * * @param index The array index. * @return The value. * @throws IndexOutOfBoundsException If the index is out of bounds. * @throws TomlInvalidTypeException If the value is not an {@link LocalDateTime}. */ default LocalDateTime getLocalDateTime(int index) { Object value = get(index); if (!(value instanceof LocalDateTime)) { throw new TomlInvalidTypeException("key at index " + index + " is a " + typeNameFor(value)); } return (LocalDateTime) value; } /** * Get a local date at a specified index. * * @param index The array index. * @return The value. * @throws IndexOutOfBoundsException If the index is out of bounds. * @throws TomlInvalidTypeException If the value is not an {@link LocalDate}. */ default LocalDate getLocalDate(int index) { Object value = get(index); if (!(value instanceof LocalDate)) { throw new TomlInvalidTypeException("key at index " + index + " is a " + typeNameFor(value)); } return (LocalDate) value; } /** * Get a local time at a specified index. * * @param index The array index. * @return The value. * @throws IndexOutOfBoundsException If the index is out of bounds. * @throws TomlInvalidTypeException If the value is not an {@link LocalTime}. */ default LocalTime getLocalTime(int index) { Object value = get(index); if (!(value instanceof LocalTime)) { throw new TomlInvalidTypeException("key at index " + index + " is a " + typeNameFor(value)); } return (LocalTime) value; } /** * Get an array at a specified index. * * @param index The array index. * @return The value. * @throws IndexOutOfBoundsException If the index is out of bounds. * @throws TomlInvalidTypeException If the value is not an array. */ default TomlArray getArray(int index) { Object value = get(index); if (!(value instanceof TomlArray)) { throw new TomlInvalidTypeException("key at index " + index + " is a " + typeNameFor(value)); } return (TomlArray) value; } /** * Get a table at a specified index. * * @param index The array index. * @return The value. * @throws IndexOutOfBoundsException If the index is out of bounds. * @throws TomlInvalidTypeException If the value is not a table. */ default TomlTable getTable(int index) { Object value = get(index); if (!(value instanceof TomlTable)) { throw new TomlInvalidTypeException("key at index " + index + " is a " + typeNameFor(value)); } return (TomlTable) value; } /** * Get the elements of this array as a {@link List}. * *

* Note that this does not do a deep conversion. If this array contains tables or arrays, they will be of type * {@link TomlTable} or {@link TomlArray} respectively. * * @return The elements of this array as a {@link List}. */ List toList(); /** * Return a representation of this array using JSON. * * @return A JSON representation of this array. */ default String toJson() { StringBuilder builder = new StringBuilder(); try { toJson(builder); } catch (IOException e) { // not reachable throw new UncheckedIOException(e); } return builder.toString(); } /** * Append a JSON representation of this array to the appendable output. * * @param appendable The appendable output. * @throws IOException If an IO error occurs. */ default void toJson(Appendable appendable) throws IOException { JsonSerializer.toJson(this, appendable); } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/TomlInvalidTypeException.java000066400000000000000000000014671341750772100303620ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; /** * An exception thrown when an invalid type is encountered. */ public class TomlInvalidTypeException extends RuntimeException { TomlInvalidTypeException(String message) { super(message); } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/TomlParseError.java000066400000000000000000000023451341750772100263330ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; /** * An error that occurred while parsing. */ public final class TomlParseError extends RuntimeException { private final TomlPosition position; TomlParseError(String message, TomlPosition position) { super(message); this.position = position; } TomlParseError(String message, TomlPosition position, Throwable cause) { super(message, cause); this.position = position; } /** * @return The position in the input where the error occurred. */ public TomlPosition position() { return position; } @Override public String toString() { return getMessage() + " (" + position + ")"; } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/TomlParseResult.java000066400000000000000000000020011341750772100265050ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import java.util.List; /** * The result from parsing a TOML document. */ public interface TomlParseResult extends TomlTable { /** * @return {@code true} if the TOML document contained errors. */ default boolean hasErrors() { return !(errors().isEmpty()); } /** * The errors that occurred during parsing. * * @return A list of errors. */ List errors(); } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/TomlPosition.java000066400000000000000000000045741341750772100260610ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.Token; /** * A position in an input document. */ public final class TomlPosition { private final int line; private final int column; /** * Create a position. * * @param line The line. * @param column The column. * @return A position. */ public static TomlPosition positionAt(int line, int column) { if (line < 1) { throw new IllegalArgumentException("line must be >= 1"); } if (column < 1) { throw new IllegalArgumentException("column must be >= 1"); } return new TomlPosition(line, column); } private TomlPosition(int line, int column) { this.line = line; this.column = column; } TomlPosition(ParserRuleContext ctx) { this(ctx, 0); } TomlPosition(ParserRuleContext ctx, int offset) { Token token = ctx.getStart(); this.line = token.getLine(); this.column = token.getCharPositionInLine() + 1 + offset; } /** * The line number. * *

* The first line of the document is line 1. * * @return The line number (1..). */ public int line() { return line; } /** * The column number. * *

* The first column of the document is column 1. * * @return The column number (1..). */ public int column() { return column; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof TomlPosition)) { return false; } TomlPosition other = (TomlPosition) obj; return this.line == other.line && this.column == other.column; } @Override public int hashCode() { return 31 * line + column; } @Override public String toString() { return "line " + line + ", column " + column; } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/TomlTable.java000066400000000000000000001171621341750772100253020ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import static java.util.Objects.requireNonNull; import static net.consensys.cava.toml.Parser.parseDottedKey; import static net.consensys.cava.toml.Toml.joinKeyPath; import static net.consensys.cava.toml.TomlType.typeNameFor; import java.io.IOException; import java.io.UncheckedIOException; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BooleanSupplier; import java.util.function.DoubleSupplier; import java.util.function.LongSupplier; import java.util.function.Supplier; import java.util.stream.Collectors; import javax.annotation.Nullable; /** * An interface for accessing data stored in Tom's Obvious, Minimal Language (TOML). */ public interface TomlTable { /** * @return The number of entries in tis table. */ int size(); /** * @return {@code true} if there are no entries in this table. */ boolean isEmpty(); /** * Check if a key was set in the TOML document. * * @param dottedKey A dotted key (e.g. {@code "server.port"}). * @return {@code true} if the key was set in the TOML document. * @throws IllegalArgumentException If the key cannot be parsed. */ default boolean contains(String dottedKey) { requireNonNull(dottedKey); return contains(parseDottedKey(dottedKey)); } /** * Check if a key was set in the TOML document. * * @param path The key path. * @return {@code true} if the key was set in the TOML document. */ default boolean contains(List path) { try { return get(path) != null; } catch (TomlInvalidTypeException e) { return false; } } /** * Get the keys of this table. * *

* The returned set contains only immediate keys to this table, and not dotted keys or key paths. For a complete view * of keys available in the TOML document, use {@link #dottedKeySet()} or {@link #keyPathSet()}. * * @return A set containing the keys of this table. */ Set keySet(); /** * Get all the dotted keys of this table. * *

* Paths to intermediary and empty tables are not returned. To include these, use {@link #dottedKeySet(boolean)}. * * @return A set containing all the dotted keys of this table. */ default Set dottedKeySet() { return keyPathSet().stream().map(Toml::joinKeyPath).collect(Collectors.toSet()); } /** * Get all the dotted keys of this table. * * @param includeTables If {@code true}, also include paths to intermediary and empty tables. * @return A set containing all the dotted keys of this table. */ default Set dottedKeySet(boolean includeTables) { return keyPathSet(includeTables).stream().map(Toml::joinKeyPath).collect(Collectors.toSet()); } /** * Get all the paths in this table. * *

* Paths to intermediary and empty tables are not returned. To include these, use {@link #keyPathSet(boolean)}. * * @return A set containing all the key paths of this table. */ default Set> keyPathSet() { return keyPathSet(false); } /** * Get all the paths in this table. * * @param includeTables If {@code true}, also include paths to intermediary and empty tables. * @return A set containing all the key paths of this table. */ Set> keyPathSet(boolean includeTables); /** * Get a value from the TOML document. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @return The value, or {@code null} if no value was set in the TOML document. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If any element of the path preceding the final key is not a table. */ @Nullable default Object get(String dottedKey) { requireNonNull(dottedKey); return get(parseDottedKey(dottedKey)); } /** * Get a value from the TOML document. * * @param path The key path. * @return The value, or {@code null} if no value was set in the TOML document. * @throws TomlInvalidTypeException If any element of the path preceding the final key is not a table. */ @Nullable Object get(List path); /** * Get the position where a key is defined in the TOML document. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @return The input position, or {@code null} if the key was not set in the TOML document. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If any element of the path preceding the final key is not a table. */ @Nullable default TomlPosition inputPositionOf(String dottedKey) { requireNonNull(dottedKey); return inputPositionOf(parseDottedKey(dottedKey)); } /** * Get the position where a key is defined in the TOML document. * * @param path The key path. * @return The input position, or {@code null} if the key was not set in the TOML document. * @throws TomlInvalidTypeException If any element of the path preceding the final key is not a table. */ @Nullable TomlPosition inputPositionOf(List path); /** * Check if a value in the TOML document is a string. * * @param dottedKey A dotted key (e.g. {@code "server.address.hostname"}). * @return {@code true} if the value can be obtained as a string. * @throws IllegalArgumentException If the key cannot be parsed. */ default boolean isString(String dottedKey) { requireNonNull(dottedKey); return isString(parseDottedKey(dottedKey)); } /** * Check if a value in the TOML document is a string. * * @param path The key path. * @return {@code true} if the value can be obtained as a string. */ default boolean isString(List path) { Object value; try { value = get(path); } catch (TomlInvalidTypeException e) { return false; } return value instanceof String; } /** * Get a string from the TOML document. * * @param dottedKey A dotted key (e.g. {@code "server.address.hostname"}). * @return The value, or {@code null} if no value was set in the TOML document. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not a string, or any element of the path preceding the * final key is not a table. */ @Nullable default String getString(String dottedKey) { requireNonNull(dottedKey); return getString(parseDottedKey(dottedKey)); } /** * Get a string from the TOML document. * * @param path A dotted key (e.g. {@code "server.address.hostname"}). * @return The value, or {@code null} if no value was set in the TOML document. * @throws TomlInvalidTypeException If the value is present but not a string, or any element of the path preceding the * final key is not a table. */ @Nullable default String getString(List path) { Object value = get(path); if (value == null) { return null; } if (!(value instanceof String)) { throw new TomlInvalidTypeException("Value of '" + joinKeyPath(path) + "' is a " + typeNameFor(value)); } return (String) value; } /** * Get a string from the TOML document, or return a default. * * @param dottedKey A dotted key (e.g. {@code "server.address.hostname"}). * @param defaultValue A supplier for the default value. * @return The value, or the default. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not a string, or any element of the path preceding the * final key is not a table. */ default String getString(String dottedKey, Supplier defaultValue) { requireNonNull(dottedKey); return getString(parseDottedKey(dottedKey), defaultValue); } /** * Get a string from the TOML document, or return a default. * * @param path The key path. * @param defaultValue A supplier for the default value. * @return The value, or the default. * @throws TomlInvalidTypeException If the value is present but not a string, or any element of the path preceding the * final key is not a table. */ default String getString(List path, Supplier defaultValue) { requireNonNull(defaultValue); String value = getString(path); if (value != null) { return value; } return defaultValue.get(); } /** * Check if a value in the TOML document is a long. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @return {@code true} if the value can be obtained as a long. * @throws IllegalArgumentException If the key cannot be parsed. */ default boolean isLong(String dottedKey) { requireNonNull(dottedKey); return isLong(parseDottedKey(dottedKey)); } /** * Check if a value in the TOML document is a long. * * @param path The key path. * @return {@code true} if the value can be obtained as a long. */ default boolean isLong(List path) { Object value; try { value = get(path); } catch (TomlInvalidTypeException e) { return false; } return value instanceof Long; } /** * Get a long from the TOML document. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @return The value, or {@code null} if no value was set in the TOML document. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not a long, or any element of the path preceding the * final key is not a table. */ @Nullable default Long getLong(String dottedKey) { requireNonNull(dottedKey); return getLong(parseDottedKey(dottedKey)); } /** * Get a long from the TOML document. * * @param path The key path. * @return The value, or {@code null} if no value was set in the TOML document. * @throws TomlInvalidTypeException If the value is present but not a long, or any element of the path preceding the * final key is not a table. */ @Nullable default Long getLong(List path) { Object value = get(path); if (value == null) { return null; } if (!(value instanceof Long)) { throw new TomlInvalidTypeException("Value of '" + joinKeyPath(path) + "' is a " + typeNameFor(value)); } return (Long) value; } /** * Get a long from the TOML document, or return a default. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @param defaultValue A supplier for the default value. * @return The value, or the default. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not a long, or any element of the path preceding the * final key is not a table. */ default long getLong(String dottedKey, LongSupplier defaultValue) { requireNonNull(dottedKey); return getLong(parseDottedKey(dottedKey), defaultValue); } /** * Get a long from the TOML document, or return a default. * * @param path The key path. * @param defaultValue A supplier for the default value. * @return The value, or the default. * @throws TomlInvalidTypeException If the value is present but not a long, or any element of the path preceding the * final key is not a table. */ default long getLong(List path, LongSupplier defaultValue) { requireNonNull(defaultValue); Long value = getLong(path); if (value != null) { return value; } return defaultValue.getAsLong(); } /** * Check if a value in the TOML document is a double. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @return {@code true} if the value can be obtained as a double. * @throws IllegalArgumentException If the key cannot be parsed. */ default boolean isDouble(String dottedKey) { requireNonNull(dottedKey); return isDouble(parseDottedKey(dottedKey)); } /** * Check if a value in the TOML document is a double. * * @param path The key path. * @return {@code true} if the value can be obtained as a double. */ default boolean isDouble(List path) { Object value; try { value = get(path); } catch (TomlInvalidTypeException e) { return false; } return value instanceof Double; } /** * Get a double from the TOML document. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @return The value, or {@code null} if no value was set in the TOML document. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not a double, or any element of the path preceding the * final key is not a table. */ @Nullable default Double getDouble(String dottedKey) { requireNonNull(dottedKey); return getDouble(parseDottedKey(dottedKey)); } /** * Get a double from the TOML document. * * @param path A dotted key. * @return The value, or {@code null} if no value was set in the TOML document. * @throws TomlInvalidTypeException If the value is present but not a double, or any element of the path preceding the * final key is not a table. */ @Nullable default Double getDouble(List path) { Object value = get(path); if (value == null) { return null; } if (!(value instanceof Double)) { throw new TomlInvalidTypeException("Value of '" + joinKeyPath(path) + "' is a " + typeNameFor(value)); } return (Double) value; } /** * Get a double from the TOML document, or return a default. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @param defaultValue A supplier for the default value. * @return The value, or the default. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not a double, or any element of the path preceding the * final key is not a table. */ default double getDouble(String dottedKey, DoubleSupplier defaultValue) { requireNonNull(dottedKey); return getDouble(parseDottedKey(dottedKey), defaultValue); } /** * Get a double from the TOML document, or return a default. * * @param path The key path. * @param defaultValue A supplier for the default value. * @return The value, or the default. * @throws TomlInvalidTypeException If the value is present but not a double, or any element of the path preceding the * final key is not a table. */ default double getDouble(List path, DoubleSupplier defaultValue) { requireNonNull(defaultValue); Double value = getDouble(path); if (value != null) { return value; } return defaultValue.getAsDouble(); } /** * Check if a value in the TOML document is a boolean. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @return {@code true} if the value can be obtained as a boolean. * @throws IllegalArgumentException If the key cannot be parsed. */ default boolean isBoolean(String dottedKey) { requireNonNull(dottedKey); return isBoolean(parseDottedKey(dottedKey)); } /** * Check if a value in the TOML document is a boolean. * * @param path The key path. * @return {@code true} if the value can be obtained as a boolean. */ default boolean isBoolean(List path) { Object value; try { value = get(path); } catch (TomlInvalidTypeException e) { return false; } return value instanceof Boolean; } /** * Get a boolean from the TOML document. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @return The value, or {@code null} if no value was set in the TOML document. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not a boolean, or any element of the path preceding * the final key is not a table. */ @Nullable default Boolean getBoolean(String dottedKey) { requireNonNull(dottedKey); return getBoolean(parseDottedKey(dottedKey)); } /** * Get a boolean from the TOML document. * * @param path The key path. * @return The value, or {@code null} if no value was set in the TOML document. * @throws TomlInvalidTypeException If the value is present but not a boolean, or any element of the path preceding * the final key is not a table. */ @Nullable default Boolean getBoolean(List path) { Object value = get(path); if (value == null) { return null; } if (!(value instanceof Boolean)) { throw new TomlInvalidTypeException("Value of '" + joinKeyPath(path) + "' is a " + typeNameFor(value)); } return (Boolean) value; } /** * Get a boolean from the TOML document, or return a default. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @param defaultValue A supplier for the default value. * @return The value, or the default. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not a boolean, or any element of the path preceding * the final key is not a table. */ default boolean getBoolean(String dottedKey, BooleanSupplier defaultValue) { requireNonNull(dottedKey); return getBoolean(parseDottedKey(dottedKey), defaultValue); } /** * Get a boolean from the TOML document, or return a default. * * @param path The key path. * @param defaultValue A supplier for the default value. * @return The value, or the default. * @throws TomlInvalidTypeException If the value is present but not a boolean, or any element of the path preceding * the final key is not a table. */ default boolean getBoolean(List path, BooleanSupplier defaultValue) { requireNonNull(defaultValue); Boolean value = getBoolean(path); if (value != null) { return value; } return defaultValue.getAsBoolean(); } /** * Check if a value in the TOML document is an {@link OffsetDateTime}. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @return {@code true} if the value can be obtained as an {@link OffsetDateTime}. * @throws IllegalArgumentException If the key cannot be parsed. */ default boolean isOffsetDateTime(String dottedKey) { requireNonNull(dottedKey); return isOffsetDateTime(parseDottedKey(dottedKey)); } /** * Check if a value in the TOML document is an {@link OffsetDateTime}. * * @param path The key path. * @return {@code true} if the value can be obtained as an {@link OffsetDateTime}. */ default boolean isOffsetDateTime(List path) { Object value; try { value = get(path); } catch (TomlInvalidTypeException e) { return false; } return value instanceof OffsetDateTime; } /** * Get an offset date time from the TOML document. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @return The value, or {@code null} if no value was set in the TOML document. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not an {@link OffsetDateTime}, or any element of the * path preceding the final key is not a table. */ @Nullable default OffsetDateTime getOffsetDateTime(String dottedKey) { requireNonNull(dottedKey); return getOffsetDateTime(parseDottedKey(dottedKey)); } /** * Get an offset date time from the TOML document. * * @param path The key path. * @return The value, or {@code null} if no value was set in the TOML document. * @throws TomlInvalidTypeException If the value is present but not an {@link OffsetDateTime}, or any element of the * path preceding the final key is not a table. */ @Nullable default OffsetDateTime getOffsetDateTime(List path) { Object value = get(path); if (value == null) { return null; } if (!(value instanceof OffsetDateTime)) { throw new TomlInvalidTypeException("Value of '" + joinKeyPath(path) + "' is a " + typeNameFor(value)); } return (OffsetDateTime) value; } /** * Get an offset date time from the TOML document, or return a default. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @param defaultValue A supplier for the default value. * @return The value, or the default. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not an {@link OffsetDateTime}, or any element of the * path preceding the final key is not a table. */ default OffsetDateTime getOffsetDateTime(String dottedKey, Supplier defaultValue) { requireNonNull(dottedKey); return getOffsetDateTime(parseDottedKey(dottedKey), defaultValue); } /** * Get an offset date time from the TOML document, or return a default. * * @param path The key path. * @param defaultValue A supplier for the default value. * @return The value, or the default. * @throws TomlInvalidTypeException If the value is present but not an {@link OffsetDateTime}, or any element of the * path preceding the final key is not a table. */ default OffsetDateTime getOffsetDateTime(List path, Supplier defaultValue) { requireNonNull(defaultValue); OffsetDateTime value = getOffsetDateTime(path); if (value != null) { return value; } return defaultValue.get(); } /** * Check if a value in the TOML document is a {@link LocalDateTime}. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @return {@code true} if the value can be obtained as a {@link LocalDateTime}. * @throws IllegalArgumentException If the key cannot be parsed. */ default boolean isLocalDateTime(String dottedKey) { requireNonNull(dottedKey); return isLocalDateTime(parseDottedKey(dottedKey)); } /** * Check if a value in the TOML document is a {@link LocalDateTime}. * * @param path The key path. * @return {@code true} if the value can be obtained as a {@link LocalDateTime}. */ default boolean isLocalDateTime(List path) { Object value; try { value = get(path); } catch (TomlInvalidTypeException e) { return false; } return value instanceof LocalDateTime; } /** * Get a local date time from the TOML document. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @return The value, or {@code null} if no value was set in the TOML document. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not a {@link LocalDateTime}, or any element of the * path preceding the final key is not a table. */ @Nullable default LocalDateTime getLocalDateTime(String dottedKey) { requireNonNull(dottedKey); return getLocalDateTime(parseDottedKey(dottedKey)); } /** * Get a local date time from the TOML document. * * @param path The key path. * @return The value, or {@code null} if no value was set in the TOML document. * @throws TomlInvalidTypeException If the value is present but not a {@link LocalDateTime}, or any element of the * path preceding the final key is not a table. */ @Nullable default LocalDateTime getLocalDateTime(List path) { Object value = get(path); if (value == null) { return null; } if (!(value instanceof LocalDateTime)) { throw new TomlInvalidTypeException("Value of '" + joinKeyPath(path) + "' is a " + typeNameFor(value)); } return (LocalDateTime) value; } /** * Get a local date time from the TOML document, or return a default. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @param defaultValue A supplier for the default value. * @return The value, or the default. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not a {@link LocalDateTime}, or any element of the * path preceding the final key is not a table. */ default LocalDateTime getLocalDateTime(String dottedKey, Supplier defaultValue) { requireNonNull(dottedKey); return getLocalDateTime(parseDottedKey(dottedKey), defaultValue); } /** * Get a local date time from the TOML document, or return a default. * * @param path The key path. * @param defaultValue A supplier for the default value. * @return The value, or the default. * @throws TomlInvalidTypeException If the value is present but not a {@link LocalDateTime}, or any element of the * path preceding the final key is not a table. */ default LocalDateTime getLocalDateTime(List path, Supplier defaultValue) { requireNonNull(defaultValue); LocalDateTime value = getLocalDateTime(path); if (value != null) { return value; } return defaultValue.get(); } /** * Check if a value in the TOML document is a {@link LocalDate}. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @return {@code true} if the value can be obtained as a {@link LocalDate}. * @throws IllegalArgumentException If the key cannot be parsed. */ default boolean isLocalDate(String dottedKey) { requireNonNull(dottedKey); return isLocalDate(parseDottedKey(dottedKey)); } /** * Check if a value in the TOML document is a {@link LocalDate}. * * @param path The key path. * @return {@code true} if the value can be obtained as a {@link LocalDate}. */ default boolean isLocalDate(List path) { Object value; try { value = get(path); } catch (TomlInvalidTypeException e) { return false; } return value instanceof LocalDate; } /** * Get a local date from the TOML document. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @return The value, or {@code null} if no value was set in the TOML document. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not a {@link LocalDate}, or any element of the path * preceding the final key is not a table. */ @Nullable default LocalDate getLocalDate(String dottedKey) { requireNonNull(dottedKey); return getLocalDate(parseDottedKey(dottedKey)); } /** * Get a local date from the TOML document. * * @param path The key path. * @return The value, or {@code null} if no value was set in the TOML document. * @throws TomlInvalidTypeException If the value is present but not a {@link LocalDate}, or any element of the path * preceding the final key is not a table. */ @Nullable default LocalDate getLocalDate(List path) { Object value = get(path); if (value == null) { return null; } if (!(value instanceof LocalDate)) { throw new TomlInvalidTypeException("Value of '" + joinKeyPath(path) + "' is a " + typeNameFor(value)); } return (LocalDate) value; } /** * Get a local date from the TOML document, or return a default. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @param defaultValue A supplier for the default value. * @return The value, or the default. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not a {@link LocalDate}, or any element of the path * preceding the final key is not a table. */ default LocalDate getLocalDate(String dottedKey, Supplier defaultValue) { requireNonNull(dottedKey); return getLocalDate(parseDottedKey(dottedKey), defaultValue); } /** * Get a local date from the TOML document, or return a default. * * @param path The key path. * @param defaultValue A supplier for the default value. * @return The value, or the default. * @throws TomlInvalidTypeException If the value is present but not a {@link LocalDate}, or any element of the path * preceding the final key is not a table. */ default LocalDate getLocalDate(List path, Supplier defaultValue) { requireNonNull(defaultValue); LocalDate value = getLocalDate(path); if (value != null) { return value; } return defaultValue.get(); } /** * Check if a value in the TOML document is a {@link LocalTime}. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @return {@code true} if the value can be obtained as a {@link LocalTime}. * @throws IllegalArgumentException If the key cannot be parsed. */ default boolean isLocalTime(String dottedKey) { requireNonNull(dottedKey); return isLocalTime(parseDottedKey(dottedKey)); } /** * Check if a value in the TOML document is a {@link LocalTime}. * * @param path The key path. * @return {@code true} if the value can be obtained as a {@link LocalTime}. */ default boolean isLocalTime(List path) { Object value; try { value = get(path); } catch (TomlInvalidTypeException e) { return false; } return value instanceof LocalTime; } /** * Get a local time from the TOML document. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @return The value, or {@code null} if no value was set in the TOML document. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not a {@link LocalTime}, or any element of the path * preceding the final key is not a table. */ @Nullable default LocalTime getLocalTime(String dottedKey) { requireNonNull(dottedKey); return getLocalTime(parseDottedKey(dottedKey)); } /** * Get a local time from the TOML document. * * @param path The key path. * @return The value, or {@code null} if no value was set in the TOML document. * @throws TomlInvalidTypeException If the value is present but not a {@link LocalTime}, or any element of the path * preceding the final key is not a table. */ @Nullable default LocalTime getLocalTime(List path) { Object value = get(path); if (value == null) { return null; } if (!(value instanceof LocalTime)) { throw new TomlInvalidTypeException("Value of '" + joinKeyPath(path) + "' is a " + typeNameFor(value)); } return (LocalTime) value; } /** * Get a local time from the TOML document, or return a default. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @param defaultValue A supplier for the default value. * @return The value, or the default. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not a {@link LocalTime}, or any element of the path * preceding the final key is not a table. */ default LocalTime getLocalTime(String dottedKey, Supplier defaultValue) { requireNonNull(dottedKey); return getLocalTime(parseDottedKey(dottedKey), defaultValue); } /** * Get a local time from the TOML document, or return a default. * * @param path The key path. * @param defaultValue A supplier for the default value. * @return The value, or the default. * @throws TomlInvalidTypeException If the value is present but not a {@link LocalTime}, or any element of the path * preceding the final key is not a table. */ default LocalTime getLocalTime(List path, Supplier defaultValue) { requireNonNull(defaultValue); LocalTime value = getLocalTime(path); if (value != null) { return value; } return defaultValue.get(); } /** * Check if a value in the TOML document is an array. * * @param dottedKey A dotted key (e.g. {@code "server.addresses"}). * @return {@code true} if the value can be obtained as an array. * @throws IllegalArgumentException If the key cannot be parsed. */ default boolean isArray(String dottedKey) { requireNonNull(dottedKey); return isArray(parseDottedKey(dottedKey)); } /** * Check if a value in the TOML document is an array. * * @param path The key path. * @return {@code true} if the value can be obtained as an array. */ default boolean isArray(List path) { Object value; try { value = get(path); } catch (TomlInvalidTypeException e) { return false; } return value instanceof TomlArray; } /** * Get an array from the TOML document. * * @param dottedKey A dotted key (e.g. {@code "server.addresses"}). * @return The value, or {@code null} if no value was set in the TOML document. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not an array, or any element of the path preceding the * final key is not a table. */ @Nullable default TomlArray getArray(String dottedKey) { requireNonNull(dottedKey); return getArray(parseDottedKey(dottedKey)); } /** * Get an array from the TOML document. * * @param path The key path. * @return The value, or {@code null} if no value was set in the TOML document. * @throws TomlInvalidTypeException If the value is present but not an array, or any element of the path preceding the * final key is not a table. */ @Nullable default TomlArray getArray(List path) { Object value = get(path); if (value == null) { return null; } if (!(value instanceof TomlArray)) { throw new TomlInvalidTypeException("Value of '" + joinKeyPath(path) + "' is a " + typeNameFor(value)); } return (TomlArray) value; } /** * Get an array from the TOML document. * * @param dottedKey A dotted key (e.g. {@code "server.addresses"}). * @return The value, or an empty list if no list was set in the TOML document. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not an array, or any element of the path preceding the * final key is not a table. */ default TomlArray getArrayOrEmpty(String dottedKey) { requireNonNull(dottedKey); return getArrayOrEmpty(parseDottedKey(dottedKey)); } /** * Get an array from the TOML document. * * @param path The key path. * @return The value, or an empty list if no list was set in the TOML document. * @throws TomlInvalidTypeException If the value is present but not an array, or any element of the path preceding the * final key is not a table. */ default TomlArray getArrayOrEmpty(List path) { TomlArray value = getArray(path); if (value != null) { return value; } return MutableTomlArray.EMPTY; } /** * Check if a value in the TOML document is a table. * * @param dottedKey A dotted key (e.g. {@code "server.address"}). * @return {@code true} if the value can be obtained as a table. * @throws IllegalArgumentException If the key cannot be parsed. */ default boolean isTable(String dottedKey) { requireNonNull(dottedKey); return isTable(parseDottedKey(dottedKey)); } /** * Check if a value in the TOML document is a table. * * @param path The key path. * @return {@code true} if the value can be obtained as a table. */ default boolean isTable(List path) { Object value; try { value = get(path); } catch (TomlInvalidTypeException e) { return false; } return value instanceof TomlTable; } /** * Get a table from the TOML document. * * @param dottedKey A dotted key (e.g. {@code "server.address"}). * @return The value, or {@code null} if no value was set in the TOML document. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not a table, or any element of the path preceding the * final key is not a table. */ @Nullable default TomlTable getTable(String dottedKey) { requireNonNull(dottedKey); return getTable(parseDottedKey(dottedKey)); } /** * Get a table from the TOML document. * * @param path The key path. * @return The value, or {@code null} if no value was set in the TOML document. * @throws TomlInvalidTypeException If the value is present but not a table, or any element of the path preceding the * final key is not a table. */ @Nullable default TomlTable getTable(List path) { Object value = get(path); if (value == null) { return null; } if (!(value instanceof TomlTable)) { throw new TomlInvalidTypeException("Value of '" + joinKeyPath(path) + "' is a " + typeNameFor(value)); } return (TomlTable) value; } /** * Get a table from the TOML document. * * @param dottedKey A dotted key (e.g. {@code "server.address.port"}). * @return The value, or an empty table if no value was set in the TOML document. * @throws IllegalArgumentException If the key cannot be parsed. * @throws TomlInvalidTypeException If the value is present but not a table, or any element of the path preceding the * final key is not a table. */ default TomlTable getTableOrEmpty(String dottedKey) { requireNonNull(dottedKey); return getTableOrEmpty(parseDottedKey(dottedKey)); } /** * Get a table from the TOML document. * * @param path The key path. * @return The value, or an empty table if no value was set in the TOML document. * @throws TomlInvalidTypeException If the value is present but not a table, or any element of the path preceding the * final key is not a table. */ default TomlTable getTableOrEmpty(List path) { TomlTable value = getTable(path); if (value != null) { return value; } return MutableTomlTable.EMPTY; } /** * Get the elements of this array as a {@link Map}. * *

* Note that this does not do a deep conversion. If this array contains tables or arrays, they will be of type * {@link TomlTable} or {@link TomlArray} respectively. * * @return The elements of this array as a {@link Map}. */ Map toMap(); /** * Return a representation of this table using JSON. * * @return A JSON representation of this table. */ default String toJson() { StringBuilder builder = new StringBuilder(); try { toJson(builder); } catch (IOException e) { // not reachable throw new UncheckedIOException(e); } return builder.toString(); } /** * Append a JSON representation of this table to the appendable output. * * @param appendable The appendable output. * @throws IOException If an IO error occurs. */ default void toJson(Appendable appendable) throws IOException { JsonSerializer.toJson(this, appendable); } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/TomlType.java000066400000000000000000000035721341750772100251730ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.util.Arrays; import java.util.Optional; enum TomlType { STRING("string", String.class), INTEGER("integer", Long.class), FLOAT("float", Double.class), BOOLEAN("boolean", Boolean.class), OFFSET_DATE_TIME("offset date-time", OffsetDateTime.class), LOCAL_DATE_TIME("local date-time", LocalDateTime.class), LOCAL_DATE("local date", LocalDate.class), LOCAL_TIME("local time", LocalTime.class), ARRAY("array", TomlArray.class), TABLE("table", TomlTable.class); private final String name; private final Class clazz; TomlType(String name, Class clazz) { this.name = name; this.clazz = clazz; } static Optional typeFor(Object obj) { return typeForClass(obj.getClass()); } static Optional typeForClass(Class clazz) { return Arrays.stream(values()).filter(t -> t.clazz.isAssignableFrom(clazz)).findAny(); } static String typeNameFor(Object obj) { return typeNameForClass(obj.getClass()); } static String typeNameForClass(Class clazz) { return typeForClass(clazz).map(t -> t.name).orElseGet(clazz::getSimpleName); } public String typeName() { return name; } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/TomlVersion.java000066400000000000000000000037201341750772100256720ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import javax.annotation.Nullable; /** * Supported TOML specification versions. */ public enum TomlVersion { /** * The 0.4.0 version of TOML. * *

* This specification can be found at https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md. */ V0_4_0(null), /** * The 0.5.0 version of TOML. * *

* This specification can be found at https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.5.0.md. */ V0_5_0(null), /** * The latest stable specification of TOML. */ LATEST(V0_5_0), /** * The head (development) specification of TOML. * *

* The latest specification can be found at https://github.com/toml-lang/toml/blob/master/README.md. * *

* Note: As the specification is under active development, this implementation may not match the latest changes. */ HEAD(null); final TomlVersion canonical; TomlVersion(@Nullable TomlVersion canonical) { this.canonical = canonical != null ? canonical : this; } boolean after(TomlVersion other) { return this.ordinal() > other.ordinal(); } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/ValueVisitor.java000066400000000000000000000136251341750772100260520ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import net.consensys.cava.toml.internal.TomlParser.ArrayContext; import net.consensys.cava.toml.internal.TomlParser.ArrayValuesContext; import net.consensys.cava.toml.internal.TomlParser.BinIntContext; import net.consensys.cava.toml.internal.TomlParser.DecIntContext; import net.consensys.cava.toml.internal.TomlParser.FalseBoolContext; import net.consensys.cava.toml.internal.TomlParser.HexIntContext; import net.consensys.cava.toml.internal.TomlParser.InlineTableContext; import net.consensys.cava.toml.internal.TomlParser.InlineTableValuesContext; import net.consensys.cava.toml.internal.TomlParser.LocalDateContext; import net.consensys.cava.toml.internal.TomlParser.LocalDateTimeContext; import net.consensys.cava.toml.internal.TomlParser.LocalTimeContext; import net.consensys.cava.toml.internal.TomlParser.OctIntContext; import net.consensys.cava.toml.internal.TomlParser.OffsetDateTimeContext; import net.consensys.cava.toml.internal.TomlParser.RegularFloatContext; import net.consensys.cava.toml.internal.TomlParser.RegularFloatInfContext; import net.consensys.cava.toml.internal.TomlParser.RegularFloatNaNContext; import net.consensys.cava.toml.internal.TomlParser.StringContext; import net.consensys.cava.toml.internal.TomlParser.TrueBoolContext; import net.consensys.cava.toml.internal.TomlParserBaseVisitor; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.regex.Pattern; import org.antlr.v4.runtime.ParserRuleContext; final class ValueVisitor extends TomlParserBaseVisitor { private static final Pattern zeroFloat = Pattern.compile("[+-]?0+(\\.[+-]?0*)?([eE].*)?"); @Override public Object visitString(StringContext ctx) { return ctx.accept(new QuotedStringVisitor()).toString(); } @Override public Object visitDecInt(DecIntContext ctx) { return toLong(ctx.getText().replaceAll("_", ""), 10, ctx); } @Override public Object visitHexInt(HexIntContext ctx) { return toLong(ctx.getText().substring(2).replaceAll("_", ""), 16, ctx); } @Override public Object visitOctInt(OctIntContext ctx) { return toLong(ctx.getText().substring(2).replaceAll("_", ""), 8, ctx); } @Override public Object visitBinInt(BinIntContext ctx) { return toLong(ctx.getText().substring(2).replaceAll("_", ""), 2, ctx); } private Long toLong(String s, int radix, ParserRuleContext ctx) { try { return Long.valueOf(s, radix); } catch (NumberFormatException e) { throw new TomlParseError("Integer is too large", new TomlPosition(ctx)); } } @Override public Object visitRegularFloat(RegularFloatContext ctx) { return toDouble(ctx.getText().replaceAll("_", ""), ctx); } @Override public Object visitRegularFloatInf(RegularFloatInfContext ctx) { return (ctx.getText().startsWith("-")) ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; } @Override public Object visitRegularFloatNaN(RegularFloatNaNContext ctx) { return Double.NaN; } private Double toDouble(String s, ParserRuleContext ctx) { try { Double value = Double.valueOf(s); if (value == Double.POSITIVE_INFINITY || value == Double.NEGATIVE_INFINITY) { throw new TomlParseError("Float is too large", new TomlPosition(ctx)); } if (value == 0d && !zeroFloat.matcher(s).matches()) { throw new TomlParseError("Float is too small", new TomlPosition(ctx)); } return value; } catch (NumberFormatException e) { throw new TomlParseError("Invalid floating point number: " + e.getMessage(), new TomlPosition(ctx)); } } @Override public Object visitTrueBool(TrueBoolContext ctx) { return Boolean.TRUE; } @Override public Object visitFalseBool(FalseBoolContext ctx) { return Boolean.FALSE; } @Override public Object visitOffsetDateTime(OffsetDateTimeContext ctx) { LocalDate date = ctx.date().accept(new LocalDateVisitor()); LocalTime time = ctx.time().accept(new LocalTimeVisitor()); ZoneOffset offset = ctx.timeOffset().accept(new ZoneOffsetVisitor()); return OffsetDateTime.of(date, time, offset); } @Override public Object visitLocalDateTime(LocalDateTimeContext ctx) { LocalDate date = ctx.date().accept(new LocalDateVisitor()); LocalTime time = ctx.time().accept(new LocalTimeVisitor()); return LocalDateTime.of(date, time); } @Override public Object visitLocalDate(LocalDateContext ctx) { return ctx.date().accept(new LocalDateVisitor()); } @Override public Object visitLocalTime(LocalTimeContext ctx) { return ctx.time().accept(new LocalTimeVisitor()); } @Override public Object visitArray(ArrayContext ctx) { ArrayValuesContext valuesContext = ctx.arrayValues(); if (valuesContext == null) { return MutableTomlArray.EMPTY; } return valuesContext.accept(new ArrayVisitor()); } @Override public Object visitInlineTable(InlineTableContext ctx) { InlineTableValuesContext valuesContext = ctx.inlineTableValues(); if (valuesContext == null) { return MutableTomlTable.EMPTY; } return valuesContext.accept(new InlineTableVisitor()); } @Override protected Object aggregateResult(Object aggregate, Object nextResult) { return nextResult; } @Override protected Object defaultResult() { return null; } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/ZoneOffsetVisitor.java000066400000000000000000000056001341750772100270520ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import net.consensys.cava.toml.internal.TomlParser.HourOffsetContext; import net.consensys.cava.toml.internal.TomlParser.MinuteOffsetContext; import net.consensys.cava.toml.internal.TomlParserBaseVisitor; import java.time.DateTimeException; import java.time.ZoneOffset; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ErrorNode; final class ZoneOffsetVisitor extends TomlParserBaseVisitor { private int hours = 0; private int minutes = 0; @Override public ZoneOffset visitHourOffset(HourOffsetContext ctx) { int hours; try { hours = Integer.parseInt(ctx.getText()); } catch (NumberFormatException e) { throw new TomlParseError("Invalid zone offset", new TomlPosition(ctx), e); } if (hours < -18 || hours > 18) { throw new TomlParseError("Invalid zone offset hours (valid range -18..+18)", new TomlPosition(ctx)); } ZoneOffset offset = toZoneOffset(hours, minutes, ctx, 0); this.hours = hours; return offset; } @Override public ZoneOffset visitMinuteOffset(MinuteOffsetContext ctx) { int minutes; try { minutes = Integer.parseInt(ctx.getText()); } catch (NumberFormatException e) { throw new TomlParseError("Invalid zone offset", new TomlPosition(ctx), e); } if (minutes < 0 || minutes > 59) { throw new TomlParseError("Invalid zone offset minutes (valid range 0..59)", new TomlPosition(ctx)); } ZoneOffset offset = toZoneOffset(hours, minutes, ctx, -4); this.minutes = minutes; return offset; } private static ZoneOffset toZoneOffset(int hours, int minutes, ParserRuleContext ctx, int offset) { try { return ZoneOffset.ofHoursMinutes(hours, (hours < 0) ? -minutes : minutes); } catch (DateTimeException e) { throw new TomlParseError("Invalid zone offset (valid range -18:00..+18:00)", new TomlPosition(ctx, offset), e); } } @Override public ZoneOffset visitErrorNode(ErrorNode node) { return null; } @Override protected ZoneOffset aggregateResult(ZoneOffset aggregate, ZoneOffset nextResult) { return aggregate == null ? null : nextResult; } @Override protected ZoneOffset defaultResult() { return ZoneOffset.ofHoursMinutes(this.hours, (this.hours < 0) ? -this.minutes : this.minutes); } } cava-0.6.0/toml/src/main/java/net/consensys/cava/toml/package-info.java000066400000000000000000000010361341750772100257330ustar00rootroot00000000000000/** * 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). */ @ParametersAreNonnullByDefault package net.consensys.cava.toml; import javax.annotation.ParametersAreNonnullByDefault; cava-0.6.0/toml/src/test/000077500000000000000000000000001341750772100151575ustar00rootroot00000000000000cava-0.6.0/toml/src/test/java/000077500000000000000000000000001341750772100161005ustar00rootroot00000000000000cava-0.6.0/toml/src/test/java/net/000077500000000000000000000000001341750772100166665ustar00rootroot00000000000000cava-0.6.0/toml/src/test/java/net/consensys/000077500000000000000000000000001341750772100207125ustar00rootroot00000000000000cava-0.6.0/toml/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100216245ustar00rootroot00000000000000cava-0.6.0/toml/src/test/java/net/consensys/cava/toml/000077500000000000000000000000001341750772100225775ustar00rootroot00000000000000cava-0.6.0/toml/src/test/java/net/consensys/cava/toml/MutableTomlArrayTest.java000066400000000000000000000065111341750772100275310ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import static net.consensys.cava.toml.TomlPosition.positionAt; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; class MutableTomlArrayTest { @Test void emptyArrayContainsAllTypes() { TomlArray array = new MutableTomlArray(); assertTrue(array.isEmpty()); assertEquals(0, array.size()); assertTrue(array.containsStrings()); assertTrue(array.containsLongs()); assertTrue(array.containsDoubles()); assertTrue(array.containsBooleans()); assertTrue(array.containsOffsetDateTimes()); assertTrue(array.containsLocalDateTimes()); assertTrue(array.containsLocalDates()); assertTrue(array.containsLocalTimes()); assertTrue(array.containsArrays()); assertTrue(array.containsTables()); } @Test void arrayContainsTypeAfterAddingItem() { MutableTomlArray array = new MutableTomlArray().append("foo", positionAt(2, 3)); assertFalse(array.isEmpty()); assertEquals(1, array.size()); assertTrue(array.containsStrings()); assertFalse(array.containsLongs()); assertFalse(array.containsDoubles()); assertFalse(array.containsBooleans()); assertFalse(array.containsOffsetDateTimes()); assertFalse(array.containsLocalDateTimes()); assertFalse(array.containsLocalDates()); assertFalse(array.containsLocalTimes()); assertFalse(array.containsArrays()); assertFalse(array.containsTables()); } @Test void cannotAppendUnsupportedType() { MutableTomlArray array = new MutableTomlArray(); assertThrows(IllegalArgumentException.class, () -> array.append(this, positionAt(1, 1))); assertThrows(NullPointerException.class, () -> array.append(null, positionAt(1, 1))); } @Test void cannotAppendDifferentTypes() { MutableTomlArray array = new MutableTomlArray(); array.append("Foo", positionAt(1, 1)); assertThrows(TomlInvalidTypeException.class, () -> array.append(1L, positionAt(1, 1))); array.append("Bar", positionAt(1, 1)); assertEquals(2, array.size()); } @Test void shouldReturnNullForUnknownIndex() { MutableTomlArray array = new MutableTomlArray(); assertThrows(IndexOutOfBoundsException.class, () -> array.get(0)); } @Test void shouldReturnInputPosition() { MutableTomlArray array = new MutableTomlArray(); array.append("Foo", positionAt(4, 3)); array.append("Bar", positionAt(9, 5)); assertEquals(positionAt(4, 3), array.inputPositionOf(0)); assertEquals(positionAt(9, 5), array.inputPositionOf(1)); assertThrows(IndexOutOfBoundsException.class, () -> array.get(2)); } } cava-0.6.0/toml/src/test/java/net/consensys/cava/toml/MutableTomlTableTest.java000066400000000000000000000162311341750772100275020ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import static net.consensys.cava.toml.TomlPosition.positionAt; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.util.Arrays; import java.util.HashSet; import java.util.List; 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 MutableTomlTableTest { @Test void emptyTableIsEmpty() { TomlTable table = new MutableTomlTable(); assertTrue(table.isEmpty()); assertEquals(0, table.size()); } @Test void getMissingPropertyReturnsNull() { MutableTomlTable table = new MutableTomlTable(); table.set("bar", "one", positionAt(1, 1)); table.set("foo.baz", "two", positionAt(1, 1)); assertNull(table.get("baz")); assertNull(table.get("foo.bar")); assertNull(table.get("foo.bar.baz")); } @Test void getStringProperty() { MutableTomlTable table = new MutableTomlTable(); table.set("foo.bar", "one", positionAt(1, 1)); assertTrue(table.isString("foo.bar")); assertEquals("one", table.getString("foo.bar")); } @Test void shouldCreateParentTables() { MutableTomlTable table = new MutableTomlTable(); table.set("foo.bar", "one", positionAt(1, 1)); assertTrue(table.isTable("foo")); assertNotNull(table.getTable("foo")); } @Test void cannotReplaceProperty() { MutableTomlTable table = new MutableTomlTable(); table.set("foo.bar", "one", positionAt(1, 3)); TomlParseError e = assertThrows(TomlParseError.class, () -> { table.set("foo.bar", "two", positionAt(2, 5)); }); assertEquals("foo.bar previously defined at line 1, column 3", e.getMessage()); } @ParameterizedTest @MethodSource("quotesComplexKeyInErrorSupplier") void quotesComplexKeysInError(List path, String expected) { MutableTomlTable table = new MutableTomlTable(); table.set(path, "one", positionAt(1, 3)); TomlParseError e = assertThrows(TomlParseError.class, () -> { table.set(path, "two", positionAt(2, 5)); }); assertEquals(expected + " previously defined at line 1, column 3", e.getMessage()); } private static Stream quotesComplexKeyInErrorSupplier() { return Stream.of( Arguments.of(Arrays.asList("", "bar"), "\"\".bar"), Arguments.of(Arrays.asList("foo ", "bar"), "\"foo \".bar"), Arguments.of(Arrays.asList("foo\n", "bar"), "\"foo\\n\".bar")); } @Test void cannotTreatNonTableAsTable() { MutableTomlTable table = new MutableTomlTable(); table.set("foo.bar", "one", positionAt(5, 3)); TomlParseError e = assertThrows(TomlParseError.class, () -> { table.set("foo.bar.baz", "two", positionAt(2, 5)); }); assertEquals("foo.bar is not a table (previously defined at line 5, column 3)", e.getMessage()); } @Test void ignoresWhitespaceInUnquotedKeys() { MutableTomlTable table = new MutableTomlTable(); table.set("foo.bar", 4, positionAt(5, 3)); assertEquals(Long.valueOf(4), table.getLong(" foo . bar")); table.set(Arrays.asList(" Bar ", " B A Z "), 9, positionAt(5, 3)); assertEquals(Long.valueOf(9), table.getLong("' Bar '. \" B A Z \"")); } @Test void throwsForInvalidKey() { MutableTomlTable table = new MutableTomlTable(); IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> { table.get("foo.=bar"); }); assertEquals("Invalid key: Unexpected '=', expected a-z, A-Z, 0-9, ', or \"", e.getMessage()); } @Test void shouldReturnInputPosition() { MutableTomlTable table = new MutableTomlTable(); table.set("bar", "one", positionAt(4, 3)); table.set("foo.baz", "two", positionAt(15, 2)); assertEquals(positionAt(4, 3), table.inputPositionOf("bar")); assertEquals(positionAt(15, 2), table.inputPositionOf("foo.baz")); assertNull(table.inputPositionOf("baz")); assertNull(table.inputPositionOf("foo.bar")); assertNull(table.inputPositionOf("foo.bar.baz")); } @Test void shouldReturnKeySet() { MutableTomlTable table = new MutableTomlTable(); table.set("bar", "one", positionAt(4, 3)); table.set("foo.baz", "two", positionAt(15, 2)); assertEquals(new HashSet<>(Arrays.asList("bar", "foo")), table.keySet()); } @Test void shouldReturnDottedKeySet() { MutableTomlTable table = new MutableTomlTable(); table.set("bar", "one", positionAt(4, 3)); table.set("foo.baz", "two", positionAt(15, 2)); table.set("foo.buz.bar", "three", positionAt(15, 2)); assertEquals( new HashSet<>(Arrays.asList("bar", "foo", "foo.baz", "foo.buz", "foo.buz.bar")), table.dottedKeySet(true)); assertEquals(new HashSet<>(Arrays.asList("bar", "foo.baz", "foo.buz.bar")), table.dottedKeySet()); } @Test void shouldSerializeToJSON() { MutableTomlTable table = new MutableTomlTable(); table.set("bar", "one", positionAt(2, 1)); table.set("foo.baz", "two", positionAt(3, 2)); table.set("foo.buz", MutableTomlArray.EMPTY, positionAt(3, 2)); table.set("foo.foo", MutableTomlTable.EMPTY, positionAt(3, 2)); MutableTomlArray array = new MutableTomlArray(); array.append("hello\nthere", positionAt(5, 2)); array.append("goodbye", positionAt(5, 2)); table.set("foo.blah", array, positionAt(5, 2)); table.set("buz", OffsetDateTime.parse("1937-07-18T03:25:43-04:00"), positionAt(5, 2)); table.set("glad", LocalDateTime.parse("1937-07-18T03:25:43"), positionAt(5, 2)); table.set("zoo", LocalDate.parse("1937-07-18"), positionAt(5, 2)); table.set("alpha", LocalTime.parse("03:25:43"), positionAt(5, 2)); String expected = "{\n" + " \"alpha\" : \"03:25:43\",\n" + " \"bar\" : \"one\",\n" + " \"buz\" : \"1937-07-18T03:25:43-04:00\",\n" + " \"foo\" : {\n" + " \"baz\" : \"two\",\n" + " \"blah\" : [\n" + " \"hello\\nthere\",\n" + " \"goodbye\"\n" + " ],\n" + " \"buz\" : [],\n" + " \"foo\" : {}\n" + " },\n" + " \"glad\" : \"1937-07-18T03:25:43\",\n" + " \"zoo\" : \"1937-07-18\"\n" + "}\n"; assertEquals(expected.replace("\n", System.lineSeparator()), table.toJson()); } } cava-0.6.0/toml/src/test/java/net/consensys/cava/toml/TokenNameTest.java000066400000000000000000000023411341750772100261630ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.cava.toml.internal.TomlLexer; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; class TokenNameTest { @Test void shouldHaveTokenNameForAllTokens() { List missing = new ArrayList<>(); for (int i = 0; i < TomlLexer.VOCABULARY.getMaxTokenType(); ++i) { if (!TokenName.namesForToken(i).findFirst().isPresent()) { missing.add(TomlLexer.VOCABULARY.getSymbolicName(i)); } } assertTrue(missing.isEmpty(), () -> "No TokenName's for " + String.join(", ", missing)); } } cava-0.6.0/toml/src/test/java/net/consensys/cava/toml/TomlTest.java000066400000000000000000000712701341750772100252240ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.toml; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.io.InputStream; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; 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 TomlTest { @Test void shouldParseEmptyDocument() { TomlParseResult result = Toml.parse("\n"); assertFalse(result.hasErrors(), () -> joinErrors(result)); } @Test void shouldParseSimpleKey() { TomlParseResult result = Toml.parse("foo = 'bar'"); assertFalse(result.hasErrors(), () -> joinErrors(result)); assertEquals("bar", result.getString("foo")); } @Test void shouldParseQuotedKey() { TomlParseResult result = Toml.parse("\"foo\\nba\\\"r\" = 0b11111111"); assertFalse(result.hasErrors(), () -> joinErrors(result)); assertEquals(Long.valueOf(255), result.getLong(Collections.singletonList("foo\nba\"r"))); } @Test void shouldParseDottedKey() { TomlParseResult result = Toml.parse(" foo . \" bar\\t\" . -baz = 0x000a"); assertFalse(result.hasErrors(), () -> joinErrors(result)); assertEquals(Long.valueOf(10), result.getLong(Arrays.asList("foo", " bar\t", "-baz"))); } @Test void shouldNotParseDottedKeysAtV0_4_0OrEarlier() { TomlParseResult result = Toml.parse("[foo]\n bar.baz = 1", TomlVersion.V0_4_0); assertTrue(result.hasErrors()); TomlParseError error = result.errors().get(0); assertEquals("Dotted keys are not supported", error.getMessage()); assertEquals(2, error.position().line()); assertEquals(2, error.position().column()); } @ParameterizedTest @MethodSource("stringSupplier") void shouldParseString(String input, String expected) { TomlParseResult result = Toml.parse(input); assertFalse(result.hasErrors(), () -> joinErrors(result)); assertEquals(expected.replaceAll("\n", System.lineSeparator()), result.getString("foo")); } private static Stream stringSupplier() { return Stream.of( Arguments.of("foo = \"\"", ""), Arguments.of("foo = \"\\\"\"", "\""), Arguments.of("foo = \"bar \\b \\f \\n \\\\ \\u0053 \\U0010FfFf baz\"", "bar \b \f \n \\ S \uDBFF\uDFFF baz"), Arguments.of("foo = \"\"\"\"\"\"", ""), Arguments.of("foo = \"\"\" foo\nbar\"\"\"", " foo\nbar"), Arguments.of("foo = \"\"\"\n foobar\"\"\"", " foobar"), Arguments.of("foo = \"\"\"\n foo\nbar\"\"\"", " foo\nbar"), Arguments.of("foo = \"\"\"\\n foo\nbar\"\"\"", "\n foo\nbar"), Arguments.of("foo = \"\"\"\n\n foo\nbar\"\"\"", "\n foo\nbar"), Arguments.of("foo = \"\"\" foo \\ \nbar\"\"\"", " foo \nbar"), Arguments.of("foo = \"\"\" foo \\\nbar\"\"\"", " foo \nbar"), Arguments.of("foo = \"\"\" foo \\ \nbar\"\"\"", " foo \nbar"), Arguments.of("foo = \"foobar#\" # comment", "foobar#"), Arguments.of("foo = \"foobar#\"", "foobar#"), Arguments.of("foo = \"foo \\\" bar #\" # \"baz\"", "foo \" bar #"), Arguments.of("foo = ''", ""), Arguments.of("foo = '\"'", "\""), Arguments.of("foo = 'foobar \\'", "foobar \\"), Arguments.of("foo = '''foobar \n'''", "foobar \n"), Arguments.of("foo = '''\nfoobar \n'''", "foobar \n"), Arguments.of("foo = '''\nfoobar \\ \n'''", "foobar \\ \n"), Arguments.of("# I am a comment. Hear me roar. Roar.\nfoo = \"value\" # Yeah, you can do this.", "value"), Arguments.of( "foo = \"I'm a string. \\\"You can quote me\\\". Name\\tJos\\u00E9\\nLocation\\tSF.\"", "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF."), Arguments.of("foo=\"\"\"\nRoses are red\nViolets are blue\"\"\"", "Roses are red\nViolets are blue")); } @Test void shouldFailForInvalidUnicodeEscape() { TomlParseResult result = Toml.parse("foo = \"\\UFFFF00FF\""); assertTrue(result.hasErrors()); TomlParseError error = result.errors().get(0); assertEquals("Invalid unicode escape sequence", error.getMessage()); assertEquals(1, error.position().line()); assertEquals(8, error.position().column()); } @ParameterizedTest @MethodSource("integerSupplier") void shouldParseInteger(String input, Long expected) { TomlParseResult result = Toml.parse(input); assertFalse(result.hasErrors(), () -> joinErrors(result)); assertEquals(expected, result.getLong("foo")); } private static Stream integerSupplier() { return Stream.of( Arguments.of("foo = 1", 1L), Arguments.of("foo = 0", 0L), Arguments.of("foo = 100", 100L), Arguments.of("foo = -9876", -9876L), Arguments.of("foo = +5_433", 5433L), Arguments.of("foo = 0xff", 255L), Arguments.of("foo = 0xffbccd34", 4290563380L), Arguments.of("foo = 0o7656", 4014L), Arguments.of("foo = 0o0007_6543_21", 2054353L), Arguments.of("foo = 0b11111100010101_0100000000111111111", 8466858495L), Arguments.of("foo = 0b0000000_00000000000000000000000000", 0L), Arguments.of("foo = 0b111111111111111111111111111111111", 8589934591L)); } @ParameterizedTest @MethodSource("floatSupplier") void shouldParseFloat(String input, Double expected) { TomlParseResult result = Toml.parse(input); assertFalse(result.hasErrors(), () -> joinErrors(result)); assertEquals(expected, result.getDouble("foo")); } private static Stream floatSupplier() { // @formatter:off return Stream.of( Arguments.of("foo = 0.0", 0D), Arguments.of("foo = 0E100", 0D), Arguments.of("foo = 0.00e+100", 0D), Arguments.of("foo = 0.00e-100", 0D), Arguments.of("foo = +0.0", 0D), Arguments.of("foo = -0.0", -0D), Arguments.of("foo = 0.000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000", 0D), Arguments.of("foo = -0.0E999999999999999999999999999999999999999", -0D), Arguments.of("foo = 1.0", 1D), Arguments.of("foo = 43.55E34", 43.55E34D), Arguments.of("foo = 43.557_654E-34", 43.557654E-34D), Arguments.of("foo = inf", Double.POSITIVE_INFINITY), Arguments.of("foo = +inf", Double.POSITIVE_INFINITY), Arguments.of("foo = -inf", Double.NEGATIVE_INFINITY), Arguments.of("foo = nan", Double.NaN), Arguments.of("foo = +nan", Double.NaN), Arguments.of("foo = -nan", Double.NaN) ); // @formatter:on } @Test void shouldParseBoolean() { TomlParseResult result = Toml.parse("foo = true"); assertFalse(result.hasErrors(), () -> joinErrors(result)); assertEquals(Boolean.TRUE, result.getBoolean("foo")); TomlParseResult result2 = Toml.parse("\nfoo=false"); assertFalse(result2.hasErrors(), () -> joinErrors(result2)); assertEquals(Boolean.FALSE, result2.getBoolean("foo")); } @ParameterizedTest @MethodSource("offsetDateSupplier") void shouldParseOffsetDateTime(String input, OffsetDateTime expected) { TomlParseResult result = Toml.parse(input); assertFalse(result.hasErrors(), () -> joinErrors(result)); assertEquals(expected, result.getOffsetDateTime("foo")); } private static Stream offsetDateSupplier() { // @formatter:off return Stream.of( Arguments.of("foo = 1937-07-18T03:25:43-04:00", OffsetDateTime.parse("1937-07-18T03:25:43-04:00")), Arguments.of("foo = 1937-07-18 11:44:02+18:00", OffsetDateTime.parse("1937-07-18T11:44:02+18:00")), Arguments.of("foo = 0000-07-18 11:44:02.00+18:00", OffsetDateTime.parse("0000-07-18T11:44:02+18:00")), Arguments.of("foo = 1979-05-27T07:32:00Z\nbar = 1979-05-27T00:32:00-07:00\n", OffsetDateTime.parse("1979-05-27T07:32:00Z")), Arguments.of("bar = 1979-05-27T07:32:00Z\nfoo = 1979-05-27T00:32:00-07:00\n", OffsetDateTime.parse("1979-05-27T00:32:00-07:00")), Arguments.of("foo = 1937-07-18 11:44:02.334543+18:00", OffsetDateTime.parse("1937-07-18T11:44:02.334543+18:00")), Arguments.of("foo = 1937-07-18 11:44:02Z", OffsetDateTime.parse("1937-07-18T11:44:02+00:00")) ); // @formatter:on } @ParameterizedTest @MethodSource("localDateTimeSupplier") void shouldParseLocalDateTime(String input, LocalDateTime expected) { TomlParseResult result = Toml.parse(input); assertFalse(result.hasErrors(), () -> joinErrors(result)); assertEquals(expected, result.getLocalDateTime("foo")); } private static Stream localDateTimeSupplier() { return Stream.of( Arguments.of("foo = 1937-07-18T03:25:43", LocalDateTime.parse("1937-07-18T03:25:43")), Arguments.of("foo = 1937-07-18 11:44:02", LocalDateTime.parse("1937-07-18T11:44:02")), Arguments.of("foo = 0000-07-18 11:44:02.00", LocalDateTime.parse("0000-07-18T11:44:02")), Arguments.of("foo = 1937-07-18 11:44:02.334543", LocalDateTime.parse("1937-07-18T11:44:02.334543")), Arguments.of("foo = 1937-07-18 11:44:02", LocalDateTime.parse("1937-07-18T11:44:02"))); } @ParameterizedTest @MethodSource("localDateSupplier") void shouldParseLocalDate(String input, LocalDate expected) { TomlParseResult result = Toml.parse(input); assertFalse(result.hasErrors(), () -> joinErrors(result)); assertEquals(expected, result.getLocalDate("foo")); } private static Stream localDateSupplier() { return Stream.of( Arguments.of("foo = 1937-07-18", LocalDate.parse("1937-07-18")), Arguments.of("foo = 1937-07-18", LocalDate.parse("1937-07-18")), Arguments.of("foo = 0000-07-18", LocalDate.parse("0000-07-18")), Arguments.of("foo = 1937-07-18", LocalDate.parse("1937-07-18")), Arguments.of("foo = 1937-07-18", LocalDate.parse("1937-07-18"))); } @ParameterizedTest @MethodSource("localTimeSupplier") void shouldParseLocalTime(String input, LocalTime expected) { TomlParseResult result = Toml.parse(input); assertFalse(result.hasErrors(), () -> joinErrors(result)); assertEquals(expected, result.getLocalTime("foo")); } private static Stream localTimeSupplier() { return Stream.of( Arguments.of("foo = 03:25:43", LocalTime.parse("03:25:43")), Arguments.of("foo = 11:44:02", LocalTime.parse("11:44:02")), Arguments.of("foo = 11:44:02.00", LocalTime.parse("11:44:02")), Arguments.of("foo = 11:44:02.334543", LocalTime.parse("11:44:02.334543")), Arguments.of("foo = 11:44:02", LocalTime.parse("11:44:02"))); } @ParameterizedTest @MethodSource("arraySupplier") void shouldParseArray(String input, Object[] expected) { TomlParseResult result = Toml.parse(input); assertFalse(result.hasErrors(), () -> joinErrors(result)); TomlArray array = result.getArray("foo"); assertNotNull(array); assertEquals(expected.length, array.size()); assertTomlArrayEquals(expected, array); } private static Stream arraySupplier() { return Stream.of( Arguments.of("foo = []", new Object[0]), Arguments.of("foo = [\n]", new Object[0]), Arguments.of("foo = [1]", new Object[] {1L}), Arguments.of("foo = [ \"bar\"\n]", new Object[] {"bar"}), Arguments.of("foo = [11:44:02,]", new Object[] {LocalTime.parse("11:44:02")}), Arguments.of("foo = [\n'bar', #baz\n]", new Object[] {"bar"}), Arguments.of("foo = ['bar', 'baz']", new Object[] {"bar", "baz"}), Arguments.of("foo = [\n'''bar\nbaz''',\n'baz'\n]", new Object[] {"bar\nbaz", "baz"}), Arguments.of("foo = [['bar']]", new Object[] {new Object[] {"bar"}})); } @ParameterizedTest @MethodSource("tableSupplier") void shouldParseTable(String input, String key, Object expected) { TomlParseResult result = Toml.parse(input); assertFalse(result.hasErrors(), () -> joinErrors(result)); assertEquals(expected, result.get(key)); } private static Stream tableSupplier() { return Stream.of( Arguments.of("[foo]\nbar = 'baz'", "foo.bar", "baz"), Arguments.of("[foo] #foo.bar\nbar = 'baz'", "foo.bar", "baz"), Arguments.of("[foo]\n[foo.bar]\nbaz = 'buz'", "foo.bar.baz", "buz"), Arguments.of("[foo.bar]\nbaz=1\n[foo]\nbaz=2", "foo.baz", 2L)); } @ParameterizedTest @MethodSource("inlineTableSupplier") void shouldParseInlineTable(String input, String key, Object expected) { TomlParseResult result = Toml.parse(input); assertFalse(result.hasErrors(), () -> joinErrors(result)); assertEquals(expected, result.get(key)); } private static Stream inlineTableSupplier() { return Stream.of( Arguments.of("foo = {}", "foo.bar", null), Arguments.of("foo = { bar = 'baz' }", "foo.bar", "baz"), Arguments.of("foo = { bar = 'baz', baz.buz = 2 }", "foo.baz.buz", 2L), Arguments.of("foo = { bar = ['baz', 'buz'] , baz . buz = 2 }", "foo.baz.buz", 2L), Arguments.of("foo = { bar = ['baz',\n'buz'\n], baz.buz = 2 }", "foo.baz.buz", 2L), Arguments.of("bar = { bar = ['baz',\n'buz'\n], baz.buz = 2 }\nfoo=2\n", "foo", 2L)); } @ParameterizedTest @MethodSource("arrayTableSupplier") void shouldParseArrayTable(String input, Object[] path, Object expected) { TomlParseResult result = Toml.parse(input); assertFalse(result.hasErrors(), () -> joinErrors(result)); Object element = result; for (Object step : path) { if (step instanceof String) { assertTrue(element instanceof TomlTable); element = ((TomlTable) element).get((String) step); } else if (step instanceof Integer) { assertTrue(element instanceof TomlArray); element = ((TomlArray) element).get((Integer) step); } else { fail("path not found"); } } assertEquals(expected, element); } private static Stream arrayTableSupplier() { return Stream.of( Arguments.of("[[foo]]\nbar = 'baz'", new Object[] {"foo", 0, "bar"}, "baz"), Arguments.of("[[foo]] #foo.bar\nbar = 'baz'", new Object[] {"foo", 0, "bar"}, "baz"), Arguments.of("[[foo]] \n bar = 'buz'\nbuz=1\n", new Object[] {"foo", 0, "buz"}, 1L), Arguments.of("[[foo]] \n bar = 'buz'\n[[foo]]\nbar=1\n", new Object[] {"foo", 0, "bar"}, "buz"), Arguments.of("[[foo]] \n bar = 'buz'\n[[foo]]\nbar=1\n", new Object[] {"foo", 1, "bar"}, 1L), Arguments.of("[[foo]]\nbar=1\n[[foo]]\nbar=2\n", new Object[] {"foo", 0, "bar"}, 1L), Arguments.of("[[foo]]\nbar=1\n[[foo]]\nbar=2\n", new Object[] {"foo", 1, "bar"}, 2L), Arguments.of("[[foo]]\n\n[foo.bar]\n\nbaz=2\n\n", new Object[] {"foo", 0, "bar", "baz"}, 2L), Arguments.of( "[[foo]]\n[[foo.bar]]\n[[foo.baz]]\n[foo.bar.baz]\nbuz=2\n[foo.baz.buz]\nbiz=3\n", new Object[] {"foo", 0, "bar", 0, "baz", "buz"}, 2L), Arguments.of( "[[foo]]\n[[foo.bar]]\n[[foo.baz]]\n[foo.bar.baz]\nbuz=2\n[foo.baz.buz]\nbiz=3\n", new Object[] {"foo", 0, "baz", 0, "buz", "biz"}, 3L)); } @ParameterizedTest @MethodSource("errorCaseSupplier") void shouldHandleParseErrors(String input, int line, int column, String expected) { TomlParseResult result = Toml.parse(input); List errors = result.errors(); assertFalse(errors.isEmpty()); assertEquals(expected, errors.get(0).getMessage(), () -> joinErrors(result)); assertEquals(line, errors.get(0).position().line()); assertEquals(column, errors.get(0).position().column()); } private static Stream errorCaseSupplier() { // @formatter:off return Stream.of( Arguments.of("\"foo\"", 1, 6, "Unexpected end of input, expected . or ="), Arguments.of("foo", 1, 4, "Unexpected end of input, expected . or ="), Arguments.of("foo \n", 1, 6, "Unexpected end of line, expected . or ="), Arguments.of("foo =", 1, 6, "Unexpected end of input, expected ', \", ''', \"\"\", a number, a boolean, a date/time, an array, or a table"), Arguments.of("foo = 0b", 1, 8, "Unexpected 'b', expected a newline or end-of-input"), Arguments.of("foo = +", 1, 7, "Unexpected '+', expected ', \", ''', \"\"\", a number, a boolean, a date/time, an array, or a table"), Arguments.of("=", 1, 1, "Unexpected '=', expected a-z, A-Z, 0-9, ', \", a table key, a newline, or end-of-input"), Arguments.of("\"foo\tbar\" = 1", 1, 5, "Unexpected '\\t', expected \" or a character"), Arguments.of("\"foo \nbar\" = 1", 1, 6, "Unexpected end of line, expected \" or a character"), Arguments.of("foo = \"bar \\y baz\"", 1, 12, "Invalid escape sequence '\\y'"), Arguments.of("\u0011abc = 'foo'", 1, 1, "Unexpected '\\u0011', expected a-z, A-Z, 0-9, ', \", a table key, a newline, or end-of-input"), Arguments.of(" \uDBFF\uDFFFAAabc='foo'", 1, 2, "Unexpected '\\U0010ffff', expected a-z, A-Z, 0-9, ', \", a table key, a newline, or end-of-input"), Arguments.of("foo = 1234567891234567891233456789", 1, 7, "Integer is too large"), Arguments.of("\n\nfoo = \t +1E1000", 3, 18, "Float is too large"), Arguments.of("foo = +1E-1000", 1, 7, "Float is too small"), Arguments.of("foo = 0.000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000001", 1, 7, "Float is too small"), Arguments.of("\nfoo = 1937-47-18-00:00:00-04:00", 2, 17, "Unexpected '-', expected a newline or end-of-input"), Arguments.of("\nfoo = 1937-47-18 00:00:00-04:00", 2, 19, "Unexpected '00', expected a newline or end-of-input"), Arguments.of("\nfoo = 2334567891233457889-07-18T00:00:00-04:00", 2, 7, "Invalid year (valid range 0000..9999)"), Arguments.of("\nfoo = 2-07-18T00:00:00-04:00", 2, 7, "Invalid year (valid range 0000..9999)"), Arguments.of("\nfoo = -07-18T00:00:00-04:00", 2, 9, "Unexpected '7-18T00', expected a newline or end-of-input"), Arguments.of("\nfoo = 1937-47-18T00:00:00-04:00", 2, 12, "Invalid month (valid range 01..12)"), Arguments.of("\nfoo = 1937-7-18T00:00:00-04:00", 2, 12, "Invalid month (valid range 01..12)"), Arguments.of("\nfoo = 1937-00-18T00:00:00-04:00", 2, 12, "Invalid month (valid range 01..12)"), Arguments.of("\nfoo = 1937--18T00:00:00-04:00", 2, 12, "Unexpected '-', expected a date/time"), Arguments.of("\nfoo = 1937-07-48T00:00:00-04:00", 2, 15, "Invalid day (valid range 01..28/31)"), Arguments.of("\nfoo = 1937-07-8T00:00:00-04:00", 2, 15, "Invalid day (valid range 01..28/31)"), Arguments.of("\nfoo = 1937-07-00T00:00:00-04:00", 2, 15, "Invalid day (valid range 01..28/31)"), Arguments.of("\nfoo = 1937-02-30T00:00:00-04:00", 2, 15, "Invalid date 'FEBRUARY 30'"), Arguments.of("\nfoo = 1937-07-18T30:00:00-04:00", 2, 18, "Invalid hour (valid range 00..23)"), Arguments.of("\nfoo = 1937-07-18T3:00:00-04:00", 2, 18, "Invalid hour (valid range 00..23)"), Arguments.of("\nfoo = 1937-07-18T13:70:00-04:00", 2, 21, "Invalid minutes (valid range 00..59)"), Arguments.of("\nfoo = 1937-07-18T13:7:00-04:00", 2, 21, "Invalid minutes (valid range 00..59)"), Arguments.of("\nfoo = 1937-07-18T13:55:92-04:00", 2, 24, "Invalid seconds (valid range 00..59)"), Arguments.of("\nfoo = 1937-07-18T13:55:2-04:00", 2, 24, "Invalid seconds (valid range 00..59)"), Arguments.of("\nfoo = 1937-07-18T13:55:02.0000000009-04:00", 2, 27, "Invalid nanoseconds (valid range 0..999999999)"), Arguments.of("\nfoo = 1937-07-18T13:55:02.-04:00", 2, 27, "Unexpected '-', expected a date/time"), Arguments.of("\nfoo = 1937-07-18T13:55:26-25:00", 2, 26, "Invalid zone offset hours (valid range -18..+18)"), Arguments.of("\nfoo = 1937-07-18T13:55:26-:00", 2, 27, "Unexpected ':', expected a date/time"), Arguments.of("\nfoo = 1937-07-18T13:55:26-04:60", 2, 30, "Invalid zone offset minutes (valid range 0..59)"), Arguments.of("\nfoo = 1937-07-18T13:55:26-18:30", 2, 26, "Invalid zone offset (valid range -18:00..+18:00)"), Arguments.of("\nfoo = 1937-07-18T13:55:26-18:", 2, 30, "Unexpected end of input, expected a date/time"), Arguments.of("\nfoo = 2334567891233457889-07-18T00:00:00", 2, 7, "Invalid year (valid range 0000..9999)"), Arguments.of("\nfoo = 1937-47-18T00:00:00", 2, 12, "Invalid month (valid range 01..12)"), Arguments.of("\nfoo = 1937-07-48T00:00:00", 2, 15, "Invalid day (valid range 01..28/31)"), Arguments.of("\nfoo = 1937-07-18T30:00:00", 2, 18, "Invalid hour (valid range 00..23)"), Arguments.of("\nfoo = 1937-07-18T13:70:00", 2, 21, "Invalid minutes (valid range 00..59)"), Arguments.of("\nfoo = 1937-07-18T13:55:92", 2, 24, "Invalid seconds (valid range 00..59)"), Arguments.of("\nfoo = 1937-07-18T13:55:02.0000000009", 2, 27, "Invalid nanoseconds (valid range 0..999999999)"), Arguments.of("\nfoo = 2334567891233457889-07-18", 2, 7, "Invalid year (valid range 0000..9999)"), Arguments.of("\nfoo = 1937-47-18", 2, 12, "Invalid month (valid range 01..12)"), Arguments.of("\nfoo = 1937-07-48", 2, 15, "Invalid day (valid range 01..28/31)"), Arguments.of("\nfoo = 30:00:00", 2, 7, "Invalid hour (valid range 00..23)"), Arguments.of("\nfoo = 13:70:00", 2, 10, "Invalid minutes (valid range 00..59)"), Arguments.of("\nfoo = 13:55:92", 2, 13, "Invalid seconds (valid range 00..59)"), Arguments.of("\nfoo = 13:55:02.0000000009", 2, 16, "Invalid nanoseconds (valid range 0..999999999)"), Arguments.of("foo = [", 1, 8, "Unexpected end of input, expected ], ', \", ''', \"\"\", a number, a boolean, a date/time, an array, a table, or a newline"), Arguments.of("foo = [ 1\n", 2, 1, "Unexpected end of input, expected ] or a newline"), Arguments.of("foo = [ 1, 'bar' ]", 1, 12, "Cannot add a string to an array containing integers"), Arguments.of("foo = [ 1, 'bar ]\n", 1, 18, "Unexpected end of line, expected '"), Arguments.of("[]", 1, 1, "Empty table key"), Arguments.of("[foo] bar='baz'", 1, 7, "Unexpected 'bar', expected a newline or end-of-input"), Arguments.of("foo='bar'\n[foo]\nbar='baz'", 2, 1, "foo previously defined at line 1, column 1"), Arguments.of("[foo]\nbar='baz'\n[foo]\nbaz=1", 3, 1, "foo previously defined at line 1, column 1"), Arguments.of("[foo]\nbar='baz'\n[foo.bar]\nbaz=1", 3, 1, "foo.bar previously defined at line 2, column 1"), Arguments.of("foo = {", 1, 8, "Unexpected end of input, expected a-z, A-Z, 0-9, }, ', or \""), Arguments.of("foo = { bar = 1,\nbaz = 2 }", 1, 17, "Unexpected end of line, expected a-z, A-Z, 0-9, ', or \""), Arguments.of("foo = { bar = 1\nbaz = 2 }", 1, 16, "Unexpected end of line, expected }"), Arguments.of("foo = { bar = 1 baz = 2 }", 1, 17, "Unexpected 'baz', expected } or a comma"), Arguments.of("[foo]\nbar=1\n[[foo]]\nbar=2\n", 3, 1, "foo is not an array (previously defined at line 1, column 1)"), Arguments.of("foo = [1]\n[[foo]]\nbar=2\n", 2, 1, "foo previously defined as a literal array at line 1, column 1"), Arguments.of("foo = []\n[[foo]]\nbar=2\n", 2, 1, "foo previously defined as a literal array at line 1, column 1"), Arguments.of("[[foo.bar]]\n[foo]\nbaz=2\nbar=3\n", 4, 1, "bar previously defined at line 1, column 1"), Arguments.of("[[foo]]\nbaz=1\n[[foo.bar]]\nbaz=2\n[foo.bar]\nbaz=3\n", 5, 1, "foo.bar previously defined at line 3, column 1") ); // @formatter:on } @Test void testTomlV0_4_0Example() throws Exception { InputStream is = this.getClass().getResourceAsStream("/net/consensys/cava/toml/example-v0.4.0.toml"); assertNotNull(is); TomlParseResult result = Toml.parse(is, TomlVersion.V0_4_0); assertFalse(result.hasErrors(), () -> joinErrors(result)); assertEquals("value", result.getString("table.key")); assertEquals("Preston-Werner", result.getString("table.inline.name.last")); assertEquals("<\\i\\c*\\s*>", result.getString("string.literal.regex")); assertEquals(2L, result.getArray("array.key5").getLong(1)); assertEquals( "granny smith", result.getArray("fruit").getTable(0).getArray("variety").getTable(1).getString("name")); } @Test void testHardExample() throws Exception { InputStream is = this.getClass().getResourceAsStream("/net/consensys/cava/toml/hard_example.toml"); assertNotNull(is); TomlParseResult result = Toml.parse(is, TomlVersion.V0_4_0); assertFalse(result.hasErrors(), () -> joinErrors(result)); assertEquals("You'll hate me after this - #", result.getString("the.test_string")); assertEquals(" And when \"'s are in the string, along with # \"", result.getString("the.hard.harder_test_string")); assertEquals("]", result.getArray("the.hard.'bit#'.multi_line_array").getString(0)); } @Test void testHardExampleUnicode() throws Exception { InputStream is = this.getClass().getResourceAsStream("/net/consensys/cava/toml/hard_example_unicode.toml"); assertNotNull(is); TomlParseResult result = Toml.parse(is, TomlVersion.V0_4_0); assertFalse(result.hasErrors(), () -> joinErrors(result)); assertEquals("Ýôú'ℓℓ λáƭè ₥è áƒƭèř ƭλïƨ - #", result.getString("the.test_string")); assertEquals(" Âñδ ωλèñ \"'ƨ ářè ïñ ƭλè ƨƭřïñϱ, áℓôñϱ ωïƭλ # \"", result.getString("the.hard.harder_test_string")); assertEquals("]", result.getArray("the.hard.'βïƭ#'.multi_line_array").getString(0)); } @Test void testSpecExample() throws Exception { InputStream is = this.getClass().getResourceAsStream("/net/consensys/cava/toml/toml-v0.5.0-spec-example.toml"); assertNotNull(is); TomlParseResult result = Toml.parse(is, TomlVersion.V0_4_0); assertFalse(result.hasErrors(), () -> joinErrors(result)); assertEquals("Tom Preston-Werner", result.getString("owner.name")); assertEquals(OffsetDateTime.parse("1979-05-27T07:32:00-08:00"), result.getOffsetDateTime("owner.dob")); assertEquals("10.0.0.2", result.getString("servers.beta.ip")); TomlArray clientHosts = result.getArray("clients.hosts"); assertNotNull(clientHosts); assertTrue(clientHosts.containsStrings()); assertEquals(Arrays.asList("alpha", "omega"), clientHosts.toList()); } @Test void testDottedKeyOrder() throws Exception { TomlParseResult result1 = Toml.parse("[dog.\"tater.man\"]\ntype.name = \"pug\""); assertFalse(result1.hasErrors(), () -> joinErrors(result1)); TomlParseResult result2 = Toml.parse("a.b.c = 1\na.d = 2\n"); assertFalse(result2.hasErrors(), () -> joinErrors(result2)); TomlParseResult result3 = Toml.parse("# THIS IS INVALID\na.b = 1\na.b.c = 2\n"); assertTrue(result3.hasErrors()); } private String joinErrors(TomlParseResult result) { return result.errors().stream().map(TomlParseError::toString).collect(Collectors.joining("\n")); } private static void assertTomlArrayEquals(Object[] expected, TomlArray array) { for (int i = 0; i < expected.length; ++i) { Object obj = array.get(i); if (expected[i] instanceof Object[]) { assertTrue(obj instanceof TomlArray); assertTomlArrayEquals((Object[]) expected[i], (TomlArray) obj); } else { assertEquals(expected[i], obj); } } } } cava-0.6.0/toml/src/test/resources/000077500000000000000000000000001341750772100171715ustar00rootroot00000000000000cava-0.6.0/toml/src/test/resources/net/000077500000000000000000000000001341750772100177575ustar00rootroot00000000000000cava-0.6.0/toml/src/test/resources/net/consensys/000077500000000000000000000000001341750772100220035ustar00rootroot00000000000000cava-0.6.0/toml/src/test/resources/net/consensys/cava/000077500000000000000000000000001341750772100227155ustar00rootroot00000000000000cava-0.6.0/toml/src/test/resources/net/consensys/cava/toml/000077500000000000000000000000001341750772100236705ustar00rootroot00000000000000cava-0.6.0/toml/src/test/resources/net/consensys/cava/toml/example-v0.4.0.toml000066400000000000000000000123041341750772100270430ustar00rootroot00000000000000################################################################################ ## Comment # Speak your mind with the hash symbol. They go from the symbol to the end of # the line. ################################################################################ ## Table # Tables (also known as hash tables or dictionaries) are collections of # key/value pairs. They appear in square brackets on a line by themselves. [table] key = "value" # Yeah, you can do this. # Nested tables are denoted by table names with dots in them. Name your tables # whatever crap you please, just don't use #, ., [ or ]. [table.subtable] key = "another value" # You don't need to specify all the super-tables if you don't want to. TOML # knows how to do it for you. # [x] you # [x.y] don't # [x.y.z] need these [x.y.z.w] # for this to work ################################################################################ ## Inline Table # Inline tables provide a more compact syntax for expressing tables. They are # especially useful for grouped data that can otherwise quickly become verbose. # Inline tables are enclosed in curly braces `{` and `}`. No newlines are # allowed between the curly braces unless they are valid within a value. [table.inline] name = { first = "Tom", last = "Preston-Werner" } point = { x = 1, y = 2 } ################################################################################ ## String # There are four ways to express strings: basic, multi-line basic, literal, and # multi-line literal. All strings must contain only valid UTF-8 characters. [string.basic] basic = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF." [string.multiline] # The following strings are byte-for-byte equivalent: key1 = "One\nTwo" key2 = """One\nTwo""" key3 = """ One Two""" [string.multiline.continued] # The following strings are byte-for-byte equivalent: key1 = "The quick brown fox jumps over the lazy dog." key2 = """ The quick brown \ fox jumps over \ the lazy dog.""" key3 = """\ The quick brown \ fox jumps over \ the lazy dog.\ """ [string.literal] # What you see is what you get. winpath = 'C:\Users\nodejs\templates' winpath2 = '\\ServerX\admin$\system32\' quoted = 'Tom "Dubs" Preston-Werner' regex = '<\i\c*\s*>' [string.literal.multiline] regex2 = '''I [dw]on't need \d{2} apples''' lines = ''' The first newline is trimmed in raw strings. All other whitespace is preserved. ''' ################################################################################ ## Integer # Integers are whole numbers. Positive numbers may be prefixed with a plus sign. # Negative numbers are prefixed with a minus sign. [integer] key1 = +99 key2 = 42 key3 = 0 key4 = -17 [integer.underscores] # For large numbers, you may use underscores to enhance readability. Each # underscore must be surrounded by at least one digit. key1 = 1_000 key2 = 5_349_221 key3 = 1_2_3_4_5 # valid but inadvisable ################################################################################ ## Float # A float consists of an integer part (which may be prefixed with a plus or # minus sign) followed by a fractional part and/or an exponent part. [float.fractional] key1 = +1.0 key2 = 3.1415 key3 = -0.01 [float.exponent] key1 = 5e+22 key2 = 1e6 key3 = -2E-2 [float.both] key = 6.626e-34 [float.underscores] key1 = 9_224_617.445_991_228_313 key2 = 1e1_00 # modified from original example of 1e1_000, which overflows a Java Double ################################################################################ ## Boolean # Booleans are just the tokens you're used to. Always lowercase. [boolean] True = true False = false ################################################################################ ## Datetime # Datetimes are RFC 3339 dates. [datetime] key1 = 1979-05-27T07:32:00Z key2 = 1979-05-27T00:32:00-07:00 key3 = 1979-05-27T00:32:00.999999-07:00 ################################################################################ ## Array # Arrays are square brackets with other primitives inside. Whitespace is # ignored. Elements are separated by commas. Data types may not be mixed. [array] key1 = [ 1, 2, 3 ] key2 = [ "red", "yellow", "green" ] key3 = [ [ 1, 2 ], [3, 4, 5] ] key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok # Arrays can also be multiline. So in addition to ignoring whitespace, arrays # also ignore newlines between the brackets. Terminating commas are ok before # the closing bracket. key5 = [ 1, 2, 3 ] key6 = [ 1, 2, # this is ok ] ################################################################################ ## Array of Tables # These can be expressed by using a table name in double brackets. Each table # with the same double bracketed name will be an element in the array. The # tables are inserted in the order encountered. [[products]] name = "Hammer" sku = 738594937 [[products]] [[products]] name = "Nail" sku = 284758393 color = "gray" # You can create nested arrays of tables as well. [[fruit]] name = "apple" [fruit.physical] color = "red" shape = "round" [[fruit.variety]] name = "red delicious" [[fruit.variety]] name = "granny smith" [[fruit]] name = "banana" [[fruit.variety]] name = "plantain" cava-0.6.0/toml/src/test/resources/net/consensys/cava/toml/hard_example.toml000066400000000000000000000026061341750772100272220ustar00rootroot00000000000000# Test file for TOML # Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate # This part you'll really hate [the] test_string = "You'll hate me after this - #" # " Annoying, isn't it? [the.hard] test_array = [ "] ", " # "] # ] There you go, parse this! test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ] # You didn't think it'd as easy as chucking out the last #, did you? another_test_string = " Same thing, but with a string #" harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too" # Things will get harder [the.hard."bit#"] "what?" = "You don't think some user won't do that?" multi_line_array = [ "]", # ] Oh yes I did ] # Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test #[error] if you didn't catch this, your parser is broken #string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment" like this #array = [ # "This might most likely happen in multiline arrays", # Like here, # "or here, # and here" # ] End of array comment, forgot the # #number = 3.14 pi <--again forgot the # cava-0.6.0/toml/src/test/resources/net/consensys/cava/toml/hard_example_unicode.toml000066400000000000000000000040661341750772100307320ustar00rootroot00000000000000# Tèƨƭ ƒïℓè ƒôř TÓM£ # Óñℓ¥ ƭλïƨ ôñè ƭřïèƨ ƭô è₥úℓáƭè á TÓM£ ƒïℓè ωřïƭƭèñ β¥ á úƨèř ôƒ ƭλè ƙïñδ ôƒ ƥářƨèř ωřïƭèřƨ ƥřôβáβℓ¥ λáƭè # Tλïƨ ƥářƭ ¥ôú'ℓℓ řèáℓℓ¥ λáƭè [the] test_string = "Ýôú'ℓℓ λáƭè ₥è áƒƭèř ƭλïƨ - #" # " Âññô¥ïñϱ, ïƨñ'ƭ ïƭ? [the.hard] test_array = [ "] ", " # "] # ] Tλèřè ¥ôú ϱô, ƥářƨè ƭλïƨ! test_array2 = [ "Tèƨƭ #11 ]ƥřôƲèδ ƭλáƭ", "Éжƥèřï₥èñƭ #9 ωáƨ á ƨúççèƨƨ" ] # Ýôú δïδñ'ƭ ƭλïñƙ ïƭ'δ áƨ èáƨ¥ áƨ çλúçƙïñϱ ôúƭ ƭλè ℓáƨƭ #, δïδ ¥ôú? another_test_string = "§á₥è ƭλïñϱ, βúƭ ωïƭλ á ƨƭřïñϱ #" harder_test_string = " Âñδ ωλèñ \"'ƨ ářè ïñ ƭλè ƨƭřïñϱ, áℓôñϱ ωïƭλ # \"" # "áñδ çô₥₥èñƭƨ ářè ƭλèřè ƭôô" # Tλïñϱƨ ωïℓℓ ϱèƭ λářδèř [the.hard."βïƭ#"] "ωλáƭ?" = "Ýôú δôñ'ƭ ƭλïñƙ ƨô₥è úƨèř ωôñ'ƭ δô ƭλáƭ?" multi_line_array = [ "]", # ] Óλ ¥èƨ Ì δïδ ] # Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test #[error] ïƒ ¥ôú δïδñ'ƭ çáƭçλ ƭλïƨ, ¥ôúř ƥářƨèř ïƨ βřôƙèñ #string = "Âñ¥ƭλïñϱ ôƭλèř ƭλáñ ƭáβƨ, ƨƥáçèƨ áñδ ñèωℓïñè áƒƭèř á ƙè¥ϱřôúƥ ôř ƙè¥ Ʋáℓúè ƥáïř λáƨ èñδèδ ƨλôúℓδ ƥřôδúçè áñ èřřôř úñℓèƨƨ ïƭ ïƨ á çô₥₥èñƭ" ℓïƙè ƭλïƨ #array = [ # "Tλïƨ ₥ïϱλƭ ₥ôƨƭ ℓïƙèℓ¥ λáƥƥèñ ïñ ₥úℓƭïℓïñè ářřá¥ƨ", # £ïƙè λèřè, # "ôř λèřè, # áñδ λèřè" # ] Éñδ ôƒ ářřᥠçô₥₥èñƭ, ƒôřϱôƭ ƭλè # #number = 3.14 ƥï <--áϱáïñ ƒôřϱôƭ ƭλè # cava-0.6.0/toml/src/test/resources/net/consensys/cava/toml/toml-v0.5.0-spec-example.toml000066400000000000000000000010411341750772100307410ustar00rootroot00000000000000# This is a TOML document. title = "TOML Example" [owner] name = "Tom Preston-Werner" dob = 1979-05-27T07:32:00-08:00 # First class dates [database] server = "192.168.1.1" ports = [ 8001, 8001, 8002 ] connection_max = 5000 enabled = true [servers] # Indentation (tabs and/or spaces) is allowed but not required [servers.alpha] ip = "10.0.0.1" dc = "eqdc10" [servers.beta] ip = "10.0.0.2" dc = "eqdc10" [clients] data = [ ["gamma", "delta"], [1, 2] ] # Line breaks are OK when inside arrays hosts = [ "alpha", "omega" ] cava-0.6.0/units/000077500000000000000000000000001341750772100136005ustar00rootroot00000000000000cava-0.6.0/units/build.gradle000066400000000000000000000004761341750772100160660ustar00rootroot00000000000000description = 'Classes and utilities for working with 256 bit integers.' dependencies { compile project(':bytes') compile 'com.google.guava:guava' testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' testRuntime 'org.junit.jupiter:junit-jupiter-engine' } cava-0.6.0/units/src/000077500000000000000000000000001341750772100143675ustar00rootroot00000000000000cava-0.6.0/units/src/main/000077500000000000000000000000001341750772100153135ustar00rootroot00000000000000cava-0.6.0/units/src/main/java/000077500000000000000000000000001341750772100162345ustar00rootroot00000000000000cava-0.6.0/units/src/main/java/net/000077500000000000000000000000001341750772100170225ustar00rootroot00000000000000cava-0.6.0/units/src/main/java/net/consensys/000077500000000000000000000000001341750772100210465ustar00rootroot00000000000000cava-0.6.0/units/src/main/java/net/consensys/cava/000077500000000000000000000000001341750772100217605ustar00rootroot00000000000000cava-0.6.0/units/src/main/java/net/consensys/cava/units/000077500000000000000000000000001341750772100231225ustar00rootroot00000000000000cava-0.6.0/units/src/main/java/net/consensys/cava/units/bigints/000077500000000000000000000000001341750772100245615ustar00rootroot00000000000000cava-0.6.0/units/src/main/java/net/consensys/cava/units/bigints/BaseUInt256Value.java000066400000000000000000000214421341750772100303330ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.units.bigints; import static java.util.Objects.requireNonNull; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import java.math.BigInteger; import java.util.function.Function; /** * Base class for {@link UInt256Value}. * *

* This class is abstract as it is not meant to be used directly, but it has no abstract methods. As mentioned in * {@link UInt256Value}, this is used to create strongly-typed type aliases of {@link UInt256}. In other words, this * allow to "tag" numbers with the unit of what they represent for the type-system, which can help clarity, but also * forbid mixing numbers that are mean to be of different units (the strongly-typed part). * *

* This class implements {@link UInt256Value}, but also adds a few operations that take a {@link UInt256} directly, for * instance {@link #multiply(UInt256)}. The rational is that multiplying a given quantity of something by a "raw" number * is always meaningful, and return a new quantity of the same thing. * * @param The concrete type of the value. */ public abstract class BaseUInt256Value> implements UInt256Value { private final UInt256 value; private final Function ctor; /** * @param value The value to instantiate this {@code UInt256Value} with. * @param ctor A constructor for the concrete type. */ protected BaseUInt256Value(UInt256 value, Function ctor) { requireNonNull(value); requireNonNull(ctor); this.value = value; this.ctor = ctor; } /** * @param value An unsigned value to instantiate this {@code UInt256Value} with. * @param ctor A constructor for the concrete type. */ protected BaseUInt256Value(long value, Function ctor) { requireNonNull(ctor); this.value = UInt256.valueOf(value); this.ctor = ctor; } /** * @param value An unsigned value to instantiate this {@code UInt256Value} with. * @param ctor A constructor for the concrete type. */ protected BaseUInt256Value(BigInteger value, Function ctor) { requireNonNull(value); requireNonNull(ctor); this.value = UInt256.valueOf(value); this.ctor = ctor; } /** * Return a copy of this value, or itself if immutable. * *

* The default implementation of this method returns a copy using the constructor for the concrete type and the bytes * returned from {@link #toBytes()}. Most implementations will want to override this method to instead return * {@code this}. * * @return A copy of this value, or itself if immutable. */ protected T copy() { return ctor.apply(value); } /** * Return the zero value for this type. * *

* The default implementation of this method returns a value obtained from calling the concrete type constructor with * an argument of {@link Bytes32#ZERO}. Most implementations will want to override this method to instead return a * static constant. * * @return The zero value for this type. */ protected T zero() { return ctor.apply(UInt256.ZERO); } @Override public T add(T value) { return add(value.toUInt256()); } /** * Returns a value that is {@code (this + value)}. * * @param value The amount to be added to this value. * @return {@code this + value} */ public T add(UInt256 value) { if (value.isZero()) { return copy(); } return ctor.apply(this.value.add(value)); } @Override public T add(long value) { if (value == 0) { return copy(); } return ctor.apply(this.value.add(value)); } @Override public T addMod(T value, UInt256 modulus) { return addMod(value.toUInt256(), modulus); } /** * Returns a value equivalent to {@code ((this + value) mod modulus)}. * * @param value The amount to be added to this value. * @param modulus The modulus. * @return {@code (this + value) mod modulus} * @throws ArithmeticException {@code modulus} == 0. */ public T addMod(UInt256 value, UInt256 modulus) { return ctor.apply(this.value.addMod(value, modulus)); } @Override public T addMod(long value, UInt256 modulus) { return ctor.apply(this.value.addMod(value, modulus)); } @Override public T addMod(long value, long modulus) { return ctor.apply(this.value.addMod(value, modulus)); } @Override public T subtract(T value) { return subtract(value.toUInt256()); } /** * Returns a value that is {@code (this - value)}. * * @param value The amount to be subtracted from this value. * @return {@code this - value} */ public T subtract(UInt256 value) { if (value.isZero()) { return copy(); } return ctor.apply(this.value.subtract(value)); } @Override public T subtract(long value) { if (value == 0) { return copy(); } return ctor.apply(this.value.subtract(value)); } @Override public T multiply(T value) { return multiply(value.toUInt256()); } /** * Returns a value that is {@code (this * value)}. * * @param value The amount to multiply this value by. * @return {@code this * value} */ public T multiply(UInt256 value) { if (isZero() || value.isZero()) { return zero(); } if (value.equals(UInt256.ONE)) { return copy(); } return ctor.apply(this.value.multiply(value)); } @Override public T multiply(long value) { if (value == 0 || isZero()) { return zero(); } if (value == 1) { return copy(); } return ctor.apply(this.value.multiply(value)); } @Override public T multiplyMod(T value, UInt256 modulus) { return multiplyMod(value.toUInt256(), modulus); } /** * Returns a value that is {@code ((this * value) mod modulus)}. * * @param value The amount to multiply this value by. * @param modulus The modulus. * @return {@code (this * value) mod modulus} * @throws ArithmeticException {@code value} < 0 or {@code modulus} == 0. */ public T multiplyMod(UInt256 value, UInt256 modulus) { return ctor.apply(this.value.multiplyMod(value, modulus)); } @Override public T multiplyMod(long value, UInt256 modulus) { return ctor.apply(this.value.multiplyMod(value, modulus)); } @Override public T multiplyMod(long value, long modulus) { return ctor.apply(this.value.multiplyMod(value, modulus)); } @Override public T divide(T value) { return divide(value.toUInt256()); } /** * Returns a value that is {@code (this / value)}. * * @param value The amount to divide this value by. * @return {@code this / value} * @throws ArithmeticException {@code value} == 0. */ public T divide(UInt256 value) { return ctor.apply(this.value.divide(value)); } @Override public T divide(long value) { return ctor.apply(this.value.divide(value)); } @Override public T pow(UInt256 exponent) { return ctor.apply(this.value.pow(exponent)); } @Override public T pow(long exponent) { return ctor.apply(this.value.pow(exponent)); } @Override public T mod(UInt256 modulus) { return ctor.apply(this.value.mod(modulus)); } @Override public T mod(long modulus) { return ctor.apply(this.value.mod(modulus)); } @Override public int compareTo(T other) { return compareTo(other.toUInt256()); } /** * Compare two {@link UInt256} values. * * @param other The value to compare to. * @return A negative integer, zero, or a positive integer as this value is less than, equal to, or greater than the * specified value. */ public int compareTo(UInt256 other) { return this.value.compareTo(other); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof UInt256Value)) { return false; } UInt256Value other = (UInt256Value) obj; return this.value.equals(other.toUInt256()); } @Override public int hashCode() { return value.hashCode(); } @Override public String toString() { return value.toString(); } @Override public UInt256 toUInt256() { return value; } @Override public Bytes32 toBytes() { return value.toBytes(); } @Override public Bytes toMinimalBytes() { return value.toMinimalBytes(); } } cava-0.6.0/units/src/main/java/net/consensys/cava/units/bigints/UInt256.java000066400000000000000000000562231341750772100265500ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.units.bigints; import static com.google.common.base.Preconditions.checkArgument; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import net.consensys.cava.bytes.MutableBytes; import net.consensys.cava.bytes.MutableBytes32; import java.math.BigInteger; import java.util.Arrays; /** * An unsigned 256-bit precision number. * * This is a raw {@link UInt256Value} - a 256-bit precision unsigned number of no particular unit. */ public final class UInt256 implements UInt256Value { private final static int MAX_CONSTANT = 64; private final static BigInteger BI_MAX_CONSTANT = BigInteger.valueOf(MAX_CONSTANT); private static UInt256 CONSTANTS[] = new UInt256[MAX_CONSTANT + 1]; static { CONSTANTS[0] = new UInt256(Bytes32.ZERO); for (int i = 1; i <= MAX_CONSTANT; ++i) { CONSTANTS[i] = new UInt256(i); } } /** The minimum value of a UInt256 */ public final static UInt256 MIN_VALUE = valueOf(0); /** The maximum value of a UInt256 */ public final static UInt256 MAX_VALUE = new UInt256(Bytes32.ZERO.not()); /** The value 0 */ public final static UInt256 ZERO = valueOf(0); /** The value 1 */ public final static UInt256 ONE = valueOf(1); /** The value 10 */ public final static UInt256 TEN = valueOf(10); private static final int INTS_SIZE = 32 / 4; // The mask is used to obtain the value of an int as if it were unsigned. private static final long LONG_MASK = 0xFFFFFFFFL; private static final BigInteger P_2_256 = BigInteger.valueOf(2).pow(256); // The unsigned int components of the value private final int ints[]; /** * Return a {@code UInt256} containing the specified value. * * @param value The value to create a {@code UInt256} for. * @return A {@code UInt256} containing the specified value. * @throws IllegalArgumentException If the value is negative. */ public static UInt256 valueOf(long value) { checkArgument(value >= 0, "Argument must be positive"); if (value <= MAX_CONSTANT) { return CONSTANTS[(int) value]; } return new UInt256(value); } private UInt256(long value) { this.ints = new int[INTS_SIZE]; this.ints[6] = (int) ((value >>> 32) & LONG_MASK); this.ints[7] = (int) (value & LONG_MASK); } /** * Return a {@link UInt256} containing the specified value. * * @param value The value to create a {@link UInt256} for. * @return A {@link UInt256} containing the specified value. * @throws IllegalArgumentException If the value is negative. */ public static UInt256 valueOf(BigInteger value) { checkArgument(value.signum() >= 0, "Argument must be positive"); if (value.compareTo(BI_MAX_CONSTANT) <= 0) { return CONSTANTS[value.intValue()]; } int[] ints = new int[INTS_SIZE]; for (int i = INTS_SIZE - 1; i >= 0; --i) { ints[i] = value.intValue(); value = value.shiftRight(32); } return new UInt256(ints); } /** * Return a {@link UInt256} containing the value described by the specified bytes. * * @param bytes The bytes containing a {@link UInt256}. * @return A {@link UInt256} containing the specified value. * @throws IllegalArgumentException if {@code bytes.size() > 32}. */ public static UInt256 fromBytes(Bytes bytes) { return new UInt256(Bytes32.leftPad(bytes)); } /** * Parse a hexadecimal string into a {@link UInt256}. * * @param str The hexadecimal string to parse, which may or may not start with "0x". That representation may contain * less than 32 bytes, in which case the result is left padded with zeros. * @return The value corresponding to {@code str}. * @throws IllegalArgumentException if {@code str} does not correspond to a valid hexadecimal representation or * contains more than 32 bytes. */ public static UInt256 fromHexString(String str) { return new UInt256(Bytes32.fromHexStringLenient(str)); } private UInt256(Bytes32 bytes) { this.ints = new int[INTS_SIZE]; for (int i = 0, j = 0; i < INTS_SIZE; ++i, j += 4) { ints[i] = bytes.getInt(j); } } private UInt256(int ints[]) { this.ints = ints; } @SuppressWarnings("ReferenceEquality") @Override public boolean isZero() { if (this == ZERO) { return true; } for (int i = INTS_SIZE - 1; i >= 0; --i) { if (this.ints[i] != 0) { return false; } } return true; } @Override public UInt256 add(UInt256 value) { if (value.isZero()) { return this; } if (isZero()) { return value; } int result[] = new int[INTS_SIZE]; boolean constant = true; long sum = (this.ints[INTS_SIZE - 1] & LONG_MASK) + (value.ints[INTS_SIZE - 1] & LONG_MASK); result[INTS_SIZE - 1] = (int) (sum & LONG_MASK); if (result[INTS_SIZE - 1] < 0 || result[INTS_SIZE - 1] > MAX_CONSTANT) { constant = false; } for (int i = INTS_SIZE - 2; i >= 0; --i) { sum = (this.ints[i] & LONG_MASK) + (value.ints[i] & LONG_MASK) + (sum >>> 32); result[i] = (int) (sum & LONG_MASK); constant &= result[i] == 0; } if (constant) { return CONSTANTS[result[INTS_SIZE - 1]]; } return new UInt256(result); } @Override public UInt256 add(long value) { if (value == 0) { return this; } if (value > 0 && isZero()) { return UInt256.valueOf(value); } int result[] = new int[INTS_SIZE]; boolean constant = true; long sum = (this.ints[INTS_SIZE - 1] & LONG_MASK) + (value & LONG_MASK); result[INTS_SIZE - 1] = (int) (sum & LONG_MASK); if (result[INTS_SIZE - 1] < 0 || result[INTS_SIZE - 1] > MAX_CONSTANT) { constant = false; } sum = (this.ints[INTS_SIZE - 2] & LONG_MASK) + (value >>> 32) + (sum >>> 32); result[INTS_SIZE - 2] = (int) (sum & LONG_MASK); constant &= result[INTS_SIZE - 2] == 0; long signExtent = (value >> 63) & LONG_MASK; for (int i = INTS_SIZE - 3; i >= 0; --i) { sum = (this.ints[i] & LONG_MASK) + signExtent + (sum >>> 32); result[i] = (int) (sum & LONG_MASK); constant &= result[i] == 0; } if (constant) { return CONSTANTS[result[INTS_SIZE - 1]]; } return new UInt256(result); } @Override public UInt256 addMod(UInt256 value, UInt256 modulus) { if (modulus.isZero()) { throw new ArithmeticException("addMod with zero modulus"); } return UInt256.valueOf(toBigInteger().add(value.toBigInteger()).mod(modulus.toBigInteger())); } @Override public UInt256 addMod(long value, UInt256 modulus) { if (modulus.isZero()) { throw new ArithmeticException("addMod with zero modulus"); } return UInt256.valueOf(toBigInteger().add(BigInteger.valueOf(value)).mod(modulus.toBigInteger())); } @Override public UInt256 addMod(long value, long modulus) { if (modulus == 0) { throw new ArithmeticException("addMod with zero modulus"); } if (modulus < 0) { throw new ArithmeticException("addMod unsigned with negative modulus"); } return UInt256.valueOf(toBigInteger().add(BigInteger.valueOf(value)).mod(BigInteger.valueOf(modulus))); } @Override public UInt256 subtract(UInt256 value) { if (value.isZero()) { return this; } int result[] = new int[INTS_SIZE]; boolean constant = true; long sum = (this.ints[INTS_SIZE - 1] & LONG_MASK) + ((~value.ints[INTS_SIZE - 1]) & LONG_MASK) + 1; result[INTS_SIZE - 1] = (int) (sum & LONG_MASK); if (result[INTS_SIZE - 1] < 0 || result[INTS_SIZE - 1] > MAX_CONSTANT) { constant = false; } for (int i = INTS_SIZE - 2; i >= 0; --i) { sum = (this.ints[i] & LONG_MASK) + ((~value.ints[i]) & LONG_MASK) + (sum >>> 32); result[i] = (int) (sum & LONG_MASK); constant &= result[i] == 0; } if (constant) { return CONSTANTS[result[INTS_SIZE - 1]]; } return new UInt256(result); } @Override public UInt256 subtract(long value) { return add(-value); } @Override public UInt256 multiply(UInt256 value) { if (isZero() || value.isZero()) { return ZERO; } if (value.equals(UInt256.ONE)) { return this; } return multiply(this.ints, value.ints); } private static UInt256 multiply(int[] x, int[] y) { int result[] = new int[INTS_SIZE + INTS_SIZE]; long carry = 0; for (int j = INTS_SIZE - 1, k = INTS_SIZE + INTS_SIZE - 1; j >= 0; j--, k--) { long product = (y[j] & LONG_MASK) * (x[INTS_SIZE - 1] & LONG_MASK) + carry; result[k] = (int) product; carry = product >>> 32; } result[INTS_SIZE - 1] = (int) carry; for (int i = INTS_SIZE - 2; i >= 0; i--) { carry = 0; for (int j = INTS_SIZE - 1, k = INTS_SIZE + i; j >= 0; j--, k--) { long product = (y[j] & LONG_MASK) * (x[i] & LONG_MASK) + (result[k] & LONG_MASK) + carry; result[k] = (int) product; carry = product >>> 32; } result[i] = (int) carry; } boolean constant = true; for (int i = INTS_SIZE; i < (INTS_SIZE + INTS_SIZE) - 2; ++i) { constant &= (result[i] == 0); } if (constant && result[INTS_SIZE + INTS_SIZE - 1] >= 0 && result[INTS_SIZE + INTS_SIZE - 1] <= MAX_CONSTANT) { return CONSTANTS[result[INTS_SIZE + INTS_SIZE - 1]]; } return new UInt256(Arrays.copyOfRange(result, INTS_SIZE, INTS_SIZE + INTS_SIZE)); } @Override public UInt256 multiply(long value) { if (value == 0 || isZero()) { return ZERO; } if (value == 1) { return this; } if (value < 0) { throw new ArithmeticException("multiply unsigned by negative"); } UInt256 other = new UInt256(value); return multiply(this.ints, other.ints); } @Override public UInt256 multiplyMod(UInt256 value, UInt256 modulus) { if (modulus.isZero()) { throw new ArithmeticException("multiplyMod with zero modulus"); } if (isZero() || value.isZero()) { return ZERO; } if (value.equals(UInt256.ONE)) { return mod(modulus); } return UInt256.valueOf(toBigInteger().multiply(value.toBigInteger()).mod(modulus.toBigInteger())); } @Override public UInt256 multiplyMod(long value, UInt256 modulus) { if (modulus.isZero()) { throw new ArithmeticException("multiplyMod with zero modulus"); } if (value == 0 || isZero()) { return ZERO; } if (value == 1) { return mod(modulus); } if (value < 0) { throw new ArithmeticException("multiplyMod unsigned by negative"); } return UInt256.valueOf(toBigInteger().multiply(BigInteger.valueOf(value)).mod(modulus.toBigInteger())); } @Override public UInt256 multiplyMod(long value, long modulus) { if (modulus == 0) { throw new ArithmeticException("multiplyMod with zero modulus"); } if (modulus < 0) { throw new ArithmeticException("multiplyMod unsigned with negative modulus"); } if (value == 0 || isZero()) { return ZERO; } if (value == 1) { return mod(modulus); } if (value < 0) { throw new ArithmeticException("multiplyMod unsigned by negative"); } return UInt256.valueOf(toBigInteger().multiply(BigInteger.valueOf(value)).mod(BigInteger.valueOf(modulus))); } @Override public UInt256 divide(UInt256 value) { if (value.isZero()) { throw new ArithmeticException("divide by zero"); } if (value.equals(UInt256.ONE)) { return this; } return UInt256.valueOf(toBigInteger().divide(value.toBigInteger())); } @Override public UInt256 divide(long value) { if (value == 0) { throw new ArithmeticException("divide by zero"); } if (value < 0) { throw new ArithmeticException("divide unsigned by negative"); } if (value == 1) { return this; } if (isPowerOf2(value)) { return shiftRight(log2(value)); } return UInt256.valueOf(toBigInteger().divide(BigInteger.valueOf(value))); } @Override public UInt256 pow(UInt256 exponent) { return UInt256.valueOf(toBigInteger().modPow(exponent.toBigInteger(), P_2_256)); } @Override public UInt256 pow(long exponent) { return UInt256.valueOf(toBigInteger().modPow(BigInteger.valueOf(exponent), P_2_256)); } @Override public UInt256 mod(UInt256 modulus) { if (modulus.isZero()) { throw new ArithmeticException("mod by zero"); } return UInt256.valueOf(toBigInteger().mod(modulus.toBigInteger())); } @Override public UInt256 mod(long modulus) { if (modulus == 0) { throw new ArithmeticException("mod by zero"); } if (modulus < 0) { throw new ArithmeticException("mod by negative"); } if (isPowerOf2(modulus)) { int log2 = log2(modulus); int d = log2 / 32; int s = log2 % 32; assert (d == 0 || d == 1); int[] result = new int[INTS_SIZE]; // Mask the byte at d to only include the s right-most bits result[INTS_SIZE - 1 - d] = this.ints[INTS_SIZE - 1 - d] & ~(0xFFFFFFFF << s); if (d != 0) { result[INTS_SIZE - 1] = this.ints[INTS_SIZE - 1]; } return new UInt256(result); } return UInt256.valueOf(toBigInteger().mod(BigInteger.valueOf(modulus))); } /** * Return a bit-wise AND of this value and the supplied value. * * If this value and the supplied value are different lengths, then the shorter will be zero-padded to the left. * * @param value The value to perform the operation with. * @return The result of a bit-wise AND. */ public UInt256 and(UInt256 value) { int result[] = new int[INTS_SIZE]; for (int i = INTS_SIZE - 1; i >= 0; --i) { result[i] = this.ints[i] & value.ints[i]; } return new UInt256(result); } /** * Return a bit-wise AND of this value and the supplied bytes. * * If this value and the supplied value are different lengths, then the shorter will be zero-padded to the left. * * @param bytes The bytes to perform the operation with. * @return The result of a bit-wise AND. */ public UInt256 and(Bytes32 bytes) { int result[] = new int[INTS_SIZE]; for (int i = INTS_SIZE - 1, j = 28; i >= 0; --i, j -= 4) { int other = ((int) bytes.get(j) & 0xFF) << 24; other |= ((int) bytes.get(j + 1) & 0xFF) << 16; other |= ((int) bytes.get(i + 2) & 0xFF) << 8; other |= ((int) bytes.get(i + 3) & 0xFF); result[i] = this.ints[i] & other; } return new UInt256(result); } /** * Return a bit-wise OR of this value and the supplied value. * * If this value and the supplied value are different lengths, then the shorter will be zero-padded to the left. * * @param value The value to perform the operation with. * @return The result of a bit-wise OR. */ public UInt256 or(UInt256 value) { int result[] = new int[INTS_SIZE]; for (int i = INTS_SIZE - 1; i >= 0; --i) { result[i] = this.ints[i] | value.ints[i]; } return new UInt256(result); } /** * Return a bit-wise OR of this value and the supplied bytes. * * If this value and the supplied value are different lengths, then the shorter will be zero-padded to the left. * * @param bytes The bytes to perform the operation with. * @return The result of a bit-wise OR. */ public UInt256 or(Bytes32 bytes) { int result[] = new int[INTS_SIZE]; for (int i = INTS_SIZE - 1, j = 28; i >= 0; --i, j -= 4) { result[i] = this.ints[i] | (((int) bytes.get(j) & 0xFF) << 24); result[i] |= ((int) bytes.get(j + 1) & 0xFF) << 16; result[i] |= ((int) bytes.get(j + 2) & 0xFF) << 8; result[i] |= ((int) bytes.get(j + 3) & 0xFF); } return new UInt256(result); } /** * Return a bit-wise XOR of this value and the supplied value. * * If this value and the supplied value are different lengths, then the shorter will be zero-padded to the left. * * @param value The value to perform the operation with. * @return The result of a bit-wise XOR. */ public UInt256 xor(UInt256 value) { int result[] = new int[INTS_SIZE]; for (int i = INTS_SIZE - 1; i >= 0; --i) { result[i] = this.ints[i] ^ value.ints[i]; } return new UInt256(result); } /** * Return a bit-wise XOR of this value and the supplied bytes. * * If this value and the supplied value are different lengths, then the shorter will be zero-padded to the left. * * @param bytes The bytes to perform the operation with. * @return The result of a bit-wise XOR. */ public UInt256 xor(Bytes32 bytes) { int result[] = new int[INTS_SIZE]; for (int i = INTS_SIZE - 1, j = 28; i >= 0; --i, j -= 4) { result[i] = this.ints[i] ^ (((int) bytes.get(j) & 0xFF) << 24); result[i] ^= ((int) bytes.get(j + 1) & 0xFF) << 16; result[i] ^= ((int) bytes.get(j + 2) & 0xFF) << 8; result[i] ^= ((int) bytes.get(j + 3) & 0xFF); } return new UInt256(result); } /** * Return a bit-wise NOT of this value. * * @return The result of a bit-wise NOT. */ public UInt256 not() { int result[] = new int[INTS_SIZE]; for (int i = INTS_SIZE - 1; i >= 0; --i) { result[i] = ~(this.ints[i]); } return new UInt256(result); } /** * Shift all bits in this value to the right. * * @param distance The number of bits to shift by. * @return A value containing the shifted bits. */ public UInt256 shiftRight(int distance) { if (distance == 0) { return this; } if (distance >= 256) { return ZERO; } int result[] = new int[INTS_SIZE]; int d = distance / 32; int s = distance % 32; int resIdx = INTS_SIZE; if (s == 0) { for (int i = INTS_SIZE - d; i > 0;) { result[--resIdx] = this.ints[--i]; } } else { for (int i = INTS_SIZE - 1 - d; i >= 0; i--) { int leftSide = this.ints[i] >>> s; int rightSide = (i == 0) ? 0 : this.ints[i - 1] << (32 - s); result[--resIdx] = (leftSide | rightSide); } } return new UInt256(result); } /** * Shift all bits in this value to the left. * * @param distance The number of bits to shift by. * @return A value containing the shifted bits. */ public UInt256 shiftLeft(int distance) { if (distance == 0) { return this; } if (distance >= 256) { return ZERO; } int result[] = new int[INTS_SIZE]; int d = distance / 32; int s = distance % 32; int resIdx = 0; if (s == 0) { for (int i = d; i < INTS_SIZE;) { result[resIdx++] = this.ints[i++]; } } else { for (int i = d; i < INTS_SIZE; ++i) { int leftSide = this.ints[i] << s; int rightSide = (i == INTS_SIZE - 1) ? 0 : (this.ints[i + 1] >>> (32 - s)); result[resIdx++] = (leftSide | rightSide); } } return new UInt256(result); } @Override public boolean equals(Object object) { if (object == this) { return true; } if (!(object instanceof UInt256)) { return false; } UInt256 other = (UInt256) object; for (int i = 0; i < INTS_SIZE; ++i) { if (this.ints[i] != other.ints[i]) { return false; } } return true; } @Override public int hashCode() { int result = 1; for (int i = 0; i < INTS_SIZE; ++i) { result = 31 * result + this.ints[i]; } return result; } @Override public int compareTo(UInt256 other) { for (int i = 0; i < INTS_SIZE; ++i) { int cmp = Long.compare(((long) this.ints[i]) & LONG_MASK, ((long) other.ints[i]) & LONG_MASK); if (cmp != 0) { return cmp; } } return 0; } @Override public boolean fitsInt() { for (int i = 0; i < INTS_SIZE - 1; i++) { if (this.ints[i] != 0) { return false; } } // Lastly, the left-most byte of the int must not start with a 1. return this.ints[INTS_SIZE - 1] >= 0; } @Override public int intValue() { if (!fitsInt()) { throw new ArithmeticException("Value does not fit a 4 byte int"); } return this.ints[INTS_SIZE - 1]; } @Override public boolean fitsLong() { for (int i = 0; i < INTS_SIZE - 2; i++) { if (this.ints[i] != 0) { return false; } } // Lastly, the left-most byte of the int must not start with a 1. return this.ints[INTS_SIZE - 2] >= 0; } @Override public long toLong() { if (!fitsLong()) { throw new ArithmeticException("Value does not fit a 8 byte long"); } return (((long) this.ints[INTS_SIZE - 2]) << 32) | (((long) (this.ints[INTS_SIZE - 1])) & LONG_MASK); } @Override public String toString() { return toBigInteger().toString(); } @Override public BigInteger toBigInteger() { byte mag[] = new byte[32]; for (int i = 0, j = 0; i < INTS_SIZE; ++i) { mag[j++] = (byte) (this.ints[i] >>> 24); mag[j++] = (byte) ((this.ints[i] >>> 16) & 0xFF); mag[j++] = (byte) ((this.ints[i] >>> 8) & 0xFF); mag[j++] = (byte) (this.ints[i] & 0xFF); } return new BigInteger(1, mag); } @Override public UInt256 toUInt256() { return this; } @Override public Bytes32 toBytes() { MutableBytes32 bytes = MutableBytes32.create(); for (int i = 0, j = 0; i < INTS_SIZE; ++i, j += 4) { bytes.setInt(j, this.ints[i]); } return bytes; } @Override public Bytes toMinimalBytes() { int i = 0; while (i < INTS_SIZE && this.ints[i] == 0) { ++i; } if (i == INTS_SIZE) { return Bytes.EMPTY; } int firstIntBytes = 4 - (Integer.numberOfLeadingZeros(this.ints[i]) / 8); int totalBytes = firstIntBytes + ((INTS_SIZE - (i + 1)) * 4); MutableBytes bytes = MutableBytes.create(totalBytes); int j = 0; switch (firstIntBytes) { case 4: bytes.set(j++, (byte) (this.ints[i] >>> 24)); // fall through case 3: bytes.set(j++, (byte) ((this.ints[i] >>> 16) & 0xFF)); // fall through case 2: bytes.set(j++, (byte) ((this.ints[i] >>> 8) & 0xFF)); // fall through case 1: bytes.set(j++, (byte) (this.ints[i] & 0xFF)); } ++i; for (; i < INTS_SIZE; ++i, j += 4) { bytes.setInt(j, this.ints[i]); } return bytes; } @Override public int numberOfLeadingZeros() { for (int i = 0; i < INTS_SIZE; i++) { if (this.ints[i] == 0) { continue; } return (i * 32) + Integer.numberOfLeadingZeros(this.ints[i]); } return 256; } @Override public int bitLength() { for (int i = 0; i < INTS_SIZE; i++) { if (this.ints[i] == 0) { continue; } return (INTS_SIZE * 32) - (i * 32) - Integer.numberOfLeadingZeros(this.ints[i]); } return 0; } private static boolean isPowerOf2(long n) { assert n > 0; return (n & (n - 1)) == 0; } private static int log2(long v) { assert v > 0; return 63 - Long.numberOfLeadingZeros(v); } } cava-0.6.0/units/src/main/java/net/consensys/cava/units/bigints/UInt256Domain.java000066400000000000000000000030131341750772100276650ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.units.bigints; import com.google.common.collect.DiscreteDomain; /** * A {@link DiscreteDomain} over {@link UInt256}. */ public final class UInt256Domain extends DiscreteDomain { @Override public UInt256 next(UInt256 value) { return value.add(1); } @Override public UInt256 previous(UInt256 value) { return value.subtract(1); } @Override public long distance(UInt256 start, UInt256 end) { boolean negativeDistance = start.compareTo(end) < 0; UInt256 distance = negativeDistance ? end.subtract(start) : start.subtract(end); if (!distance.fitsLong()) { return negativeDistance ? Long.MIN_VALUE : Long.MAX_VALUE; } long distanceLong = distance.toLong(); return negativeDistance ? -distanceLong : distanceLong; } @Override public UInt256 minValue() { return UInt256.MIN_VALUE; } @Override public UInt256 maxValue() { return UInt256.MAX_VALUE; } } cava-0.6.0/units/src/main/java/net/consensys/cava/units/bigints/UInt256Value.java000066400000000000000000000236431341750772100275450ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.units.bigints; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.Bytes32; import java.math.BigInteger; /** * Represents a 256-bit (32 bytes) unsigned integer value. * *

* A {@link UInt256Value} is an unsigned integer value stored with 32 bytes, so whose value can range between 0 and * 2^256-1. * *

* This interface defines operations for value types with a 256-bit precision range. The methods provided by this * interface take parameters of the same type (and also {@code long}. This provides type safety by ensuring calculations * cannot mix different {@code UInt256Value} types. * *

* Where only a pure numerical 256-bit value is required, {@link UInt256} should be used. * *

* It is strongly advised to extend {@link BaseUInt256Value} rather than implementing this interface directly. Doing so * provides type safety in that quantities of different units cannot be mixed accidentally. * * @param The concrete type of the value. */ public interface UInt256Value> extends Comparable { /** * @return True if this is the value 0. */ default boolean isZero() { return toBytes().isZero(); } /** * Returns a value that is {@code (this + value)}. * * @param value The amount to be added to this value. * @return {@code this + value} */ T add(T value); /** * Returns a value that is {@code (this + value)}. * * @param value The amount to be added to this value. * @return {@code this + value} */ T add(long value); /** * Returns a value equivalent to {@code ((this + value) mod modulus)}. * * @param value The amount to be added to this value. * @param modulus The modulus. * @return {@code (this + value) mod modulus} * @throws ArithmeticException {@code modulus} == 0. */ T addMod(T value, UInt256 modulus); /** * Returns a value equivalent to {@code ((this + value) mod modulus)}. * * @param value The amount to be added to this value. * @param modulus The modulus. * @return {@code (this + value) mod modulus} * @throws ArithmeticException {@code modulus} == 0. */ T addMod(long value, UInt256 modulus); /** * Returns a value equivalent to {@code ((this + value) mod modulus)}. * * @param value The amount to be added to this value. * @param modulus The modulus. * @return {@code (this + value) mod modulus} * @throws ArithmeticException {@code modulus} ≤ 0. */ T addMod(long value, long modulus); /** * Returns a value that is {@code (this - value)}. * * @param value The amount to be subtracted from this value. * @return {@code this - value} */ T subtract(T value); /** * Returns a value that is {@code (this - value)}. * * @param value The amount to be subtracted from this value. * @return {@code this - value} */ T subtract(long value); /** * Returns a value that is {@code (this * value)}. * * @param value The amount to multiply this value by. * @return {@code this * value} */ T multiply(T value); /** * Returns a value that is {@code (this * value)}. * * @param value The amount to multiply this value by. * @return {@code this * value} * @throws ArithmeticException {@code value} < 0. */ T multiply(long value); /** * Returns a value that is {@code ((this * value) mod modulus)}. * * @param value The amount to multiply this value by. * @param modulus The modulus. * @return {@code (this * value) mod modulus} * @throws ArithmeticException {@code value} < 0 or {@code modulus} == 0. */ T multiplyMod(T value, UInt256 modulus); /** * Returns a value that is {@code ((this * value) mod modulus)}. * * @param value The amount to multiply this value by. * @param modulus The modulus. * @return {@code (this * value) mod modulus} * @throws ArithmeticException {@code value} < 0 or {@code modulus} == 0. */ T multiplyMod(long value, UInt256 modulus); /** * Returns a value that is {@code ((this * value) mod modulus)}. * * @param value The amount to multiply this value by. * @param modulus The modulus. * @return {@code (this * value) mod modulus} * @throws ArithmeticException {@code value} < 0 or {@code modulus} ≤ 0. */ T multiplyMod(long value, long modulus); /** * Returns a value that is {@code (this / value)}. * * @param value The amount to divide this value by. * @return {@code this / value} * @throws ArithmeticException {@code value} == 0. */ T divide(T value); /** * Returns a value that is {@code (this / value)}. * * @param value The amount to divide this value by. * @return {@code this / value} * @throws ArithmeticException {@code value} ≤ 0. */ T divide(long value); /** * Returns a value that is {@code (thisexponent mod 2256)} * *

* This calculates an exponentiation over the modulus of {@code 2^256}. * *

* Note that {@code exponent} is an {@link UInt256} rather than of the type {@code T}. * * @param exponent The exponent to which this value is to be raised. * @return {@code thisexponent mod 2256} */ T pow(UInt256 exponent); /** * Returns a value that is {@code (thisexponent mod 2256)} * *

* This calculates an exponentiation over the modulus of {@code 2^256}. * * @param exponent The exponent to which this value is to be raised. * @return {@code thisexponent mod 2256} */ T pow(long exponent); /** * Returns a value that is {@code (this mod modulus)}. * * @param modulus The modulus. * @return {@code this mod modulus}. * @throws ArithmeticException {@code modulus} == 0. */ T mod(UInt256 modulus); /** * Returns a value that is {@code (this mod modulus)}. * * @param modulus The modulus. * @return {@code this mod modulus}. * @throws ArithmeticException {@code modulus} ≤ 0. */ T mod(long modulus); /** * @return True if this value fits a java {@code int} (i.e. is less or equal to {@code Integer.MAX_VALUE}). */ default boolean fitsInt() { // Ints are 4 bytes, so anything but the 4 last bytes must be zeroes Bytes32 bytes = toBytes(); for (int i = 0; i < Bytes32.SIZE - 4; i++) { if (bytes.get(i) != 0) return false; } // Lastly, the left-most byte of the int must not start with a 1. return bytes.get(Bytes32.SIZE - 4) >= 0; } /** * @return This value as a java {@code int} assuming it is small enough to fit an {@code int}. * @throws ArithmeticException If the value does not fit an {@code int}, that is if {@code * !fitsInt()}. */ default int intValue() { if (!fitsInt()) { throw new ArithmeticException("Value does not fit a 4 byte int"); } return toBytes().getInt(Bytes32.SIZE - 4); } /** * @return True if this value fits a java {@code long} (i.e. is less or equal to {@code Long.MAX_VALUE}). */ default boolean fitsLong() { // Longs are 8 bytes, so anything but the 8 last bytes must be zeroes for (int i = 0; i < Bytes32.SIZE - 8; i++) { if (toBytes().get(i) != 0) return false; } // Lastly, the left-most byte of the long must not start with a 1. return toBytes().get(Bytes32.SIZE - 8) >= 0; } /** * @return This value as a java {@code long} assuming it is small enough to fit a {@code long}. * @throws ArithmeticException If the value does not fit a {@code long}, that is if {@code * !fitsLong()}. */ default long toLong() { if (!fitsLong()) { throw new ArithmeticException("Value does not fit a 8 byte long"); } return toBytes().getLong(Bytes32.SIZE - 8); } /** * @return This value as a {@link BigInteger}. */ default BigInteger toBigInteger() { return toBytes().toUnsignedBigInteger(); } /** * This value represented as an hexadecimal string. * *

* Note that this representation includes all the 32 underlying bytes, no matter what the integer actually represents * (in other words, it can have many leading zeros). For a shorter representation that don't include leading zeros, * use {@link #toShortHexString}. * * @return This value represented as an hexadecimal string. */ default String toHexString() { return toBytes().toHexString(); } /** @return This value represented as a minimal hexadecimal string (without any leading zero). */ default String toShortHexString() { return toBytes().toShortHexString(); } /** * Convert this value to a {@link UInt256}. * * @return This value as a {@link UInt256}. */ UInt256 toUInt256(); /** * @return The value as bytes. */ Bytes32 toBytes(); /** * @return The value as bytes without any leading zero bytes. */ Bytes toMinimalBytes(); /** * @return the number of zero bits preceding the highest-order ("leftmost") one-bit in the binary representation of * this value, or 256 if the value is equal to zero. */ default int numberOfLeadingZeros() { return toBytes().numberOfLeadingZeros(); } /** * @return The number of bits following and including the highest-order ("leftmost") one-bit in the binary * representation of this value, or zero if all bits are zero. */ default int bitLength() { return toBytes().bitLength(); } } cava-0.6.0/units/src/main/java/net/consensys/cava/units/bigints/UInt256ValueDomain.java000066400000000000000000000034661341750772100306760ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.units.bigints; import java.util.function.Function; import com.google.common.collect.DiscreteDomain; /** * A {@link DiscreteDomain} over a {@link UInt256Value}. */ public final class UInt256ValueDomain> extends DiscreteDomain { private final T minValue; private final T maxValue; /** * @param ctor The constructor for the {@link UInt256Value} type. */ public UInt256ValueDomain(Function ctor) { this.minValue = ctor.apply(UInt256.MIN_VALUE); this.maxValue = ctor.apply(UInt256.MAX_VALUE); } @Override public T next(T value) { return value.add(1); } @Override public T previous(T value) { return value.subtract(1); } @Override public long distance(T start, T end) { boolean negativeDistance = start.compareTo(end) < 0; T distance = negativeDistance ? end.subtract(start) : start.subtract(end); if (!distance.fitsLong()) { return negativeDistance ? Long.MIN_VALUE : Long.MAX_VALUE; } long distanceLong = distance.toLong(); return negativeDistance ? -distanceLong : distanceLong; } @Override public T minValue() { return minValue; } @Override public T maxValue() { return maxValue; } } cava-0.6.0/units/src/main/java/net/consensys/cava/units/bigints/UInt256s.java000066400000000000000000000026351341750772100267310ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.units.bigints; /** Static utility methods on UInt256 values. */ public final class UInt256s { private UInt256s() {} /** * Returns the maximum of two UInt256 values. * * @param v1 The first value. * @param v2 The second value. * @return The maximum of {@code v1} and {@code v2}. * @param The concrete type of the two values. */ public static > T max(T v1, T v2) { return (v1.compareTo(v2)) >= 0 ? v1 : v2; } /** * Returns the minimum of two UInt256 values. * * @param v1 The first value. * @param v2 The second value. * @return The minimum of {@code v1} and {@code v2}. * @param The concrete type of the two values. */ public static > T min(T v1, T v2) { return (v1.compareTo(v2)) < 0 ? v1 : v2; } } cava-0.6.0/units/src/main/java/net/consensys/cava/units/bigints/package-info.java000066400000000000000000000003051341750772100277460ustar00rootroot00000000000000/** * Classes and utilities for working with 256 bit integers. */ @ParametersAreNonnullByDefault package net.consensys.cava.units.bigints; import javax.annotation.ParametersAreNonnullByDefault; cava-0.6.0/units/src/main/java/net/consensys/cava/units/ethereum/000077500000000000000000000000001341750772100247405ustar00rootroot00000000000000cava-0.6.0/units/src/main/java/net/consensys/cava/units/ethereum/Gas.java000066400000000000000000000103301341750772100263120ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.units.ethereum; import static com.google.common.base.Preconditions.checkArgument; import net.consensys.cava.bytes.Bytes; import net.consensys.cava.bytes.MutableBytes; import net.consensys.cava.units.bigints.UInt256; import java.math.BigInteger; import com.google.common.base.Objects; /** * A unit measure of Gas as used by the Ethereum VM. */ public final class Gas { private final static int MAX_CONSTANT = 64; private final static BigInteger BI_MAX_CONSTANT = BigInteger.valueOf(MAX_CONSTANT); private final static UInt256 UINT256_MAX_CONSTANT = UInt256.valueOf(MAX_CONSTANT); private static Gas CONSTANTS[] = new Gas[MAX_CONSTANT + 1]; static { CONSTANTS[0] = new Gas(0L); for (int i = 1; i <= MAX_CONSTANT; ++i) { CONSTANTS[i] = new Gas(i); } } private final long value; private Gas(long value) { this.value = value; } /** * Return a {@link Gas} containing the specified value. * * @param value The value to create a {@link Gas} for. * @return A {@link Gas} containing the specified value. * @throws IllegalArgumentException If the value is negative. */ public static Gas valueOf(UInt256 value) { if (value.compareTo(UINT256_MAX_CONSTANT) <= 0) { return CONSTANTS[value.intValue()]; } if (!value.fitsLong()) { throw new IllegalArgumentException("Gas value cannot be larger than 2^63 - 1"); } return new Gas(value.toLong()); } /** * Return a {@link Gas} containing the specified value. * * @param value The value to create a {@link Gas} for. * @return A {@link Gas} containing the specified value. * @throws IllegalArgumentException If the value is negative. */ public static Gas valueOf(long value) { checkArgument(value >= 0, "Argument must be positive"); if (value <= MAX_CONSTANT) { return CONSTANTS[(int) value]; } return new Gas(value); } /** * Return a {@link Gas} containing the specified value. * * @param value The value to create a {@link Gas} for. * @return A {@link Gas} containing the specified value. * @throws IllegalArgumentException If the value is negative. */ public static Gas valueOf(BigInteger value) { checkArgument(value.signum() >= 0, "Argument must be positive"); if (value.compareTo(BI_MAX_CONSTANT) <= 0) { return CONSTANTS[value.intValue()]; } try { return new Gas(value.longValueExact()); } catch (ArithmeticException e) { throw new IllegalArgumentException(e.getMessage(), e); } } /** * The price of this amount of gas given the provided price per unit of gas. * * @param gasPrice The price per unit of gas. * @return The price of this amount of gas for a per unit of gas price of {@code gasPrice}. */ public Wei priceFor(Wei gasPrice) { return Wei.valueOf(gasPrice.toUInt256().multiply(value).toUInt256()); } public Gas add(Gas other) { return Gas.valueOf(Math.addExact(value, other.value)); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Gas)) { return false; } Gas gas = (Gas) o; return value == gas.value; } @Override public int hashCode() { return Objects.hashCode(value); } @Override public String toString() { return "Gas{" + "value=" + value + '}'; } public long toLong() { return value; } public Bytes toBytes() { MutableBytes bytes = MutableBytes.create(8); bytes.setLong(0, value); return bytes; } public Bytes toMinimalBytes() { return Bytes.minimalBytes(value); } public int compareTo(long other) { return Long.compare(value, other); } } cava-0.6.0/units/src/main/java/net/consensys/cava/units/ethereum/Wei.java000066400000000000000000000056251341750772100263370ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.units.ethereum; import static com.google.common.base.Preconditions.checkArgument; import net.consensys.cava.units.bigints.BaseUInt256Value; import net.consensys.cava.units.bigints.UInt256; import java.math.BigInteger; /** * A unit measure of Wei as used by the Ethereum VM. */ public final class Wei extends BaseUInt256Value { private final static int MAX_CONSTANT = 64; private final static BigInteger BI_MAX_CONSTANT = BigInteger.valueOf(MAX_CONSTANT); private final static UInt256 UINT256_MAX_CONSTANT = UInt256.valueOf(MAX_CONSTANT); private static Wei CONSTANTS[] = new Wei[MAX_CONSTANT + 1]; static { CONSTANTS[0] = new Wei(UInt256.ZERO); for (int i = 1; i <= MAX_CONSTANT; ++i) { CONSTANTS[i] = new Wei(i); } } private Wei(UInt256 bytes) { super(bytes, Wei::new); } /** * Return a {@link Wei} containing the specified value. * * @param value The value to create a {@link Wei} for. * @return A {@link Wei} containing the specified value. * @throws IllegalArgumentException If the value is negative. */ public static Wei valueOf(UInt256 value) { if (value.compareTo(UINT256_MAX_CONSTANT) <= 0) { return CONSTANTS[value.intValue()]; } return new Wei(value); } private Wei(long value) { super(value, Wei::new); } /** * Return a {@link Wei} containing the specified value. * * @param value The value to create a {@link Wei} for. * @return A {@link Wei} containing the specified value. * @throws IllegalArgumentException If the value is negative. */ public static Wei valueOf(long value) { checkArgument(value >= 0, "Argument must be positive"); if (value <= MAX_CONSTANT) { return CONSTANTS[(int) value]; } return new Wei(value); } private Wei(BigInteger value) { super(value, Wei::new); } /** * Return a {@link Wei} containing the specified value. * * @param value The value to create a {@link Wei} for. * @return A {@link Wei} containing the specified value. * @throws IllegalArgumentException If the value is negative. */ public static Wei valueOf(BigInteger value) { checkArgument(value.signum() >= 0, "Argument must be positive"); if (value.compareTo(BI_MAX_CONSTANT) <= 0) { return CONSTANTS[value.intValue()]; } return new Wei(value); } } cava-0.6.0/units/src/main/java/net/consensys/cava/units/ethereum/package-info.java000066400000000000000000000003041341750772100301240ustar00rootroot00000000000000/** * Classes and utilities for working with Ethereum units. */ @ParametersAreNonnullByDefault package net.consensys.cava.units.ethereum; import javax.annotation.ParametersAreNonnullByDefault; cava-0.6.0/units/src/main/java/net/consensys/cava/units/package-info.java000066400000000000000000000004501341750772100263100ustar00rootroot00000000000000/** * 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; cava-0.6.0/units/src/test/000077500000000000000000000000001341750772100153465ustar00rootroot00000000000000cava-0.6.0/units/src/test/java/000077500000000000000000000000001341750772100162675ustar00rootroot00000000000000cava-0.6.0/units/src/test/java/net/000077500000000000000000000000001341750772100170555ustar00rootroot00000000000000cava-0.6.0/units/src/test/java/net/consensys/000077500000000000000000000000001341750772100211015ustar00rootroot00000000000000cava-0.6.0/units/src/test/java/net/consensys/cava/000077500000000000000000000000001341750772100220135ustar00rootroot00000000000000cava-0.6.0/units/src/test/java/net/consensys/cava/units/000077500000000000000000000000001341750772100231555ustar00rootroot00000000000000cava-0.6.0/units/src/test/java/net/consensys/cava/units/bigints/000077500000000000000000000000001341750772100246145ustar00rootroot00000000000000cava-0.6.0/units/src/test/java/net/consensys/cava/units/bigints/BaseUInt256ValueTest.java000066400000000000000000001100701341750772100312220ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.units.bigints; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import net.consensys.cava.bytes.Bytes; import java.math.BigInteger; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; // This test is in a `test` sub-package to ensure that it does not have access to package-private // methods within the bigints package, as it should be testing the usage of the public API. class BaseUInt256ValueTest { private static class Value extends BaseUInt256Value { static final Value MAX_VALUE = new Value(UInt256.MAX_VALUE); Value(UInt256 v) { super(v, Value::new); } Value(long v) { super(v, Value::new); } Value(BigInteger s) { super(s, Value::new); } } private static Value v(long v) { return new Value(v); } private static Value biv(String s) { return new Value(new BigInteger(s)); } private static Value hv(String s) { return new Value(UInt256.fromHexString(s)); } @ParameterizedTest @MethodSource("addProvider") void add(Value v1, Value v2, Value expected) { assertValueEquals(expected, v1.add(v2)); } private static Stream addProvider() { return Stream.of( Arguments.of(v(1), v(0), v(1)), Arguments.of(v(5), v(0), v(5)), Arguments.of(v(0), v(1), v(1)), Arguments.of(v(0), v(100), v(100)), Arguments.of(v(2), v(2), v(4)), Arguments.of(v(100), v(90), v(190)), Arguments.of(biv("13492324908428420834234908342"), v(10), biv("13492324908428420834234908352")), Arguments.of(biv("13492324908428420834234908342"), v(23422141424214L), biv("13492324908428444256376332556")), Arguments.of(Value.MAX_VALUE, v(1), v(0)), Arguments.of(Value.MAX_VALUE, v(2), v(1)), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), v(1), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), Arguments.of(hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), v(1), Value.MAX_VALUE)); } @ParameterizedTest @MethodSource("addUInt256Provider") void addUInt256(Value v1, UInt256 v2, Value expected) { assertValueEquals(expected, v1.add(v2)); } private static Stream addUInt256Provider() { return Stream.of( Arguments.of(v(1), UInt256.ZERO, v(1)), Arguments.of(v(5), UInt256.ZERO, v(5)), Arguments.of(v(0), UInt256.ONE, v(1)), Arguments.of(v(0), UInt256.valueOf(100), v(100)), Arguments.of(v(2), UInt256.valueOf(2), v(4)), Arguments.of(v(100), UInt256.valueOf(90), v(190)), Arguments.of(biv("13492324908428420834234908342"), UInt256.valueOf(10), biv("13492324908428420834234908352")), Arguments.of( biv("13492324908428420834234908342"), UInt256.valueOf(23422141424214L), biv("13492324908428444256376332556")), Arguments.of(Value.MAX_VALUE, UInt256.valueOf(1), v(0)), Arguments.of(Value.MAX_VALUE, UInt256.valueOf(2), v(1)), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), UInt256.valueOf(1), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), UInt256.valueOf(1), Value.MAX_VALUE)); } @ParameterizedTest @MethodSource("addLongProvider") void addLong(Value v1, long v2, Value expected) { assertValueEquals(expected, v1.add(v2)); } private static Stream addLongProvider() { return Stream.of( Arguments.of(v(1), 0L, v(1)), Arguments.of(v(5), 0L, v(5)), Arguments.of(v(0), 1L, v(1)), Arguments.of(v(0), 100L, v(100)), Arguments.of(v(2), 2L, v(4)), Arguments.of(v(100), 90L, v(190)), Arguments.of(biv("13492324908428420834234908342"), 10L, biv("13492324908428420834234908352")), Arguments.of(biv("13492324908428420834234908342"), 23422141424214L, biv("13492324908428444256376332556")), Arguments.of(Value.MAX_VALUE, 1L, v(0)), Arguments.of(Value.MAX_VALUE, 2L, v(1)), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), 1L, hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), Arguments.of(hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), 1L, Value.MAX_VALUE), Arguments.of(v(10), -5L, v(5)), Arguments.of(v(0), -1L, Value.MAX_VALUE)); } @ParameterizedTest @MethodSource("addModProvider") void addMod(Value v1, Value v2, UInt256 m, Value expected) { assertValueEquals(expected, v1.addMod(v2, m)); } private static Stream addModProvider() { return Stream.of( Arguments.of(v(0), v(1), UInt256.valueOf(2), v(1)), Arguments.of(v(1), v(1), UInt256.valueOf(2), v(0)), Arguments.of(Value.MAX_VALUE.subtract(2), v(1), UInt256.MAX_VALUE, Value.MAX_VALUE.subtract(1)), Arguments.of(Value.MAX_VALUE.subtract(1), v(1), UInt256.MAX_VALUE, v(0)), Arguments.of(v(2), v(1), UInt256.valueOf(2), v(1)), Arguments.of(v(3), v(2), UInt256.valueOf(6), v(5)), Arguments.of(v(3), v(4), UInt256.valueOf(2), v(1))); } @Test void shouldThrowForAddModOfZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(v(1), UInt256.ZERO)); assertEquals("addMod with zero modulus", exception.getMessage()); } @ParameterizedTest @MethodSource("addModUInt256UInt256Provider") void addModUInt256UInt256(Value v1, UInt256 v2, UInt256 m, Value expected) { assertValueEquals(expected, v1.addMod(v2, m)); } private static Stream addModUInt256UInt256Provider() { return Stream.of( Arguments.of(v(0), UInt256.ONE, UInt256.valueOf(2), v(1)), Arguments.of(v(1), UInt256.ONE, UInt256.valueOf(2), v(0)), Arguments.of(Value.MAX_VALUE.subtract(2), UInt256.ONE, UInt256.MAX_VALUE, Value.MAX_VALUE.subtract(1)), Arguments.of(Value.MAX_VALUE.subtract(1), UInt256.ONE, UInt256.MAX_VALUE, v(0)), Arguments.of(v(2), UInt256.ONE, UInt256.valueOf(2), v(1)), Arguments.of(v(3), UInt256.valueOf(2), UInt256.valueOf(6), v(5)), Arguments.of(v(3), UInt256.valueOf(4), UInt256.valueOf(2), v(1))); } @Test void shouldThrowForAddModLongUInt256OfZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, UInt256.ZERO)); assertEquals("addMod with zero modulus", exception.getMessage()); } @ParameterizedTest @MethodSource("addModLongUInt256Provider") void addModLongUInt256(Value v1, long v2, UInt256 m, Value expected) { assertValueEquals(expected, v1.addMod(v2, m)); } private static Stream addModLongUInt256Provider() { return Stream.of( Arguments.of(v(0), 1L, UInt256.valueOf(2), v(1)), Arguments.of(v(1), 1L, UInt256.valueOf(2), v(0)), Arguments.of(Value.MAX_VALUE.subtract(2), 1L, UInt256.MAX_VALUE, Value.MAX_VALUE.subtract(1)), Arguments.of(Value.MAX_VALUE.subtract(1), 1L, UInt256.MAX_VALUE, v(0)), Arguments.of(v(2), 1L, UInt256.valueOf(2), v(1)), Arguments.of(v(2), -1L, UInt256.valueOf(2), v(1)), Arguments.of(v(1), -7L, UInt256.valueOf(5), v(4))); } @Test void shouldThrowForAddModUInt256UInt256OfZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(UInt256.ONE, UInt256.ZERO)); assertEquals("addMod with zero modulus", exception.getMessage()); } @ParameterizedTest @MethodSource("addModLongLongProvider") void addModLongLong(Value v1, long v2, long m, Value expected) { assertValueEquals(expected, v1.addMod(v2, m)); } private static Stream addModLongLongProvider() { return Stream .of(Arguments.of(v(0), 1L, 2L, v(1)), Arguments.of(v(1), 1L, 2L, v(0)), Arguments.of(v(2), 1L, 2L, v(1))); } @Test void shouldThrowForAddModLongLongOfZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, 0)); assertEquals("addMod with zero modulus", exception.getMessage()); } @Test void shouldThrowForAddModLongLongOfNegative() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, -5)); assertEquals("addMod unsigned with negative modulus", exception.getMessage()); } @ParameterizedTest @MethodSource("subtractProvider") void subtract(Value v1, Value v2, Value expected) { assertValueEquals(expected, v1.subtract(v2)); } private static Stream subtractProvider() { return Stream.of( Arguments.of(v(1), v(0), v(1)), Arguments.of(v(5), v(0), v(5)), Arguments.of(v(2), v(1), v(1)), Arguments.of(v(100), v(100), v(0)), Arguments.of(biv("13492324908428420834234908342"), v(10), biv("13492324908428420834234908332")), Arguments.of(biv("13492324908428420834234908342"), v(23422141424214L), biv("13492324908428397412093484128")), Arguments.of(v(0), v(1), Value.MAX_VALUE), Arguments.of(v(1), v(2), Value.MAX_VALUE), Arguments.of(Value.MAX_VALUE, v(1), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); } @ParameterizedTest @MethodSource("subtractUInt256Provider") void subtractUInt256(Value v1, UInt256 v2, Value expected) { assertValueEquals(expected, v1.subtract(v2)); } private static Stream subtractUInt256Provider() { return Stream.of( Arguments.of(v(1), UInt256.ZERO, v(1)), Arguments.of(v(5), UInt256.ZERO, v(5)), Arguments.of(v(2), UInt256.ONE, v(1)), Arguments.of(v(100), UInt256.valueOf(100), v(0)), Arguments.of(biv("13492324908428420834234908342"), UInt256.valueOf(10), biv("13492324908428420834234908332")), Arguments.of( biv("13492324908428420834234908342"), UInt256.valueOf(23422141424214L), biv("13492324908428397412093484128")), Arguments.of(v(0), UInt256.ONE, Value.MAX_VALUE), Arguments.of(v(1), UInt256.valueOf(2), Value.MAX_VALUE), Arguments.of( Value.MAX_VALUE, UInt256.ONE, hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); } @ParameterizedTest @MethodSource("subtractLongProvider") void subtractLong(Value v1, long v2, Value expected) { assertValueEquals(expected, v1.subtract(v2)); } private static Stream subtractLongProvider() { return Stream.of( Arguments.of(v(1), 0L, v(1)), Arguments.of(v(5), 0L, v(5)), Arguments.of(v(2), 1L, v(1)), Arguments.of(v(100), 100L, v(0)), Arguments.of(biv("13492324908428420834234908342"), 10L, biv("13492324908428420834234908332")), Arguments.of(biv("13492324908428420834234908342"), 23422141424214L, biv("13492324908428397412093484128")), Arguments.of(v(0), 1L, Value.MAX_VALUE), Arguments.of(v(1), 2L, Value.MAX_VALUE), Arguments.of(Value.MAX_VALUE, 1L, hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), Arguments.of(v(0), -1L, v(1)), Arguments.of(v(0), -100L, v(100)), Arguments.of(v(2), -2L, v(4))); } @ParameterizedTest @MethodSource("multiplyProvider") void multiply(Value v1, Value v2, Value expected) { assertValueEquals(expected, v1.multiply(v2)); } private static Stream multiplyProvider() { return Stream.of( Arguments.of(v(0), v(2), v(0)), Arguments.of(v(1), v(2), v(2)), Arguments.of(v(2), v(2), v(4)), Arguments.of(v(3), v(2), v(6)), Arguments.of(v(4), v(2), v(8)), Arguments.of(v(10), v(18), v(180)), Arguments.of(biv("13492324908428420834234908341"), v(2), biv("26984649816856841668469816682")), Arguments.of(biv("13492324908428420834234908342"), v(2), biv("26984649816856841668469816684")), Arguments.of(v(2), v(8), v(16)), Arguments.of(v(7), v(8), v(56)), Arguments.of(v(8), v(8), v(64)), Arguments.of(v(17), v(8), v(136)), Arguments.of(biv("13492324908428420834234908342"), v(8), biv("107938599267427366673879266736")), Arguments.of(biv("13492324908428420834234908342"), v(2048), biv("27632281412461405868513092284416")), Arguments.of(biv("13492324908428420834234908342"), v(131072), biv("1768466010397529975584837906202624")), Arguments.of(v(22), v(0), v(0))); } @ParameterizedTest @MethodSource("multiplyUInt256Provider") void multiplyUInt256(Value v1, UInt256 v2, Value expected) { assertValueEquals(expected, v1.multiply(v2)); } private static Stream multiplyUInt256Provider() { return Stream.of( Arguments.of(v(0), UInt256.valueOf(2), v(0)), Arguments.of(v(1), UInt256.valueOf(2), v(2)), Arguments.of(v(2), UInt256.valueOf(2), v(4)), Arguments.of(v(3), UInt256.valueOf(2), v(6)), Arguments.of(v(4), UInt256.valueOf(2), v(8)), Arguments.of(v(10), UInt256.valueOf(18), v(180)), Arguments.of(biv("13492324908428420834234908341"), UInt256.valueOf(2), biv("26984649816856841668469816682")), Arguments.of(biv("13492324908428420834234908342"), UInt256.valueOf(2), biv("26984649816856841668469816684")), Arguments.of(v(2), UInt256.valueOf(8), v(16)), Arguments.of(v(7), UInt256.valueOf(8), v(56)), Arguments.of(v(8), UInt256.valueOf(8), v(64)), Arguments.of(v(17), UInt256.valueOf(8), v(136)), Arguments.of(biv("13492324908428420834234908342"), UInt256.valueOf(8), biv("107938599267427366673879266736")), Arguments .of(biv("13492324908428420834234908342"), UInt256.valueOf(2048), biv("27632281412461405868513092284416")), Arguments.of( biv("13492324908428420834234908342"), UInt256.valueOf(131072), biv("1768466010397529975584837906202624")), Arguments.of(v(22), UInt256.ZERO, v(0)), Arguments.of( hv("0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), UInt256.valueOf(2), hv("0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), UInt256.valueOf(2), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); } @ParameterizedTest @MethodSource("multiplyLongProvider") void multiplyLong(Value v1, long v2, Value expected) { assertValueEquals(expected, v1.multiply(v2)); } private static Stream multiplyLongProvider() { return Stream.of( Arguments.of(v(0), 2L, v(0)), Arguments.of(v(1), 2L, v(2)), Arguments.of(v(2), 2L, v(4)), Arguments.of(v(3), 2L, v(6)), Arguments.of(v(4), 2L, v(8)), Arguments.of(v(10), 18L, v(180)), Arguments.of(biv("13492324908428420834234908341"), 2L, biv("26984649816856841668469816682")), Arguments.of(biv("13492324908428420834234908342"), 2L, biv("26984649816856841668469816684")), Arguments.of(v(2), 8L, v(16)), Arguments.of(v(7), 8L, v(56)), Arguments.of(v(8), 8L, v(64)), Arguments.of(v(17), 8L, v(136)), Arguments.of(biv("13492324908428420834234908342"), 8L, biv("107938599267427366673879266736")), Arguments.of(biv("13492324908428420834234908342"), 2048L, biv("27632281412461405868513092284416")), Arguments.of(biv("13492324908428420834234908342"), 131072L, biv("1768466010397529975584837906202624")), Arguments.of(v(22), 0L, v(0)), Arguments.of( hv("0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), 2L, hv("0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), 2L, hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); } @Test void shouldThrowForMultiplyLongOfNegative() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiply(-5)); assertEquals("multiply unsigned by negative", exception.getMessage()); } @ParameterizedTest @MethodSource("multiplyModProvider") void multiplyMod(Value v1, Value v2, UInt256 m, Value expected) { assertValueEquals(expected, v1.multiplyMod(v2, m)); } private static Stream multiplyModProvider() { return Stream.of( Arguments.of(v(0), v(5), UInt256.valueOf(2), v(0)), Arguments.of(v(2), v(3), UInt256.valueOf(7), v(6)), Arguments.of(v(2), v(3), UInt256.valueOf(6), v(0)), Arguments.of(v(2), v(0), UInt256.valueOf(6), v(0)), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), v(2), UInt256.MAX_VALUE, hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); } @Test void shouldThrowForMultiplyModOfModZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(v(1), UInt256.ZERO)); assertEquals("multiplyMod with zero modulus", exception.getMessage()); } @ParameterizedTest @MethodSource("multiplyModUInt256UInt256Provider") void multiplyModUInt256UInt256(Value v1, UInt256 v2, UInt256 m, Value expected) { assertValueEquals(expected, v1.multiplyMod(v2, m)); } private static Stream multiplyModUInt256UInt256Provider() { return Stream.of( Arguments.of(v(0), UInt256.valueOf(5), UInt256.valueOf(2), v(0)), Arguments.of(v(2), UInt256.valueOf(3), UInt256.valueOf(7), v(6)), Arguments.of(v(2), UInt256.valueOf(3), UInt256.valueOf(6), v(0)), Arguments.of(v(2), UInt256.ZERO, UInt256.valueOf(6), v(0)), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), UInt256.valueOf(2), UInt256.MAX_VALUE, hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); } @Test void shouldThrowForMultiplyModUInt256UInt256OfModZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(UInt256.valueOf(5), UInt256.ZERO)); assertEquals("multiplyMod with zero modulus", exception.getMessage()); } @ParameterizedTest @MethodSource("multiplyModLongUInt256Provider") void multiplyModLongUInt256(Value v1, long v2, UInt256 m, Value expected) { assertValueEquals(expected, v1.multiplyMod(v2, m)); } private static Stream multiplyModLongUInt256Provider() { return Stream.of( Arguments.of(v(0), 5L, UInt256.valueOf(2), v(0)), Arguments.of(v(2), 3L, UInt256.valueOf(7), v(6)), Arguments.of(v(2), 3L, UInt256.valueOf(6), v(0)), Arguments.of(v(2), 0L, UInt256.valueOf(6), v(0)), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), 2L, UInt256.MAX_VALUE, hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); } @Test void shouldThrowForMultiplyModLongUInt256OfModZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(1L, UInt256.ZERO)); assertEquals("multiplyMod with zero modulus", exception.getMessage()); } @Test void shouldThrowForMultiplyModLongUInt256OfNegative() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(-1, UInt256.valueOf(2))); assertEquals("multiplyMod unsigned by negative", exception.getMessage()); } @ParameterizedTest @MethodSource("multiplyModLongLongProvider") void multiplyModLongLong(Value v1, long v2, long m, Value expected) { assertValueEquals(expected, v1.multiplyMod(v2, m)); } private static Stream multiplyModLongLongProvider() { return Stream.of( Arguments.of(v(0), 5L, 2L, v(0)), Arguments.of(v(2), 3L, 7L, v(6)), Arguments.of(v(2), 3L, 6L, v(0)), Arguments.of(v(2), 0L, 6L, v(0)), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), 2L, Long.MAX_VALUE, hv("0x000000000000000000000000000000000000000000000000000000000000001C"))); } @Test void shouldThrowForMultiplyModLongLongOfModZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1, 0)); assertEquals("multiplyMod with zero modulus", exception.getMessage()); } @Test void shouldThrowForMultiplyModLongLongOfModNegative() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiplyMod(5, -7)); assertEquals("multiplyMod unsigned with negative modulus", exception.getMessage()); } @Test void shouldThrowForMultiplyModLongLongOfNegative() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, 2)); assertEquals("multiplyMod unsigned by negative", exception.getMessage()); } @ParameterizedTest @MethodSource("divideProvider") void divide(Value v1, Value v2, Value expected) { assertValueEquals(expected, v1.divide(v2)); } private static Stream divideProvider() { return Stream.of( Arguments.of(v(0), v(2), v(0)), Arguments.of(v(1), v(2), v(0)), Arguments.of(v(2), v(2), v(1)), Arguments.of(v(3), v(2), v(1)), Arguments.of(v(4), v(2), v(2)), Arguments.of(biv("13492324908428420834234908341"), v(2), biv("6746162454214210417117454170")), Arguments.of(biv("13492324908428420834234908342"), v(2), biv("6746162454214210417117454171")), Arguments.of(biv("13492324908428420834234908343"), v(2), biv("6746162454214210417117454171")), Arguments.of(v(2), v(8), v(0)), Arguments.of(v(7), v(8), v(0)), Arguments.of(v(8), v(8), v(1)), Arguments.of(v(9), v(8), v(1)), Arguments.of(v(17), v(8), v(2)), Arguments.of(v(1024), v(8), v(128)), Arguments.of(v(1026), v(8), v(128)), Arguments.of(biv("13492324908428420834234908342"), v(8), biv("1686540613553552604279363542")), Arguments.of(biv("13492324908428420834234908342"), v(2048), biv("6588049271693564860466263")), Arguments.of(biv("13492324908428420834234908342"), v(131072), biv("102938269870211950944785"))); } @Test void shouldThrowForDivideByZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(v(0))); assertEquals("divide by zero", exception.getMessage()); } @ParameterizedTest @MethodSource("divideUInt256Provider") void divideUInt256(Value v1, UInt256 v2, Value expected) { assertValueEquals(expected, v1.divide(v2)); } private static Stream divideUInt256Provider() { return Stream.of( Arguments.of(v(0), UInt256.valueOf(2), v(0)), Arguments.of(v(1), UInt256.valueOf(2), v(0)), Arguments.of(v(2), UInt256.valueOf(2), v(1)), Arguments.of(v(3), UInt256.valueOf(2), v(1)), Arguments.of(v(4), UInt256.valueOf(2), v(2)), Arguments.of(biv("13492324908428420834234908341"), UInt256.valueOf(2), biv("6746162454214210417117454170")), Arguments.of(biv("13492324908428420834234908342"), UInt256.valueOf(2), biv("6746162454214210417117454171")), Arguments.of(biv("13492324908428420834234908343"), UInt256.valueOf(2), biv("6746162454214210417117454171")), Arguments.of(v(2), UInt256.valueOf(8), v(0)), Arguments.of(v(7), UInt256.valueOf(8), v(0)), Arguments.of(v(8), UInt256.valueOf(8), v(1)), Arguments.of(v(9), UInt256.valueOf(8), v(1)), Arguments.of(v(17), UInt256.valueOf(8), v(2)), Arguments.of(v(1024), UInt256.valueOf(8), v(128)), Arguments.of(v(1026), UInt256.valueOf(8), v(128)), Arguments.of(biv("13492324908428420834234908342"), UInt256.valueOf(8), biv("1686540613553552604279363542")), Arguments.of(biv("13492324908428420834234908342"), UInt256.valueOf(2048), biv("6588049271693564860466263")), Arguments.of(biv("13492324908428420834234908342"), UInt256.valueOf(131072), biv("102938269870211950944785"))); } @Test void shouldThrowForDivideUInt256ByZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(UInt256.ZERO)); assertEquals("divide by zero", exception.getMessage()); } @ParameterizedTest @MethodSource("divideLongProvider") void divideLong(Value v1, long v2, Value expected) { assertValueEquals(expected, v1.divide(v2)); } private static Stream divideLongProvider() { return Stream.of( Arguments.of(v(0), 2L, v(0)), Arguments.of(v(1), 2L, v(0)), Arguments.of(v(2), 2L, v(1)), Arguments.of(v(3), 2L, v(1)), Arguments.of(v(4), 2L, v(2)), Arguments.of(biv("13492324908428420834234908341"), 2L, biv("6746162454214210417117454170")), Arguments.of(biv("13492324908428420834234908342"), 2L, biv("6746162454214210417117454171")), Arguments.of(biv("13492324908428420834234908343"), 2L, biv("6746162454214210417117454171")), Arguments.of(v(2), 8L, v(0)), Arguments.of(v(7), 8L, v(0)), Arguments.of(v(8), 8L, v(1)), Arguments.of(v(9), 8L, v(1)), Arguments.of(v(17), 8L, v(2)), Arguments.of(v(1024), 8L, v(128)), Arguments.of(v(1026), 8L, v(128)), Arguments.of(biv("13492324908428420834234908342"), 8L, biv("1686540613553552604279363542")), Arguments.of(biv("13492324908428420834234908342"), 2048L, biv("6588049271693564860466263")), Arguments.of(biv("13492324908428420834234908342"), 131072L, biv("102938269870211950944785"))); } @Test void shouldThrowForDivideLongByZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(0)); assertEquals("divide by zero", exception.getMessage()); } @Test void shouldThrowForDivideLongByNegative() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(-5)); assertEquals("divide unsigned by negative", exception.getMessage()); } @ParameterizedTest @MethodSource("powUInt256Provider") void powUInt256(Value v1, UInt256 v2, Value expected) { assertValueEquals(expected, v1.pow(v2)); } private static Stream powUInt256Provider() { return Stream.of( Arguments.of(v(0), UInt256.valueOf(2), v(0)), Arguments.of(v(2), UInt256.valueOf(2), v(4)), Arguments.of(v(2), UInt256.valueOf(8), v(256)), Arguments.of(v(3), UInt256.valueOf(3), v(27)), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0F0"), UInt256.valueOf(3), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A920E119A2F000"))); } @ParameterizedTest @MethodSource("powLongProvider") void powLong(Value v1, long v2, Value expected) { assertValueEquals(expected, v1.pow(v2)); } private static Stream powLongProvider() { return Stream.of( Arguments.of(v(0), 2L, v(0)), Arguments.of(v(2), 2L, v(4)), Arguments.of(v(2), 8L, v(256)), Arguments.of(v(3), 3L, v(27)), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0F0"), 3L, hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A920E119A2F000")), Arguments.of(v(3), -3L, hv("0x2F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA13"))); } @ParameterizedTest @MethodSource("modUInt256Provider") void modUInt256(Value v1, UInt256 v2, Value expected) { assertValueEquals(expected, v1.mod(v2)); } private static Stream modUInt256Provider() { return Stream.of( Arguments.of(v(0), UInt256.valueOf(2), v(0)), Arguments.of(v(1), UInt256.valueOf(2), v(1)), Arguments.of(v(2), UInt256.valueOf(2), v(0)), Arguments.of(v(3), UInt256.valueOf(2), v(1)), Arguments.of(biv("13492324908428420834234908342"), UInt256.valueOf(2), v(0)), Arguments.of(biv("13492324908428420834234908343"), UInt256.valueOf(2), v(1)), Arguments.of(v(0), UInt256.valueOf(8), v(0)), Arguments.of(v(1), UInt256.valueOf(8), v(1)), Arguments.of(v(2), UInt256.valueOf(8), v(2)), Arguments.of(v(3), UInt256.valueOf(8), v(3)), Arguments.of(v(7), UInt256.valueOf(8), v(7)), Arguments.of(v(8), UInt256.valueOf(8), v(0)), Arguments.of(v(9), UInt256.valueOf(8), v(1)), Arguments.of(v(1024), UInt256.valueOf(8), v(0)), Arguments.of(v(1026), UInt256.valueOf(8), v(2)), Arguments.of(biv("13492324908428420834234908342"), UInt256.valueOf(8), v(6)), Arguments.of(biv("13492324908428420834234908343"), UInt256.valueOf(8), v(7)), Arguments.of(biv("13492324908428420834234908344"), UInt256.valueOf(8), v(0))); } @Test void shouldThrowForModUInt256ByZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(UInt256.ZERO)); assertEquals("mod by zero", exception.getMessage()); } @ParameterizedTest @MethodSource("modLongProvider") void modLong(Value v1, long v2, Value expected) { assertValueEquals(expected, v1.mod(v2)); } private static Stream modLongProvider() { return Stream.of( Arguments.of(v(0), 2L, v(0)), Arguments.of(v(1), 2L, v(1)), Arguments.of(v(2), 2L, v(0)), Arguments.of(v(3), 2L, v(1)), Arguments.of(biv("13492324908428420834234908342"), 2L, v(0)), Arguments.of(biv("13492324908428420834234908343"), 2L, v(1)), Arguments.of(v(0), 8L, v(0)), Arguments.of(v(1), 8L, v(1)), Arguments.of(v(2), 8L, v(2)), Arguments.of(v(3), 8L, v(3)), Arguments.of(v(7), 8L, v(7)), Arguments.of(v(8), 8L, v(0)), Arguments.of(v(9), 8L, v(1)), Arguments.of(v(1024), 8L, v(0)), Arguments.of(v(1026), 8L, v(2)), Arguments.of(biv("13492324908428420834234908342"), 8L, v(6)), Arguments.of(biv("13492324908428420834234908343"), 8L, v(7)), Arguments.of(biv("13492324908428420834234908344"), 8L, v(0))); } @Test void shouldThrowForModLongByZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(0)); assertEquals("mod by zero", exception.getMessage()); } @Test void shouldThrowForModLongByNegative() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(-5)); assertEquals("mod by negative", exception.getMessage()); } @ParameterizedTest @MethodSource("compareToProvider") void compareTo(Value v1, Value v2, int expected) { assertEquals(expected, v1.compareTo(v2)); } private static Stream compareToProvider() { return Stream.of( Arguments.of(v(5), v(5), 0), Arguments.of(v(5), v(3), 1), Arguments.of(v(5), v(6), -1), Arguments.of( hv("0x0000000000000000000000000000000000000000000000000000000000000000"), hv("0x0000000000000000000000000000000000000000000000000000000000000000"), 0), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), 0), Arguments.of( hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), 0), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0x0000000000000000000000000000000000000000000000000000000000000000"), 1), Arguments.of( hv("0x0000000000000000000000000000000000000000000000000000000000000000"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), -1), Arguments.of( hv("0x000000000000000000000000000001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), 1), Arguments.of( hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), -1)); } @ParameterizedTest @MethodSource("toBytesProvider") void toBytesTest(Value value, Bytes expected) { assertEquals(expected, value.toBytes()); } private static Stream toBytesProvider() { return Stream.of( Arguments .of(hv("0x00"), Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000000")), Arguments.of( hv("0x01000000"), Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000001000000")), Arguments.of( hv("0x0100000000"), Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000100000000")), Arguments.of( hv("0xf100000000ab"), Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000f100000000ab")), Arguments.of( hv("0x0400000000000000000000000000000000000000000000000000f100000000ab"), Bytes.fromHexString("0x0400000000000000000000000000000000000000000000000000f100000000ab"))); } @ParameterizedTest @MethodSource("toMinimalBytesProvider") void toMinimalBytesTest(Value value, Bytes expected) { assertEquals(expected, value.toMinimalBytes()); } private static Stream toMinimalBytesProvider() { return Stream.of( Arguments.of(hv("0x00"), Bytes.EMPTY), Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000")), Arguments.of(hv("0x0100000000"), Bytes.fromHexString("0x0100000000")), Arguments.of(hv("0xf100000000ab"), Bytes.fromHexString("0xf100000000ab")), Arguments.of( hv("0x0400000000000000000000000000000000000000000000000000f100000000ab"), Bytes.fromHexString("0x0400000000000000000000000000000000000000000000000000f100000000ab"))); } @ParameterizedTest @MethodSource("numberOfLeadingZerosProvider") void numberOfLeadingZeros(Value value, int expected) { assertEquals(expected, value.numberOfLeadingZeros()); } private static Stream numberOfLeadingZerosProvider() { return Stream.of( Arguments.of(hv("0x00"), 256), Arguments.of(hv("0x01"), 255), Arguments.of(hv("0x02"), 254), Arguments.of(hv("0x03"), 254), Arguments.of(hv("0x0F"), 252), Arguments.of(hv("0x8F"), 248), Arguments.of(hv("0x100000000"), 223)); } @ParameterizedTest @MethodSource("bitLengthProvider") void bitLength(Value value, int expected) { assertEquals(expected, value.bitLength()); } private static Stream bitLengthProvider() { return Stream.of( Arguments.of(hv("0x00"), 0), Arguments.of(hv("0x01"), 1), Arguments.of(hv("0x02"), 2), Arguments.of(hv("0x03"), 2), Arguments.of(hv("0x0F"), 4), Arguments.of(hv("0x8F"), 8), Arguments.of(hv("0x100000000"), 33)); } private void assertValueEquals(Value expected, Value actual) { String msg = String.format("Expected %s but got %s", expected.toHexString(), actual.toHexString()); assertEquals(expected, actual, msg); } } cava-0.6.0/units/src/test/java/net/consensys/cava/units/bigints/UInt256Test.java000066400000000000000000001100141341750772100274300ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.units.bigints; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import net.consensys.cava.bytes.Bytes; import java.math.BigInteger; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; class UInt256Test { private static UInt256 v(long v) { return UInt256.valueOf(v); } private static UInt256 biv(String s) { return UInt256.valueOf(new BigInteger(s)); } private static UInt256 hv(String s) { return UInt256.fromHexString(s); } @ParameterizedTest @MethodSource("addProvider") void add(UInt256 v1, UInt256 v2, UInt256 expected) { assertValueEquals(expected, v1.add(v2)); } private static Stream addProvider() { return Stream.of( Arguments.of(v(1), v(0), v(1)), Arguments.of(v(5), v(0), v(5)), Arguments.of(v(0), v(1), v(1)), Arguments.of(v(0), v(100), v(100)), Arguments.of(v(2), v(2), v(4)), Arguments.of(v(100), v(90), v(190)), Arguments.of(biv("9223372036854775807"), v(1), biv("9223372036854775808")), Arguments.of(biv("13492324908428420834234908342"), v(10), biv("13492324908428420834234908352")), Arguments.of(biv("13492324908428420834234908342"), v(23422141424214L), biv("13492324908428444256376332556")), Arguments.of(UInt256.MAX_VALUE, v(1), v(0)), Arguments.of(UInt256.MAX_VALUE, v(2), v(1)), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), v(1), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), Arguments .of(hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), v(1), UInt256.MAX_VALUE)); } @ParameterizedTest @MethodSource("addLongProvider") void addLong(UInt256 v1, long v2, UInt256 expected) { assertValueEquals(expected, v1.add(v2)); } private static Stream addLongProvider() { return Stream.of( Arguments.of(v(1), 0L, v(1)), Arguments.of(v(5), 0L, v(5)), Arguments.of(v(0), 1L, v(1)), Arguments.of(v(0), 100L, v(100)), Arguments.of(v(2), 2L, v(4)), Arguments.of(v(100), 90L, v(190)), Arguments.of(biv("13492324908428420834234908342"), 10L, biv("13492324908428420834234908352")), Arguments.of(biv("13492324908428420834234908342"), 23422141424214L, biv("13492324908428444256376332556")), Arguments.of(UInt256.MAX_VALUE, 1L, v(0)), Arguments.of(UInt256.MAX_VALUE, 2L, v(1)), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0"), 1L, hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1")), Arguments.of(hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), 1L, UInt256.MAX_VALUE), Arguments.of(v(10), -5L, v(5)), Arguments.of(v(0), -1L, UInt256.MAX_VALUE)); } @ParameterizedTest @MethodSource("addModProvider") void addMod(UInt256 v1, UInt256 v2, UInt256 m, UInt256 expected) { assertValueEquals(expected, v1.addMod(v2, m)); } private static Stream addModProvider() { return Stream.of( Arguments.of(v(0), v(1), UInt256.valueOf(2), v(1)), Arguments.of(v(1), v(1), UInt256.valueOf(2), v(0)), Arguments.of(UInt256.MAX_VALUE.subtract(2), v(1), UInt256.MAX_VALUE, UInt256.MAX_VALUE.subtract(1)), Arguments.of(UInt256.MAX_VALUE.subtract(1), v(1), UInt256.MAX_VALUE, v(0)), Arguments.of(v(2), v(1), UInt256.valueOf(2), v(1)), Arguments.of(v(3), v(2), UInt256.valueOf(6), v(5)), Arguments.of(v(3), v(4), UInt256.valueOf(2), v(1))); } @Test void shouldThrowForAddModOfZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(v(1), UInt256.ZERO)); assertEquals("addMod with zero modulus", exception.getMessage()); } @ParameterizedTest @MethodSource("addModUInt256UInt256Provider") void addModUInt256UInt256(UInt256 v1, UInt256 v2, UInt256 m, UInt256 expected) { assertValueEquals(expected, v1.addMod(v2, m)); } private static Stream addModUInt256UInt256Provider() { return Stream.of( Arguments.of(v(0), UInt256.ONE, UInt256.valueOf(2), v(1)), Arguments.of(v(1), UInt256.ONE, UInt256.valueOf(2), v(0)), Arguments.of(UInt256.MAX_VALUE.subtract(2), UInt256.ONE, UInt256.MAX_VALUE, UInt256.MAX_VALUE.subtract(1)), Arguments.of(UInt256.MAX_VALUE.subtract(1), UInt256.ONE, UInt256.MAX_VALUE, v(0)), Arguments.of(v(2), UInt256.ONE, UInt256.valueOf(2), v(1)), Arguments.of(v(3), UInt256.valueOf(2), UInt256.valueOf(6), v(5)), Arguments.of(v(3), UInt256.valueOf(4), UInt256.valueOf(2), v(1))); } @Test void shouldThrowForAddModLongUInt256OfZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, UInt256.ZERO)); assertEquals("addMod with zero modulus", exception.getMessage()); } @ParameterizedTest @MethodSource("addModLongUInt256Provider") void addModLongUInt256(UInt256 v1, long v2, UInt256 m, UInt256 expected) { assertValueEquals(expected, v1.addMod(v2, m)); } private static Stream addModLongUInt256Provider() { return Stream.of( Arguments.of(v(0), 1L, UInt256.valueOf(2), v(1)), Arguments.of(v(1), 1L, UInt256.valueOf(2), v(0)), Arguments.of(UInt256.MAX_VALUE.subtract(2), 1L, UInt256.MAX_VALUE, UInt256.MAX_VALUE.subtract(1)), Arguments.of(UInt256.MAX_VALUE.subtract(1), 1L, UInt256.MAX_VALUE, v(0)), Arguments.of(v(2), 1L, UInt256.valueOf(2), v(1)), Arguments.of(v(2), -1L, UInt256.valueOf(2), v(1)), Arguments.of(v(1), -7L, UInt256.valueOf(5), v(4))); } @Test void shouldThrowForAddModUInt256UInt256OfZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(UInt256.ONE, UInt256.ZERO)); assertEquals("addMod with zero modulus", exception.getMessage()); } @ParameterizedTest @MethodSource("addModLongLongProvider") void addModLongLong(UInt256 v1, long v2, long m, UInt256 expected) { assertValueEquals(expected, v1.addMod(v2, m)); } private static Stream addModLongLongProvider() { return Stream .of(Arguments.of(v(0), 1L, 2L, v(1)), Arguments.of(v(1), 1L, 2L, v(0)), Arguments.of(v(2), 1L, 2L, v(1))); } @Test void shouldThrowForAddModLongLongOfZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, 0)); assertEquals("addMod with zero modulus", exception.getMessage()); } @Test void shouldThrowForAddModLongLongOfNegative() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).addMod(1, -5)); assertEquals("addMod unsigned with negative modulus", exception.getMessage()); } @ParameterizedTest @MethodSource("subtractProvider") void subtract(UInt256 v1, UInt256 v2, UInt256 expected) { assertValueEquals(expected, v1.subtract(v2)); } private static Stream subtractProvider() { return Stream.of( Arguments.of(v(1), v(0), v(1)), Arguments.of(v(5), v(0), v(5)), Arguments.of(v(2), v(1), v(1)), Arguments.of(v(100), v(100), v(0)), Arguments.of(biv("13492324908428420834234908342"), v(10), biv("13492324908428420834234908332")), Arguments.of(biv("13492324908428420834234908342"), v(23422141424214L), biv("13492324908428397412093484128")), Arguments.of(v(0), v(1), UInt256.MAX_VALUE), Arguments.of(v(1), v(2), UInt256.MAX_VALUE), Arguments .of(UInt256.MAX_VALUE, v(1), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); } @ParameterizedTest @MethodSource("subtractLongProvider") void subtractLong(UInt256 v1, long v2, UInt256 expected) { assertValueEquals(expected, v1.subtract(v2)); } private static Stream subtractLongProvider() { return Stream.of( Arguments.of(v(1), 0L, v(1)), Arguments.of(v(5), 0L, v(5)), Arguments.of(v(2), 1L, v(1)), Arguments.of(v(100), 100L, v(0)), Arguments.of(biv("13492324908428420834234908342"), 10L, biv("13492324908428420834234908332")), Arguments.of(biv("13492324908428420834234908342"), 23422141424214L, biv("13492324908428397412093484128")), Arguments.of(v(0), 1L, UInt256.MAX_VALUE), Arguments.of(v(1), 2L, UInt256.MAX_VALUE), Arguments.of(UInt256.MAX_VALUE, 1L, hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), Arguments.of(v(0), -1L, v(1)), Arguments.of(v(0), -100L, v(100)), Arguments.of(v(2), -2L, v(4))); } @ParameterizedTest @MethodSource("multiplyProvider") void multiply(UInt256 v1, UInt256 v2, UInt256 expected) { assertValueEquals(expected, v1.multiply(v2)); } private static Stream multiplyProvider() { return Stream.of( Arguments.of(v(0), v(2), v(0)), Arguments.of(v(1), v(2), v(2)), Arguments.of(v(2), v(2), v(4)), Arguments.of(v(3), v(2), v(6)), Arguments.of(v(4), v(2), v(8)), Arguments.of(v(10), v(18), v(180)), Arguments.of(biv("13492324908428420834234908341"), v(2), biv("26984649816856841668469816682")), Arguments.of(biv("13492324908428420834234908342"), v(2), biv("26984649816856841668469816684")), Arguments.of(v(2), v(8), v(16)), Arguments.of(v(7), v(8), v(56)), Arguments.of(v(8), v(8), v(64)), Arguments.of(v(17), v(8), v(136)), Arguments.of(biv("13492324908428420834234908342"), v(8), biv("107938599267427366673879266736")), Arguments.of(biv("13492324908428420834234908342"), v(2048), biv("27632281412461405868513092284416")), Arguments.of(biv("13492324908428420834234908342"), v(131072), biv("1768466010397529975584837906202624")), Arguments.of(v(22), v(0), v(0))); } @ParameterizedTest @MethodSource("multiplyLongProvider") void multiplyLong(UInt256 v1, long v2, UInt256 expected) { assertValueEquals(expected, v1.multiply(v2)); } private static Stream multiplyLongProvider() { return Stream.of( Arguments.of(v(0), 2L, v(0)), Arguments.of(v(1), 2L, v(2)), Arguments.of(v(2), 2L, v(4)), Arguments.of(v(3), 2L, v(6)), Arguments.of(v(4), 2L, v(8)), Arguments.of(v(10), 18L, v(180)), Arguments.of(biv("13492324908428420834234908341"), 2L, biv("26984649816856841668469816682")), Arguments.of(biv("13492324908428420834234908342"), 2L, biv("26984649816856841668469816684")), Arguments.of(v(2), 8L, v(16)), Arguments.of(v(7), 8L, v(56)), Arguments.of(v(8), 8L, v(64)), Arguments.of(v(17), 8L, v(136)), Arguments.of(biv("13492324908428420834234908342"), 8L, biv("107938599267427366673879266736")), Arguments.of(biv("13492324908428420834234908342"), 2048L, biv("27632281412461405868513092284416")), Arguments.of(biv("13492324908428420834234908342"), 131072L, biv("1768466010397529975584837906202624")), Arguments.of(v(22), 0L, v(0)), Arguments.of( hv("0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), 2L, hv("0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), 2L, hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"))); } @Test void shouldThrowForMultiplyLongOfNegative() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiply(-5)); assertEquals("multiply unsigned by negative", exception.getMessage()); } @ParameterizedTest @MethodSource("multiplyModProvider") void multiplyMod(UInt256 v1, UInt256 v2, UInt256 m, UInt256 expected) { assertValueEquals(expected, v1.multiplyMod(v2, m)); } private static Stream multiplyModProvider() { return Stream.of( Arguments.of(v(0), v(5), UInt256.valueOf(2), v(0)), Arguments.of(v(2), v(3), UInt256.valueOf(7), v(6)), Arguments.of(v(2), v(3), UInt256.valueOf(6), v(0)), Arguments.of(v(2), v(0), UInt256.valueOf(6), v(0)), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), v(2), UInt256.MAX_VALUE, hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); } @Test void shouldThrowForMultiplyModOfModZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(0).multiplyMod(v(1), UInt256.ZERO)); assertEquals("multiplyMod with zero modulus", exception.getMessage()); } @ParameterizedTest @MethodSource("multiplyModLongUInt256Provider") void multiplyModLongUInt256(UInt256 v1, long v2, UInt256 m, UInt256 expected) { assertValueEquals(expected, v1.multiplyMod(v2, m)); } private static Stream multiplyModLongUInt256Provider() { return Stream.of( Arguments.of(v(0), 5L, UInt256.valueOf(2), v(0)), Arguments.of(v(2), 3L, UInt256.valueOf(7), v(6)), Arguments.of(v(2), 3L, UInt256.valueOf(6), v(0)), Arguments.of(v(2), 0L, UInt256.valueOf(6), v(0)), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), 2L, UInt256.MAX_VALUE, hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD"))); } @Test void shouldThrowForMultiplyModLongUInt256OfModZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1L, UInt256.ZERO)); assertEquals("multiplyMod with zero modulus", exception.getMessage()); } @Test void shouldThrowForMultiplyModLongUInt256OfNegative() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, UInt256.valueOf(2))); assertEquals("multiplyMod unsigned by negative", exception.getMessage()); } @ParameterizedTest @MethodSource("multiplyModLongLongProvider") void multiplyModLongLong(UInt256 v1, long v2, long m, UInt256 expected) { assertValueEquals(expected, v1.multiplyMod(v2, m)); } private static Stream multiplyModLongLongProvider() { return Stream.of( Arguments.of(v(0), 5L, 2L, v(0)), Arguments.of(v(2), 3L, 7L, v(6)), Arguments.of(v(2), 3L, 6L, v(0)), Arguments.of(v(2), 0L, 6L, v(0)), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), 2L, Long.MAX_VALUE, hv("0x000000000000000000000000000000000000000000000000000000000000001C"))); } @Test void shouldThrowForMultiplyModLongLongOfModZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).multiplyMod(1, 0)); assertEquals("multiplyMod with zero modulus", exception.getMessage()); } @Test void shouldThrowForMultiplyModLongLongOfModNegative() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(2).multiplyMod(5, -7)); assertEquals("multiplyMod unsigned with negative modulus", exception.getMessage()); } @Test void shouldThrowForMultiplyModLongLongOfNegative() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(3).multiplyMod(-1, 2)); assertEquals("multiplyMod unsigned by negative", exception.getMessage()); } @ParameterizedTest @MethodSource("divideProvider") void divide(UInt256 v1, UInt256 v2, UInt256 expected) { assertValueEquals(expected, v1.divide(v2)); } private static Stream divideProvider() { return Stream.of( Arguments.of(v(0), v(2), v(0)), Arguments.of(v(1), v(2), v(0)), Arguments.of(v(2), v(2), v(1)), Arguments.of(v(3), v(2), v(1)), Arguments.of(v(4), v(2), v(2)), Arguments.of(biv("13492324908428420834234908341"), v(2), biv("6746162454214210417117454170")), Arguments.of(biv("13492324908428420834234908342"), v(2), biv("6746162454214210417117454171")), Arguments.of(biv("13492324908428420834234908343"), v(2), biv("6746162454214210417117454171")), Arguments.of(v(2), v(8), v(0)), Arguments.of(v(7), v(8), v(0)), Arguments.of(v(8), v(8), v(1)), Arguments.of(v(9), v(8), v(1)), Arguments.of(v(17), v(8), v(2)), Arguments.of(v(1024), v(8), v(128)), Arguments.of(v(1026), v(8), v(128)), Arguments.of(biv("13492324908428420834234908342"), v(8), biv("1686540613553552604279363542")), Arguments.of(biv("13492324908428420834234908342"), v(2048), biv("6588049271693564860466263")), Arguments.of(biv("13492324908428420834234908342"), v(131072), biv("102938269870211950944785"))); } @Test void shouldThrowForDivideByZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(v(0))); assertEquals("divide by zero", exception.getMessage()); } @ParameterizedTest @MethodSource("divideLongProvider") void divideLong(UInt256 v1, long v2, UInt256 expected) { assertValueEquals(expected, v1.divide(v2)); } private static Stream divideLongProvider() { return Stream.of( Arguments.of(v(0), 2L, v(0)), Arguments.of(v(1), 2L, v(0)), Arguments.of(v(2), 2L, v(1)), Arguments.of(v(3), 2L, v(1)), Arguments.of(v(4), 2L, v(2)), Arguments.of(biv("13492324908428420834234908341"), 2L, biv("6746162454214210417117454170")), Arguments.of(biv("13492324908428420834234908342"), 2L, biv("6746162454214210417117454171")), Arguments.of(biv("13492324908428420834234908343"), 2L, biv("6746162454214210417117454171")), Arguments.of(v(2), 8L, v(0)), Arguments.of(v(7), 8L, v(0)), Arguments.of(v(8), 8L, v(1)), Arguments.of(v(9), 8L, v(1)), Arguments.of(v(17), 8L, v(2)), Arguments.of(v(1024), 8L, v(128)), Arguments.of(v(1026), 8L, v(128)), Arguments.of(biv("13492324908428420834234908342"), 8L, biv("1686540613553552604279363542")), Arguments.of(biv("13492324908428420834234908342"), 2048L, biv("6588049271693564860466263")), Arguments.of(biv("13492324908428420834234908342"), 131072L, biv("102938269870211950944785"))); } @Test void shouldThrowForDivideLongByZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(0)); assertEquals("divide by zero", exception.getMessage()); } @Test void shouldThrowForDivideLongByNegative() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).divide(-5)); assertEquals("divide unsigned by negative", exception.getMessage()); } @ParameterizedTest @MethodSource("powUInt256Provider") void powUInt256(UInt256 v1, UInt256 v2, UInt256 expected) { assertValueEquals(expected, v1.pow(v2)); } private static Stream powUInt256Provider() { return Stream.of( Arguments.of(v(0), UInt256.valueOf(2), v(0)), Arguments.of(v(2), UInt256.valueOf(2), v(4)), Arguments.of(v(2), UInt256.valueOf(8), v(256)), Arguments.of(v(3), UInt256.valueOf(3), v(27)), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0F0"), UInt256.valueOf(3), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A920E119A2F000"))); } @ParameterizedTest @MethodSource("powLongProvider") void powLong(UInt256 v1, long v2, UInt256 expected) { assertValueEquals(expected, v1.pow(v2)); } private static Stream powLongProvider() { return Stream.of( Arguments.of(v(0), 2L, v(0)), Arguments.of(v(2), 2L, v(4)), Arguments.of(v(2), 8L, v(256)), Arguments.of(v(3), 3L, v(27)), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0F0"), 3L, hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A920E119A2F000")), Arguments.of(v(3), -3L, hv("0x2F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA12F684BDA13"))); } @ParameterizedTest @MethodSource("modLongProvider") void modLong(UInt256 v1, long v2, UInt256 expected) { assertValueEquals(expected, v1.mod(v2)); } private static Stream modLongProvider() { return Stream.of( Arguments.of(v(0), 2L, v(0)), Arguments.of(v(1), 2L, v(1)), Arguments.of(v(2), 2L, v(0)), Arguments.of(v(3), 2L, v(1)), Arguments.of(biv("13492324908428420834234908342"), 2L, v(0)), Arguments.of(biv("13492324908428420834234908343"), 2L, v(1)), Arguments.of(v(0), 8L, v(0)), Arguments.of(v(1), 8L, v(1)), Arguments.of(v(2), 8L, v(2)), Arguments.of(v(3), 8L, v(3)), Arguments.of(v(7), 8L, v(7)), Arguments.of(v(8), 8L, v(0)), Arguments.of(v(9), 8L, v(1)), Arguments.of(v(1024), 8L, v(0)), Arguments.of(v(1026), 8L, v(2)), Arguments.of(biv("13492324908428420834234908342"), 8L, v(6)), Arguments.of(biv("13492324908428420834234908343"), 8L, v(7)), Arguments.of(biv("13492324908428420834234908344"), 8L, v(0))); } @Test void shouldThrowForModLongByZero() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(0)); assertEquals("mod by zero", exception.getMessage()); } @Test void shouldThrowForModLongByNegative() { Throwable exception = assertThrows(ArithmeticException.class, () -> v(5).mod(-5)); assertEquals("mod by negative", exception.getMessage()); } @ParameterizedTest @MethodSource("andProvider") void and(UInt256 v1, UInt256 v2, UInt256 expected) { assertValueEquals(expected, v1.and(v2)); } private static Stream andProvider() { return Stream.of( Arguments.of( hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), hv("0x0000000000000000000000000000000000000000000000000000000000000000")), Arguments.of( hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), hv("0x000000000000000000000000000000FF00000000000000000000000000000000"))); } @ParameterizedTest @MethodSource("orProvider") void or(UInt256 v1, UInt256 v2, UInt256 expected) { assertValueEquals(expected, v1.or(v2)); } private static Stream orProvider() { return Stream.of( Arguments.of( hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), Arguments.of( hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), Arguments.of( hv("0x0000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))); } @ParameterizedTest @MethodSource("xorProvider") void xor(UInt256 v1, UInt256 v2, UInt256 expected) { assertValueEquals(expected, v1.xor(v2)); } private static Stream xorProvider() { return Stream.of( Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0x0000000000000000000000000000000000000000000000000000000000000000")), Arguments.of( hv("0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), Arguments.of( hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))); } @ParameterizedTest @MethodSource("notProvider") void not(UInt256 value, UInt256 expected) { assertValueEquals(expected, value.not()); } private static Stream notProvider() { return Stream.of( Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0x0000000000000000000000000000000000000000000000000000000000000000")), Arguments.of( hv("0x0000000000000000000000000000000000000000000000000000000000000000"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), Arguments.of( hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000"))); } @ParameterizedTest @MethodSource("shiftLeftProvider") void shiftLeft(UInt256 value, int distance, UInt256 expected) { assertValueEquals(expected, value.shiftLeft(distance)); } private static Stream shiftLeftProvider() { return Stream.of( Arguments.of(hv("0x01"), 1, hv("0x02")), Arguments.of(hv("0x01"), 2, hv("0x04")), Arguments.of(hv("0x01"), 8, hv("0x0100")), Arguments.of(hv("0x01"), 9, hv("0x0200")), Arguments.of(hv("0x01"), 16, hv("0x10000")), Arguments.of(hv("0x00FF00"), 4, hv("0x0FF000")), Arguments.of(hv("0x00FF00"), 8, hv("0xFF0000")), Arguments.of(hv("0x00FF00"), 1, hv("0x01FE00")), Arguments.of( hv("0x0000000000000000000000000000000000000000000000000000000000000001"), 16, hv("0x0000000000000000000000000000000000000000000000000000000000010000")), Arguments.of( hv("0x0000000000000000000000000000000000000000000000000000000000000001"), 15, hv("0x0000000000000000000000000000000000000000000000000000000000008000")), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), 55, hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000000000000")), Arguments.of( hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), 202, hv("0xFFFFFFFFFFFFFC00000000000000000000000000000000000000000000000000"))); } @ParameterizedTest @MethodSource("shiftRightProvider") void shiftRight(UInt256 value, int distance, UInt256 expected) { assertValueEquals(expected, value.shiftRight(distance)); } private static Stream shiftRightProvider() { return Stream.of( Arguments.of(hv("0x01"), 1, hv("0x00")), Arguments.of(hv("0x10"), 1, hv("0x08")), Arguments.of(hv("0x10"), 2, hv("0x04")), Arguments.of(hv("0x10"), 8, hv("0x00")), Arguments.of(hv("0x1000"), 4, hv("0x0100")), Arguments.of(hv("0x1000"), 5, hv("0x0080")), Arguments.of(hv("0x1000"), 8, hv("0x0010")), Arguments.of(hv("0x1000"), 9, hv("0x0008")), Arguments.of(hv("0x1000"), 16, hv("0x0000")), Arguments.of(hv("0x00FF00"), 4, hv("0x000FF0")), Arguments.of(hv("0x00FF00"), 8, hv("0x0000FF")), Arguments.of(hv("0x00FF00"), 1, hv("0x007F80")), Arguments.of( hv("0x1000000000000000000000000000000000000000000000000000000000000000"), 16, hv("0x0000100000000000000000000000000000000000000000000000000000000000")), Arguments.of( hv("0x1000000000000000000000000000000000000000000000000000000000000000"), 15, hv("0x0000200000000000000000000000000000000000000000000000000000000000")), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), 55, hv("0x00000000000001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000000000"), 202, hv("0x000000000000000000000000000000000000000000000000003FFFFFFFFFFFFF"))); } @ParameterizedTest @MethodSource("intValueProvider") void intValue(UInt256 value, int expected) { assertEquals(expected, value.intValue()); } private static Stream intValueProvider() { return Stream.of( Arguments.of(hv("0x"), 0), Arguments.of(hv("0x00"), 0), Arguments.of(hv("0x00000000"), 0), Arguments.of(hv("0x01"), 1), Arguments.of(hv("0x0001"), 1), Arguments.of(hv("0x000001"), 1), Arguments.of(hv("0x00000001"), 1), Arguments.of(hv("0x0100"), 256), Arguments.of(hv("0x000100"), 256), Arguments.of(hv("0x00000100"), 256)); } @Test void shouldThrowForIntValueOfOversizeValue() { Throwable exception = assertThrows(ArithmeticException.class, () -> hv("0x0100000000").intValue()); assertEquals("Value does not fit a 4 byte int", exception.getMessage()); } @ParameterizedTest @MethodSource("longValueProvider") void longValue(UInt256 value, long expected) { assertEquals(expected, value.toLong()); } private static Stream longValueProvider() { return Stream.of( Arguments.of(hv("0x"), 0L), Arguments.of(hv("0x00"), 0L), Arguments.of(hv("0x00000000"), 0L), Arguments.of(hv("0x01"), 1L), Arguments.of(hv("0x0001"), 1L), Arguments.of(hv("0x000001"), 1L), Arguments.of(hv("0x00000001"), 1L), Arguments.of(hv("0x0000000001"), 1L), Arguments.of(hv("0x000000000001"), 1L), Arguments.of(hv("0x0100"), 256L), Arguments.of(hv("0x000100"), 256L), Arguments.of(hv("0x00000100"), 256L), Arguments.of(hv("0x00000100"), 256L), Arguments.of(hv("0x000000000100"), 256L), Arguments.of(hv("0x00000000000100"), 256L), Arguments.of(hv("0x0000000000000100"), 256L), Arguments.of(hv("0xFFFFFFFF"), (1L << 32) - 1)); } @Test void shouldThrowForLongValueOfOversizeValue() { Throwable exception = assertThrows(ArithmeticException.class, () -> hv("0x010000000000000000").toLong()); assertEquals("Value does not fit a 8 byte long", exception.getMessage()); } @ParameterizedTest @MethodSource("compareToProvider") void compareTo(UInt256 v1, UInt256 v2, int expected) { assertEquals(expected, v1.compareTo(v2)); } private static Stream compareToProvider() { return Stream.of( Arguments.of(v(5), v(5), 0), Arguments.of(v(5), v(3), 1), Arguments.of(v(5), v(6), -1), Arguments.of( hv("0x0000000000000000000000000000000000000000000000000000000000000000"), hv("0x0000000000000000000000000000000000000000000000000000000000000000"), 0), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), 0), Arguments.of( hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), 0), Arguments.of( hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0x0000000000000000000000000000000000000000000000000000000000000000"), 1), Arguments.of( hv("0x0000000000000000000000000000000000000000000000000000000000000000"), hv("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), -1), Arguments.of( hv("0x000000000000000000000000000001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), 1), Arguments.of( hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"), hv("0x000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), -1)); } @ParameterizedTest @MethodSource("toBytesProvider") void toBytesTest(UInt256 value, Bytes expected) { assertEquals(expected, value.toBytes()); } private static Stream toBytesProvider() { return Stream.of( Arguments .of(hv("0x00"), Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000000")), Arguments.of( hv("0x01000000"), Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000001000000")), Arguments.of( hv("0x0100000000"), Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000100000000")), Arguments.of( hv("0xf100000000ab"), Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000f100000000ab")), Arguments.of( hv("0x0400000000000000000000000000000000000000000000000000f100000000ab"), Bytes.fromHexString("0x0400000000000000000000000000000000000000000000000000f100000000ab"))); } @ParameterizedTest @MethodSource("toMinimalBytesProvider") void toMinimalBytesTest(UInt256 value, Bytes expected) { assertEquals(expected, value.toMinimalBytes()); } private static Stream toMinimalBytesProvider() { return Stream.of( Arguments.of(hv("0x00"), Bytes.EMPTY), Arguments.of(hv("0x01000000"), Bytes.fromHexString("0x01000000")), Arguments.of(hv("0x0100000000"), Bytes.fromHexString("0x0100000000")), Arguments.of(hv("0xf100000000ab"), Bytes.fromHexString("0xf100000000ab")), Arguments.of( hv("0x0400000000000000000000000000000000000000000000000000f100000000ab"), Bytes.fromHexString("0x0400000000000000000000000000000000000000000000000000f100000000ab"))); } @ParameterizedTest @MethodSource("numberOfLeadingZerosProvider") void numberOfLeadingZeros(UInt256 value, int expected) { assertEquals(expected, value.numberOfLeadingZeros()); } private static Stream numberOfLeadingZerosProvider() { return Stream.of( Arguments.of(hv("0x00"), 256), Arguments.of(hv("0x01"), 255), Arguments.of(hv("0x02"), 254), Arguments.of(hv("0x03"), 254), Arguments.of(hv("0x0F"), 252), Arguments.of(hv("0x8F"), 248), Arguments.of(hv("0x100000000"), 223)); } @ParameterizedTest @MethodSource("bitLengthProvider") void bitLength(UInt256 value, int expected) { assertEquals(expected, value.bitLength()); } private static Stream bitLengthProvider() { return Stream.of( Arguments.of(hv("0x00"), 0), Arguments.of(hv("0x01"), 1), Arguments.of(hv("0x02"), 2), Arguments.of(hv("0x03"), 2), Arguments.of(hv("0x0F"), 4), Arguments.of(hv("0x8F"), 8), Arguments.of(hv("0x100000000"), 33)); } private void assertValueEquals(UInt256 expected, UInt256 actual) { String msg = String.format("Expected %s but got %s", expected.toHexString(), actual.toHexString()); assertEquals(expected, actual, msg); } } cava-0.6.0/units/src/test/java/net/consensys/cava/units/ethereum/000077500000000000000000000000001341750772100247735ustar00rootroot00000000000000cava-0.6.0/units/src/test/java/net/consensys/cava/units/ethereum/GasTest.java000066400000000000000000000045271341750772100272200ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.units.ethereum; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; class GasTest { @Test void testOverflowThroughAddition() { Gas max = Gas.valueOf(Long.MAX_VALUE); assertThrows(ArithmeticException.class, () -> { max.add(Gas.valueOf(1L)); }); } @Test void testOverflow() { assertThrows(IllegalArgumentException.class, () -> { Gas.valueOf(BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE)); }); } @Test void testGetWeiPrice() { Gas gas = Gas.valueOf(5L); Wei result = gas.priceFor(Wei.valueOf(3L)); assertEquals(15, result.intValue()); } @Test void testReuseConstants() { List oneTime = new ArrayList<>(); for (int i = 0; i < 128; i++) { oneTime.add(Gas.valueOf((long) i)); } List secondTime = new ArrayList<>(); for (int i = 0; i < 128; i++) { secondTime.add(Gas.valueOf((long) i)); } for (int i = 0; i < 128; i++) { Gas first = oneTime.get(i); Gas second = secondTime.get(i); if (i <= 64) { assertSame(first, second); } else { assertNotSame(first, second); assertEquals(first, second); } } } @Test void testNegativeLong() { assertThrows(IllegalArgumentException.class, () -> { Gas.valueOf(-1L); }); } @Test void testNegativeBigInteger() { assertThrows(IllegalArgumentException.class, () -> { Gas.valueOf(BigInteger.valueOf(-123L)); }); } } cava-0.6.0/units/src/test/java/net/consensys/cava/units/ethereum/WeiTest.java000066400000000000000000000035121341750772100272230ustar00rootroot00000000000000/* * Copyright 2018 ConsenSys AG. * * 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.units.ethereum; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; class WeiTest { @Test void testReuseConstants() { List oneTime = new ArrayList<>(); for (int i = 0; i < 128; i++) { oneTime.add(Wei.valueOf((long) i)); } List secondTime = new ArrayList<>(); for (int i = 0; i < 128; i++) { secondTime.add(Wei.valueOf((long) i)); } for (int i = 0; i < 128; i++) { Wei first = oneTime.get(i); Wei second = secondTime.get(i); if (i <= 64) { assertSame(first, second); } else { assertNotSame(first, second); assertEquals(first, second); } } } @Test void testNegativeLong() { assertThrows(IllegalArgumentException.class, () -> { Wei.valueOf(-1L); }); } @Test void testNegativeBigInteger() { assertThrows(IllegalArgumentException.class, () -> { Wei.valueOf(BigInteger.valueOf(-123L)); }); } }