diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..90e2fb4 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +# dependabot analyzing maven dependencies +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + open-pull-requests-limit: 3 + schedule: + interval: "weekly" + labels: + - "dependencies" diff --git a/.github/workflows/build_deploy.yml b/.github/workflows/build_deploy.yml new file mode 100644 index 0000000..cde13a1 --- /dev/null +++ b/.github/workflows/build_deploy.yml @@ -0,0 +1,96 @@ +name: Build and Deploy with Maven + +on: + push: + branches: + - main + tags: + - '*' # Trigger on all tags + pull_request: { } + +env: + SONARQUBE_PROJECT: patrickfav_bytes-java + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Cache SonarCloud packages + uses: actions/cache@v3 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Maven + id: cache-primes + uses: actions/cache@v3 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + - name: Build with Maven + run: ./mvnw -B clean verify -DcommonConfig.jarSign.skip=true + - name: Analyze with SonaQube + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=$SONARQUBE_PROJECT + + deploy: + needs: build + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Retrieve Keystore from secrets + env: + KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} + run: | + echo $KEYSTORE_BASE64 | base64 --decode > keystore.jks + - name: Cache Maven + id: cache-primes + uses: actions/cache@v3 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Set up Maven Central Repository + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml + server-username: MAVEN_USERNAME # env variable for username in deploy + server-password: MAVEN_PASSWORD # env variable for token in deploy + gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import + gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase + - name: Publish package + run: | + ./mvnw -B verify nexus-staging:deploy -P deploy -DskipTests && \ + ./mvnw -B nexus-staging:release -P deploy + env: + OPENSOURCE_PROJECTS_KS_PW: ${{ secrets.KEYSTORE_PASSWORD }} + OPENSOURCE_PROJECTS_KEY_PW: ${{ secrets.KEYSTORE_KEY_PASSWORD }} + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + - name: Create and upload Github Release + uses: xresloader/upload-to-github-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + file: "target/*.jar;target/*.sha256;target/checksum-sha256.txt" + tags: true + draft: false diff --git a/.gitignore b/.gitignore index 3ff6d9e..9536f63 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,5 @@ Thumbs.db signing.properties keystore.jks ci-settings.xml -secrets.tar \ No newline at end of file +secrets.tar +pom.xml.versionsBackup diff --git a/.mvn/maven.config b/.mvn/maven.config new file mode 100644 index 0000000..f1099e4 --- /dev/null +++ b/.mvn/maven.config @@ -0,0 +1 @@ +-DcommonConfig.compiler.profile=jdk7 diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100644 index 4978101..0000000 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,108 +0,0 @@ -/* -Licensed to the Apache Software Foundation (ASF) under one -or more contributor license agreements. See the NOTICE file -distributed with this work for additional information -regarding copyright ownership. The ASF licenses this file -to you under the Apache License, Version 2.0 (the -"License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, -software distributed under the License is distributed on an -"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, either express or implied. See the License for the -specific language governing permissions and limitations -under the License. -*/ - -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = - "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); - - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if(mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if(mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: : " + url); - - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if(!outputFile.getParentFile().exists()) { - if(!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } - -} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar index 41c70a7..bf82ff0 100644 Binary files a/.mvn/wrapper/maven-wrapper.jar and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index c4914b5..ca5ab4b 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,2 +1,18 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 990edbc..0000000 --- a/.travis.yml +++ /dev/null @@ -1,51 +0,0 @@ -# To let the CI execute the maven wrapper, use this command and push the change: -# git update-index --chmod=+x mvnw - -language: java -install: true -jdk: - - oraclejdk8 - -env: - global: - - secure: "eV7hLuGYyG1daQIdZ4p2EtSu0vwZSC9aVpEz1/6JWypdAJKu6uLxibHtLXHqSB8PWXDJbpNIhs7gaD8NGBz506U0V+XG1eMYhJ4qonw4Xayk5HATrGW2aWxT1t8W/ZVpb+Z9r3D6N7VvxvbzzV1yUJYc0RJCHT2MklCdjv2CVdZMErDdg3tCQihP7AWm3Z7Ab4+33STcOzNkZNyiOKu9UQBSvu2EoBBrzEbjJo+tZyn4iEC4DVLxA8tLeHi7XTD6DhzgTdyUTM1YAvwNxNrkMewG4tPV8sH+sdj80Qqt4h2Wg+LfoYUwT59HFPcoMdmPauQbaSkqMMjVpwvtWXoJgWCNYRg3Us/7vtml4YTqdj2ao38QvL/QATyOXy/0kilhp32pfyY5hbknxseLr/dShhsrofbThUfxyK8As6FfeIwOywU/TNRl2D7WfQwtAL9nv5RRkNj+EVZjE7J4pl2gL4wn6O18m04stGgkoXSjKa2Zeiy4dd5Nb7FyYSL5CWhMevPVOwUNKZkz3Ys3v/DEmelerCIfFofFlULtyFc4MVWDZpHA2EJd78DC/P3MuiL8aElICRenIpvgHShxFnrGbvomWW60EKuexkM5Hrcn8oyNx7zDPwrqw0hgM8xEZCJymVu01G/JlthjynzFvR7WBxRGwAPxTxBh5adzWZdr6gw=" - - secure: "BqaCKfW/yyBh0jzrMloCxvDECKtg8qa/AxJNlw1IInQPobN7MLH/hGaGtuArG0zfY8pu0w5nGYGkmo/RgW4kgdd/YQ1UIZNS8gBoZh5S7dtSg9EXV+oXlhOfIzictJtL1sJjCGVhzlIRWj5HTysdrAY4Lg/QrjnyX7R5vSdNPnCdDyg7dIsg6r1ROSt8bjQCWxJpNLA3BrG1hvBqP2QLNmlCz7OlFmNPok9PfMumSa8VT8WpqwghrBOCEFf32wRHAkpXjQgQ5biXL2iPgTbEvfoU1ePn9N/OaUYl5mVKcDc1YnTQeMHg7tvciSZ1DZfuiCrTsnnUq8DXVghsjt3zDASlFyTvgf1PSCTjOezNEP/LZw9BXeMo5+bETZLMCcSaSZTUjnNgMRodKPXT3T8v22+5vjvC1NjZCeYdNCt0bxu+/ETdPxh4Lo7L5SNJ/rAurAKXXWt2ARmBrm5nGIiZNyOGjec59Y4ekfDqut87YNmbeqZgxxIoz40EwoRsfT0On7xss8SQi/uKhBwt9i/RW1ihyFAQLymKuSE3ChUkCVwzy/SmTduRUDhoSy982qFMnTPmjMDizfXXP2DjtPqZOExd2/AZsPmAz0N5FjCWciTHWKmhLr358SzgJIpXL+cYkJIR8pk038NKLvzlV0LQb1nrA20iiggjFzyRbwC60oE=" - -before_install: - - openssl aes-256-cbc -K $encrypted_b9f5be6a7c0d_key -iv $encrypted_b9f5be6a7c0d_iv -in secrets.tar.enc -out secrets.tar -d - - tar xvf secrets.tar - -script: - - ./mvnw clean install -X -Djarsigner.skip=false checkstyle:check - -after_success: - - ./mvnw test jacoco:report coveralls:report - -deploy: - - provider: script - script: ./mvnw -s ci-settings.xml deploy -X - skip_cleanup: true - on: - branch: master - tags: true - - provider: releases - api_key: - secure: "ozRga/xe4pNZNH59tmPK1lLpCFa4WFW674JMTBWLbf+7zjPiN8K5dtAYUkhDqngw0ifasukVCRey4F9fu479KlFciGrLBd4r/hq/rh7inVHqd1qcn4AwyDX3EkbloD5qwfas7yn5zJ7cWm7KyEkfpr+fgReaQzSRvJUpjpDBrS2xNIYOhL1tInu84/RtyB6QuJjQOkH9rulP9M8gjcEH9Iu0k40jAVYLJDL9opOWrc9qpAzL0wN4xXGbnI30WrA8nmLcrHRxODOxbYEQ5EndLFJyS3nYxL9D3QJeEYkXf9uqz46V4dgfrEirWrtin3PlSUKTD9/0Mqz4uxqrGky0vrpha7m231+ilTUJzvE0a29LdS3R4A6ssDb0oFtj4pP3O6uNDqZN20r83ughGi0GrlKL/CGpQ4fS5nOpqppx0Xg+SR78/uOqQDIOn570FILBTKiaTsCn6xKqYC4QFSlsZiXojgU4FDZpKygH932qUDE0BnzsZEtzCube2oXMktZs6keSmELMyRYVs/K9yC7+8R1Pez1Xz72zxNG1ke6588sO3wxN+Q+0ZhqmQC8Y2OY8i72b06M5SyGz5TV38SrntLU88HOMWVJtfRDcMcPnJdFgaujZCoOw5SfokzXncipldfJGpKGIb3fHBMJw0MzspT7Kp6XRzSbtJI5+NVestR4=" - file_glob: true - file: - - "target/*.jar" - - "target/*.sha256" - - "target/checksum-sha256.txt" - skip_cleanup: true - on: - branch: master - tags: true - -cache: - directories: - - $HOME/.m2 - -notifications: - slack: - secure: "PiEdsZoQlBR7ce6IT9LlxWod2XqWjm8HCxs4yP8o9iI5ix7FxEV8NDGQKY1vcDG6D8bz4gcNsBxJrPKASD0G3rLaJpjA4L9MUNhyx+4VCIBBnT1fwV+as+PJmr1tZupqUnlxZJyTdiaqkV8jtjCkHUn3P6TURbNuZws74/ki2rICbi4EebD3mdeQvFtWYKTOsAgZjLecMvPU9q+b7TAwDlaZFbkG7DMFruT8i7tA/EWMOvI48kVaUEM0QPsAxNdr32YFta0s/xJyNuOp1Hp3ZZbnZgXgKc2WvGRcg+9sucnQ+eQsQKj1tbWS2YwxHcy7B3Q7YDmBL2NIJIHbuaWNlbK9fVh3gdk7Knlr8IHpLmVuHvH3HAF6JDcrARjk8ViVP2HafkkDNbQZLupz5LkJ02hj1DMGVOAyuVTt6IpOMt3WTaQ66qtyGupQXhrqvq8tDwvg6FIy1sjPZbWE/Sq1s20PYBbHkVwNvQ3bNaC44nAkcnTv+dAoazvy8G40RIPovTO1ubzriZvuY8Db4THF2ZL1mDbyHEJstA7L9qkObqZOwnFsy5pLp4NfR9C/bTAY8EUcZVGm0spIADafip2H57Hf+n3co4aqw689t+Y453AUh3W+k4ZsC/V1kzX3MVdcluRP7ET8ejtfwduMyh2/jFmUK3g246/teEssyohp6ik=" - email: false \ No newline at end of file diff --git a/CHANGELOG b/CHANGELOG index 82232f2..e30ed69 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,111 @@ # Releases +## 1.6.2 + +* remove hashCode caching since it could introduce very subtle bugs + +## v1.6.1 + +* now build by JDK 11 and removed errorprone compiler #52 +* introduce sonarqube and remove codecov +* improve javadoc by fixing many typos #53 +* some small bugfixes + +## v1.6.0 + +* migrate to github actions, codecov and maven central #49 +* add `indexOf` (thx @hlyakhovich) #48 +* add `toShortArray` (thx @hlyakhovich) #44 +* add `from()` constructor from `short` vararg or array (thx @hlyakhovich) #45 +* add an automatic module name to support the JPMS (thx @airsquared) #47 +* fix warning of junit 4.13 CVE-2020-15250 + +## v1.5.0 + +* fix `leftShift()` and `rightShift()` to respect byte order (thx @gfpeltier) +* fix `bitAt()` to respect byte order (thx @gfpeltier) + +## v1.4.0 + +* add `from()` constructor from `float[]` +* add `from()` constructor from `double[]` +* fix throwing `IllegalArgumentException` instead of `IllegalStateException` in `.toUUID()` + +## v1.3.0 +* improve hex encoder performance by factor 5 + +## v1.2.0 + +* let hex decoder accept odd length string #37 + +## v1.1.0 + +* add `unsecureRandom()` constructor which creates random for e.g. tests or deterministic randoms +* adds overwrite method to Bytes (thx @JadePaukkunen) +* make project OSGi compatible #36 +* add `toFloatArray()` converter #30 +* add `toDoubleArray()` converter #30 + +## v1.0.0 + +* add `append()` method supporting multiple byte arrays #26 +* add `toCharArray()` method which decodes internal byte array to char[] #27 +* add `encodeBase64()` supporting padding-less encoding +* add `toIntArray()` converter #28 +* add `toLongArray()` converter #29 +* add `AutoCloseable` to MutableBytes interface #31 +* add `allocate()` as mutable byte static constructor (thx @petrukhnov) + +### Breaking + +* removed deprecated `toObjectArray()`; use `toBoxedArray()` instead + +## v0.8.0 + +* add radix encoding/parsing and fix radix tests #6, #20 +* add support for Base32 RFC4648 non-hex alphabet encoding/parsing #21 +* add constructor for `IntBuffer` and `CharBuffer` +* `parse()` methods now expect more flexible `CharSequence` instead of `String` #23 +* `from()` constructor reading from `char[]` has new version that accepts offset and length #24 +* add `from()` constructor reading file with offset and length parameter #25 + +### Breaking + +* interface `BinaryToTextEncoding.decode()` changed param to `CharSequence` from `String` #23 + +### Deprecations (will be removed in v1.0+) + +* `parseBase36()`/`encodeBase36()` - use `parseRadix(36)`/`encodeRadix(36)` instead + +## v0.7.1 + +* sign AFTER ProGuard so optimized version has correct jar signature + +## v0.7.0 + + * add `count` method for counting byte arrays (like pattern matching) + * add dedicated `md5` and `sha1` transformer methods + * add `from(Inputstream stream, int maxlength)` limiting stream reading constructor #13 + * add indexOf() with `fromIndex` parameter #14 + * add support for base64 url safe encoding #15 + * use EMPTY constant instance for empty byte array to safe memory #16 + * add `startsWith()` and `endsWidth()` methods #12 + * add cache for calculating the hashCode + * add HMAC byte transformer #11 + * add unsigned sort transformer #17 + * add `.immutable()` converter in MutableBytes #18 + +## v0.6.0 + + * add `encodeCharsetToBytes()` feature #7 + * add new `from(char[] charArray, Charset charset)` constructor with improved logic #8 + * add constructor/converter from/to UUID #9 + * add `empty()` constructor, creating empty byte array + +### Deprecations (will be removed in v1.0+) + +* `toObjectArray()` renamed to `toBoxedArray()` + ## v0.5.0 * better resource handling for compression @@ -8,6 +114,7 @@ * add appendNullSafe and append string with encoding * add proguard optimized version (can be used with classifier 'optimized') * add constant time equals + * fix or() operator using and() internally #2 ## v0.4.6 @@ -66,4 +173,4 @@ ## v0.2.0 -initial version \ No newline at end of file +initial version diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c028985..7404d32 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,14 +2,14 @@ We ❤ pull requests from everyone. -If possible proof features and bugfixes with unit tests. +If possible to proof features and bug fixes with unit tests. This repo validates against checkstyle (import the xml found in the root to your IDE if possible) To run the tests (and checkstyle): ```shell -mvn test checkstyle:check +mvn verify ``` Tests are automatically run against branches and pull requests -via TravisCI, so you can also depend on that. \ No newline at end of file +via TravisCI, so you can also depend on that. diff --git a/README.md b/README.md index 4652a0a..eea6e4b 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ Bytes is a utility library that makes it easy to **create**, **parse**, **transform**, **validate** and **convert** byte arrays in Java. It's main class `Bytes` is a collections of bytes and the main API. It supports [endianness](https://en.wikipedia.org/wiki/Endianness) -as well as **immutable** and **mutable** access, so the caller may decide to favor +as well as **copy-on-write** and **mutable** access, so the caller may decide to favor performance. This can be seen as combination of the features provided by [`BigInteger`](https://docs.oracle.com/javase/7/docs/api/java/math/BigInteger.html), [`ByteBuffer`](https://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html) but -providing a lot of additional features. The main goal is to minimize the need +providing a lot of additional features on the micro and macro level of byte arrays (similar to Okio's [ByteString](https://github.com/square/okio)). The main goal is to minimize the need to blindly paste code snippets from [s](https://stackoverflow.com/questions/140131/convert-a-string-representation-of-a-hex-dump-to-a-byte-array-using-java) [t](https://stackoverflow.com/questions/12893758/how-to-reverse-the-byte-array-in-java) @@ -22,38 +22,45 @@ to blindly paste code snippets from [l](https://stackoverflow.com/questions/4231674/converting-an-array-of-bytes-to-listbyte) [o](https://stackoverflow.com/questions/28703273/sorting-byte-arrays-in-numeric-order) [w](https://stackoverflow.com/questions/4385623/bytes-of-a-string-in-java) -[.com](https://stackoverflow.com/questions/23360692/byte-position-in-java) +[.](https://stackoverflow.com/questions/23360692/byte-position-in-java) +[c](https://stackoverflow.com/questions/11437203/byte-array-to-int-array) +[o](https://stackoverflow.com/a/9670279/774398) +[m](https://stackoverflow.com/questions/1519736/random-shuffling-of-an-array) -[![Download](https://api.bintray.com/packages/patrickfav/maven/bytes-java/images/download.svg)](https://bintray.com/patrickfav/maven/bytes-java/_latestVersion) -[![Build Status](https://travis-ci.org/patrickfav/bytes-java.svg?branch=master)](https://travis-ci.org/patrickfav/bytes-java) +[![Maven Central](https://img.shields.io/maven-central/v/at.favre.lib/bytes)](https://mvnrepository.com/artifact/at.favre.lib/bytes) +[![Github Actions](https://github.com/patrickfav/bytes-java/actions/workflows/build_deploy.yml/badge.svg)](https://github.com/patrickfav/bytes-java/actions) [![Javadocs](https://www.javadoc.io/badge/at.favre.lib/bytes.svg)](https://www.javadoc.io/doc/at.favre.lib/bytes) -[![Coverage Status](https://coveralls.io/repos/github/patrickfav/bytes-java/badge.svg?branch=master)](https://coveralls.io/github/patrickfav/bytes-java?branch=master) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=patrickfav_bytes-java&metric=coverage)](https://sonarcloud.io/summary/new_code?id=patrickfav_bytes-java) +[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=patrickfav_bytes-java&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=patrickfav_bytes-java) +[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=patrickfav_bytes-java&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=patrickfav_bytes-java) -It's main features include: +Its main features include: -* **Creation** from a wide variety of sources: multiple arrays, integers, [streams](https://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html), random, strings, files, ... +* **Creation** from a wide variety of sources: multiple arrays, integers, [streams](https://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html), random, strings, files, uuid, ... * **Transformation** with many built-in: append, [xor](https://en.wikipedia.org/wiki/Exclusive_or), [and](https://en.wikipedia.org/wiki/Logical_conjunction), [hash](https://en.wikipedia.org/wiki/Cryptographic_hash_function), [shifts](https://en.wikipedia.org/wiki/Bitwise_operation#Bit_shifts), shuffle, reverse, [checksum](https://en.wikipedia.org/wiki/Checksum), ... * **Validators** with the ability to arbitrarily combine multiple ones with logical expressions -* **Parsing and Encoding** in most common binary-to-text-encodings: [hex](https://en.wikipedia.org/wiki/Hexadecimal), [base36](https://en.wikipedia.org/wiki/Base36), [base64](https://en.wikipedia.org/wiki/Base64), ... +* **Parsing and Encoding** in most common binary-to-text-encodings: [hex](https://en.wikipedia.org/wiki/Hexadecimal), [base32](https://en.wikipedia.org/wiki/Base32), [base64](https://en.wikipedia.org/wiki/Base64), ... * **Immutable, Mutable and Read-Only** versions * **Handling Strings** with encoding and normalizing strings for arbitrary charset * **Utility Features** like `indexOf`, `count`, `isEmpty`, `bitAt`, `contains` ... * **Flexibility** provide your own Transformers, Validators and Encoders -It is written in [Java 7](https://en.wikipedia.org/wiki/Java_version_history#Java_SE_7) to keep backwards compatibility with *Android* and older *Java* applications. +The code is compiled with target [Java 7](https://en.wikipedia.org/wiki/Java_version_history#Java_SE_7) to keep backwards compatibility with *Android* and older *Java* applications. It is lightweight as it does not require any additional dependencies. ## Quickstart -Add dependency to your `pom.xml`: +Add dependency to your `pom.xml` ([check latest release](https://github.com/patrickfav/bytes-java/releases)): - - at.favre.lib - bytes - {latest-version} - +```xml + + at.favre.lib + bytes + {latest-version} + +``` -_Note:_ There is a byte-code optimized version (powerd by [ProGuard](https://www.guardsquare.com/en/products/proguard)) which can be used with [classifier](https://maven.apache.org/pom.html#Maven_Coordinates) 'optimized'. This may have issues so use at your own risk. +_Note:_ There is a byte-code optimized version (powered by [ProGuard](https://www.guardsquare.com/en/products/proguard)) which can be used with [classifier](https://maven.apache.org/pom.html#Maven_Coordinates) 'optimized'. This may have issues so use at your own risk. Some simple examples: @@ -70,13 +77,13 @@ int result = b.toInt(); //get as signed int ```java Bytes b = Bytes.from(array1); //create from copy of array1 -b.resize(2).xor(arry2); //shrink to 2 bytes and xor with other array +b.resize(2).xor(array2); //shrink to 2 bytes and xor with other array byte[] result = b.array(); //get as byte array ``` ## API Description -Per default the instance is **immutable**, which means any transformation will +Per default the instance is **semi-immutable**, which means any transformation will create a copy of the internal array (it is, however, possible to get and modify the internal array). There is a **mutable** version which supports in-place modification for better performance and a **read-only** version which @@ -131,6 +138,8 @@ Creating byte arrays from **primitive integer** types and arrays: Bytes.from(8); //00000000 00000000 00000000 00001000 Bytes.from(1897621543227L); Bytes.from(1634, 88903, 77263); +Bytes.from(0.7336f, -87263.0f); +Bytes.from(0.8160183296, 3984639846.0); ``` Initializing **empty arrays** of arbitrary length: @@ -138,14 +147,21 @@ Initializing **empty arrays** of arbitrary length: ```java Bytes.allocate(16); Bytes.allocate(4, (byte) 1); //fill with 0x01 +Bytes.empty(); //creates zero length byte array ``` -Creating **random** byte arrays for e.g. testing: +Creating cryptographically secure **random** byte arrays: ```java Bytes.random(12); ``` +Creating cryptographically unsecure **random** byte arrays for e.g. testing: + +```java +Bytes.unsecureRandom(12, 12345L); // using seed makes it deterministic +``` + Reading byte content of encoded `String`s: ```java @@ -157,12 +173,14 @@ Bytes.from(asciiString, StandardCharset.US_ASCII) //any charset And other types: ```java -Bytes.from(byteInputStream); //any java.io.InputStream +Bytes.from(byteInputStream); //read whole java.io.InputStream +Bytes.from(byteInputStream, 16); //read java.io.InputStream with length limitation Bytes.from(byteList); //List byteList = ... Bytes.from(myBitSet); //java.util.BitSet myBitSet = ... Bytes.from(bigInteger); //java.math.BigInteger Bytes.from(file); //reads bytes from any java.io.File Bytes.from(dataInput, 16); //reads bytes from any java.io.DataInput +Bytes.from(UUID.randomUUID()); //read 16 bytes from UUID ``` For parsing binary-text-encoded strings, see below. @@ -198,7 +216,7 @@ Bytes result = Bytes.wrap(array1).append("some string"); Bytes.wrap(array).xor(array2); // 0010 0011 xor() 1011 1000 = 1001 1011 Bytes.wrap(array).or(array2); // 0010 0011 or() 1101 0100 = 1111 0111 Bytes.wrap(array).and(array2); // 0010 0011 and() 1011 1000 = 0010 0000 -Bytes.wrap(array).negate(); // 0010 0011 negate() = 1101 1100 +Bytes.wrap(array).not(); // 0010 0011 negate() = 1101 1100 Bytes.wrap(array).leftShift(8); Bytes.wrap(array).rightShift(8); Bytes.wrap(array).switchBit(3, true); @@ -221,7 +239,9 @@ Bytes resized = Bytes.wrap(array).resize(3); //from {3, 9, 2, 1} to {9, 2, 1} ```java Bytes hash = Bytes.wrap(array).hashSha256(); -Bytes hash = Bytes.wrap(array).hash("MD5"); +Bytes hash = Bytes.wrap(array).hashSha1(); +Bytes hash = Bytes.wrap(array).hashMd5(); +Bytes hash = Bytes.wrap(array).hash("SHA-512"); ``` **Reversing** of the byte order in the array @@ -239,6 +259,14 @@ can be statically imported for a less verbose syntax: import static at.favre.lib.bytes.BytesTransformers.*; ``` +**HMAC** used to calculate [keyed-hash message authentication code](https://en.wikipedia.org/wiki/HMAC): + +```java +Bytes.wrap(array).transform(hmacSha256(macKey32Byte)); +Bytes.wrap(array).transform(hmacSha1(macKey20Byte)); +Bytes.wrap(array).transform(hmac(macKey16Byte,"HmacMd5")); +``` + **Checksum** can be calculated or automatically appended: ```java @@ -257,7 +285,8 @@ Bytes decompressed = compressed.transform(decompressGzip()); **Sorting** of individual bytes with either [`Comparator`](https://docs.oracle.com/javase/7/docs/api/java/util/Comparator.html) or natural order: ```java -Bytes.wrap(array).transform(sort()); +Bytes.wrap(array).transform(sort()); // 0x00 sorts after 0xff +Bytes.wrap(array).transform(sortUnsigned()); // 0xff sorts after 0x00 Bytes.wrap(array).transform(sort(byteComparator)); ``` @@ -270,7 +299,7 @@ Bytes.wrap(array).transform(shuffle()); ### Parser and Encoder for Binary-Text-Encodings This library can parse and encode a variety of encodings: binary, decimal, [octal](https://en.wikipedia.org/wiki/Octal), -[hex](https://en.wikipedia.org/wiki/Hexadecimal), [base36](https://en.wikipedia.org/wiki/Base36) and +[hex](https://en.wikipedia.org/wiki/Hexadecimal) and [base64](https://en.wikipedia.org/wiki/Base64). Additionally custom parsers are supported by providing your own implementation: @@ -294,15 +323,23 @@ This lib has it's own build in **Base64** encoder: Bytes.parseBase64("SpT9/x6v7Q=="); Bytes.from(array).encodeBase64(); //"SpT9/x6v7Q==" +Bytes.from(array).encodeBase64Url(); //"SpT9_x6v7Q==" + ``` + +also a **Base32** encoder (using the RFC4648 non-hex alphabet): + +```java +Bytes.parseBase32("MZXQ===="); +Bytes.from(array).encodeBase32(); ``` -Additionally the following encodings are supported: +Additionally the following radix encodings are supported: ```java Bytes.from(array).encodeBinary(); //1110110110101111 Bytes.from(array).encodeDec(); //20992966904426477 Bytes.from(array).encodeOctal(); //1124517677707527755 -Bytes.from(array).encodeBase36(); //5qpdvuwjvu5 +Bytes.from(array).encodeRadix(36); //5qpdvuwjvu5 ``` ### Handling Strings @@ -315,7 +352,7 @@ Bytes.from(s); ``` or get the **[normalized version](https://en.wikipedia.org/wiki/Unicode_equivalence)**, -which is the recommended way to convert e.g. user passwords +which is the recommended way to convert e.g. user names ```java String pwd = "ℌH"; @@ -332,7 +369,7 @@ Bytes.from(asciiString, StandardCharsets.US_ASCII); To easily append a string to an byte array you can do ```java -String userPwd = ...; +String userPwdHash = ...; Bytes.from(salt).append(userPwd).hashSha256(); ``` @@ -345,7 +382,11 @@ Finding occurrence of specific bytes: ```java Bytes.wrap(array).contains((byte) 0xE1); Bytes.wrap(array).indexOf((byte) 0xFD); +Bytes.wrap(array).indexOf(new byte[] {(byte) 0xFD, 0x23}); +Bytes.wrap(array).indexOf((byte) 0xFD, 5); //search fromIndex 5 Bytes.wrap(array).lastIndexOf((byte) 0xAE); +Bytes.wrap(array).startsWith(new byte[] {(byte) 0xAE, 0x32}); +Bytes.wrap(array).endsWidth(new byte[] {(byte) 0xAE, 0x23}); ``` Length checks: @@ -370,6 +411,7 @@ And others: ```java Bytes.wrap(array).count(0x01); //occurrences of 0x01 +Bytes.wrap(array).count(new byte[] {0x01, 0xEF}); //occurrences of pattern [0x01, 0xEF] Bytes.wrap(array).entropy(); ``` @@ -457,7 +499,7 @@ Conversion to [`InputStream`](https://docs.oracle.com/javase/7/docs/api/java/io/ ```java Bytes.wrap(array).inputStream(); -Bytes.wrap(array).byteBuffer(); +Bytes.wrap(array).buffer(); ``` If you just want a duplicated instance, sharing the same array: @@ -479,20 +521,34 @@ Bytes.wrap(array).toInt(); Bytes.wrap(array).toDouble(); ``` +To primitive arrays + +```java +Bytes.wrap(array).toIntArray(); // of type int[] +Bytes.wrap(array).toLongArray(); // of type long[] +``` + To other collections ```java Bytes.wrap(array).toList(); // of type List -Bytes.wrap(array).toObjectArray(); // of type Byte[] +Bytes.wrap(array).toBoxedArray(); // of type Byte[] Bytes.wrap(array).toBitSet(); //of type java.util.BitSet ``` -and to [`BigInteger`](https://docs.oracle.com/javase/7/docs/api/java/math/BigInteger.html) of course: +to [`BigInteger`](https://docs.oracle.com/javase/7/docs/api/java/math/BigInteger.html) of course ```java Bytes.wrap(array).toBigInteger(); ``` +and others + +```java +Bytes.wrap(array).toUUID(); // convert 16 byte to UUID +Bytes.wrap(array).toCharArray(StandardCharsets.UTF-8); // converts to encoded char array +``` + ### Mutable and Read-Only Per default the instance is immutable, i.e. every transformation will create a @@ -522,6 +578,12 @@ b.wipe() //fills with zeros b.secureWipe() //fills with random data ``` +Create a immutable version again with: + +```java +Bytes b2 = b.immutable(); +``` + *Note:* a copy will inherit mutability/read-only properties: ```java @@ -529,6 +591,18 @@ Bytes b = Bytes.from(array).mutable().copy(); assertTrue(b.isMutable()); ``` +##### AutoClosable for try-with-resources + +In security-relevant environments it is best practice to wipe the memory of secret data, such as +secret keys. This can be used with Java 7 feature try-with-resource like this: + +```java +try (MutableBytes b = Bytes.wrap(aesBytes).mutable()) { + SecretKey s = new SecretKeySpec(b.array(), "AES"); + ... +} +``` + #### Readonly Bytes On the other hand, if you want a export a instance with limited access, @@ -547,6 +621,37 @@ readOnlyBytes.byteBuffer(); readOnlyBytes.inputStream(); ``` +## Download + +The artifacts are deployed to [Maven Central](https://search.maven.org/). + +### Maven + +Add the dependency of the [latest version](https://github.com/patrickfav/bytes-java/releases) to your `pom.xml`: + +```xml + + at.favre.lib + bytes + {latest-version} + +``` + +### Gradle + +Add to your `build.gradle` module dependencies: + + implementation group: 'at.favre.lib', name: 'bytes', version: '{latest-version}' + +### Local Jar Library + +[Grab jar from latest release.](https://github.com/patrickfav/bytes-java/releases/latest) + +### OSGi + +The library should be prepared to be used with the OSGi framework with the help of the [bundle plugin](http://felix.apache.org/documentation/subprojects/apache-felix-maven-bundle-plugin-bnd.html). + + ## Digital Signatures ### Signed Jar @@ -583,19 +688,26 @@ If you want to skip jar signing just change the skip configuration in the ### Build with Maven -Use maven (3.1+) to create a jar including all dependencies +Use the Maven wrapper to create a jar including all dependencies + + mvnw clean install + +### Checkstyle Config File - mvn clean install +This project uses my [`common-parent`](https://github.com/patrickfav/mvn-common-parent) which centralized a lot of +the plugin versions as well as providing the checkstyle config rules. Specifically they are maintained in [`checkstyle-config`](https://github.com/patrickfav/checkstyle-config). Locally the files will be copied after you `mvnw install` into your `target` folder and is called +`target/checkstyle-checker.xml`. So if you use a plugin for your IDE, use this file as your local configuration. ## Tech Stack -* Java 7 -* Maven +* Java 7 Source, JDK 11 required to build (not yet JDK17 compatible) +* Maven 3 # Credits -* Byte util methods derived from primitives.Bytes from [Google Guava](https://github.com/google/guava) -* Entropy class derived from [Twitter Commons](https://github.com/twitter/commons) +* Byte util methods derived from `primitives.Bytes` from [Google Guava](https://github.com/google/guava) (Apache v2) +* Entropy class derived from [Twitter Commons](https://github.com/twitter/commons) (Apache v2) +* Base64 implementation and some util methods from [Okio](https://github.com/square/okio) (Apache v2) # License diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml index ff21bd3..7780abc 100644 --- a/checkstyle-suppressions.xml +++ b/checkstyle-suppressions.xml @@ -28,4 +28,4 @@ - \ No newline at end of file + diff --git a/checkstyle.xml b/checkstyle.xml deleted file mode 100644 index ee886b4..0000000 --- a/checkstyle.xml +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/misc/banner.psd b/misc/banner.psd new file mode 100644 index 0000000..515d0c6 Binary files /dev/null and b/misc/banner.psd differ diff --git a/misc/icon.png b/misc/icon.png index 94923fa..d5b7db7 100644 Binary files a/misc/icon.png and b/misc/icon.png differ diff --git a/misc/icon.psd b/misc/icon.psd index 3eb405e..d9a48eb 100644 Binary files a/misc/icon.psd and b/misc/icon.psd differ diff --git a/mvnw b/mvnw index 5551fde..b7f0646 100755 --- a/mvnw +++ b/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script +# Apache Maven Wrapper startup batch script, version 3.1.1 # # Required ENV vars: # ------------------ @@ -27,7 +27,6 @@ # # Optional ENV vars # ----------------- -# M2_HOME - location of maven2's installed home dir # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @@ -36,6 +35,10 @@ if [ -z "$MAVEN_SKIP_RC" ] ; then + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + if [ -f /etc/mavenrc ] ; then . /etc/mavenrc fi @@ -58,9 +61,9 @@ case "`uname`" in # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" + JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME else - export JAVA_HOME="/Library/Java/Home" + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME fi fi ;; @@ -72,36 +75,8 @@ if [ -z "$JAVA_HOME" ] ; then fi fi -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - 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 - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` [ -n "$CLASSPATH" ] && @@ -110,11 +85,8 @@ fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" [ -n "$JAVA_HOME" ] && JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" - # TODO classpath? fi if [ -z "$JAVA_HOME" ]; then @@ -146,7 +118,7 @@ if [ -z "$JAVACMD" ] ; then JAVACMD="$JAVA_HOME/bin/java" fi else - JAVACMD="`which java`" + JAVACMD="`\\unset -f command; \\command -v java`" fi fi @@ -160,12 +132,9 @@ if [ -z "$JAVA_HOME" ] ; then echo "Warning: JAVA_HOME environment variable is not set." fi -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { - if [ -z "$1" ] then echo "Path not specified to find_maven_basedir" @@ -185,7 +154,7 @@ find_maven_basedir() { fi # end of workaround done - echo "${basedir}" + printf '%s' "$(cd "$basedir"; pwd)" } # concatenates all lines of a file @@ -195,11 +164,16 @@ concat_lines() { fi } -BASE_DIR=`find_maven_basedir "$(pwd)"` +BASE_DIR=$(find_maven_basedir "$(dirname $0)") if [ -z "$BASE_DIR" ]; then exit 1; fi +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi + ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. @@ -212,40 +186,67 @@ else if [ "$MVNW_VERBOSE" = true ]; then echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." fi - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + fi while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;; esac done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" + echo "Downloading from: $wrapperUrl" fi wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi if command -v wget > /dev/null; then + QUIET="--quiet" if [ "$MVNW_VERBOSE" = true ]; then echo "Found wget ... using wget" + QUIET="" fi - wget "$jarUrl" -O "$wrapperJarPath" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" + fi + [ $? -eq 0 ] || rm -f "$wrapperJarPath" elif command -v curl > /dev/null; then + QUIET="--silent" if [ "$MVNW_VERBOSE" = true ]; then echo "Found curl ... using curl" + QUIET="" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L fi - curl -o "$wrapperJarPath" "$jarUrl" + [ $? -eq 0 ] || rm -f "$wrapperJarPath" else if [ "$MVNW_VERBOSE" = true ]; then echo "Falling back to using Java to download" fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=`cygpath --path --windows "$javaSource"` + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo " - Compiling MavenWrapperDownloader.java ..." fi # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") + ("$JAVA_HOME/bin/javac" "$javaSource") fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ -e "$javaClass" ]; then # Running the downloader if [ "$MVNW_VERBOSE" = true ]; then echo " - Running MavenWrapperDownloader.java ..." @@ -259,16 +260,10 @@ fi # End of extension ########################################################################################## -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` [ -n "$CLASSPATH" ] && @@ -277,10 +272,16 @@ if $cygwin; then MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` fi +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain exec "$JAVACMD" \ $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd index e5cfb0a..474c9d6 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -18,15 +18,14 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script +@REM Apache Maven Wrapper startup batch script, version 3.1.1 @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @@ -37,7 +36,7 @@ @echo off @REM set title of command window title %0 -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME @@ -46,8 +45,8 @@ if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* :skipRcPre @setlocal @@ -120,24 +119,51 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" -FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( - echo Found %WRAPPER_JAR% + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) ) else ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" - echo Finished downloading %WRAPPER_JAR% + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) ) @REM End of extension -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end @@ -147,15 +173,15 @@ set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause +if "%MAVEN_BATCH_PAUSE%"=="on" pause -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% -exit /B %ERROR_CODE% +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index 6569c7d..2d2e383 100644 --- a/pom.xml +++ b/pom.xml @@ -4,205 +4,90 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - at.favre.lib + + at.favre.lib + common-parent + 20 + + bytes - 0.5.0 - jar + 1.6.2 + bundle Bytes Utility Library Bytes is a utility library that makes it easy to create, parse, transform, validate and convert byte arrays in Java. It supports endianness as well as immutability and mutability, so the caller may decide to favor performance. - https://github.com/patrickfav/bytes-java + https://favr.dev/opensource/bytes-java 2017 - - 3.1.0 - - - UTF-8 - UTF-8 + false + + patrickfav + https://sonarcloud.io + jacoco + reuseReports + java - - - maven-central-repo - http://repo1.maven.org/maven2 - - false - - - + + org.apache.maven.plugins + maven-enforcer-plugin + + + org.apache.felix + maven-bundle-plugin + 5.1.9 + true + org.apache.maven.plugins maven-checkstyle-plugin - 3.0.0 - checkstyle.xml checkstyle-suppressions.xml - checkstyle.suppressions.file - - - com.puppycrawl.tools - checkstyle - 8.11 - - org.apache.maven.plugins maven-compiler-plugin - 3.7.0 - - javac-with-errorprone - true - 1.7 - 1.7 - - - - org.codehaus.plexus - plexus-compiler-javac-errorprone - 2.8.2 - - - - com.google.errorprone - error_prone_core - - 2.0.5 - - org.apache.maven.plugins maven-source-plugin - 3.0.1 - - - attach-sources - - jar - - - org.apache.maven.plugins maven-javadoc-plugin - 3.0.1 - - - attach-javadocs - - jar - - - org.apache.maven.plugins maven-surefire-plugin - 2.22.0 org.jacoco jacoco-maven-plugin - 0.8.1 - - - prepare-agent - - prepare-agent - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 org.apache.maven.plugins maven-jarsigner-plugin - 1.4 - - - sign - - sign - - - - - false - keystore.jks - pfopensource - ${env.OPENSOURCE_PROJECTS_KS_PW} - ${env.OPENSOURCE_PROJECTS_KEY_PW} - net.nicoulaj.maven.plugins checksum-maven-plugin - 1.6 - - - - artifacts - - - - - - SHA-256 - - true - checksum-sha256.txt - false - false - - com.github.wvengen - proguard-maven-plugin - 2.0.14 - - - package - - proguard - - - false - optimized - true - 6.0.3 - false - - - - - ${java.home}/lib/rt.jar - - - - - - - net.sf.proguard - proguard-base - 6.0.3 - runtime - - + maven-jar-plugin + + + + at.favre.lib.bytes + + + @@ -212,38 +97,25 @@ junit junit - 4.12 + test + + + org.openjdk.jmh + jmh-core + 1.37 + test + + + org.openjdk.jmh + jmh-generator-annprocess + 1.37 test - - - patrickfav - Patrick Favre-Bulle - patrick.favrebulle@gmail.com - - - - - - bintray-patrickfav - patrickfav-bytes-java - https://api.bintray.com/maven/patrickfav/maven/bytes-java/;publish=1 - - - - - - Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - https://github.com/patrickfav/bytes-java.git - https://github.com/patrickfav/bytes-java.git - HEAD + scm:git:https://github.com/patrickfav/bytes-java.git + scm:git:https://github.com/patrickfav/bytes-java.git https://github.com/patrickfav/bytes-java @@ -253,7 +125,7 @@ - Travis - https://travis-ci.org/patrickfav/bytes-java + Github Actions + https://github.com/patrickfav/bytes-java/actions diff --git a/secrets.tar.enc b/secrets.tar.enc deleted file mode 100644 index 880e62b..0000000 Binary files a/secrets.tar.enc and /dev/null differ diff --git a/src/main/java/at/favre/lib/bytes/Base64.java b/src/main/java/at/favre/lib/bytes/Base64.java index c581f75..dd31fbf 100644 --- a/src/main/java/at/favre/lib/bytes/Base64.java +++ b/src/main/java/at/favre/lib/bytes/Base64.java @@ -21,10 +21,11 @@ package at.favre.lib.bytes; -import java.nio.charset.StandardCharsets; +import java.util.Arrays; /** - * From: https://github.com/square/okio/blob/master/okio/src/main/java/okio/Base64.java + * From https://github.com/square/okio/blob/okio-parent-1.15.0/okio/src/main/java/okio/Base64.java + * under Apache 2 license. * * @author Alexander Y. Kleymenov */ @@ -32,7 +33,21 @@ final class Base64 { private Base64() { } - public static byte[] decode(String in) { + private static final byte[] MAP = new byte[]{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', + '5', '6', '7', '8', '9', '+', '/' + }; + + private static final byte[] URL_MAP = new byte[]{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', + '5', '6', '7', '8', '9', '-', '_' + }; + + static byte[] decode(CharSequence in) { // Ignore trailing '=' padding and whitespace from the input. int limit = in.length(); for (; limit > 0; limit--) { @@ -78,7 +93,7 @@ public static byte[] decode(String in) { } // Append this char's 6 bits to the word. - word = (word << 6) | (byte) bits; + word = (word << 6) | (byte) bits & 0xff; // For every 4 chars of input, we accumulate 24 bits of output. Emit 3 bytes. inCount++; @@ -108,24 +123,19 @@ public static byte[] decode(String in) { if (outCount == out.length) return out; // Copy the decoded bytes to a new, right-sized array. - byte[] prefix = new byte[outCount]; - System.arraycopy(out, 0, prefix, 0, outCount); - return prefix; + return Arrays.copyOfRange(out, 0, outCount); } - private static final byte[] MAP = new byte[]{ - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', - 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', - 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', - '5', '6', '7', '8', '9', '+', '/' - }; + static byte[] encode(byte[] in) { + return encode(in, false, true); + } - public static String encode(byte[] in) { - return encode(in, MAP); + static byte[] encode(byte[] in, boolean urlSafe, boolean usePadding) { + return encode(in, urlSafe ? URL_MAP : MAP, usePadding); } - private static String encode(byte[] in, byte[] map) { - int length = (in.length + 2) / 3 * 4; + private static byte[] encode(byte[] in, byte[] map, boolean usePadding) { + int length = outLength(in.length, usePadding); byte[] out = new byte[length]; int index = 0, end = in.length - in.length % 3; for (int i = 0; i < end; i += 3) { @@ -138,16 +148,32 @@ private static String encode(byte[] in, byte[] map) { case 1: out[index++] = map[(in[end] & 0xff) >> 2]; out[index++] = map[(in[end] & 0x03) << 4]; - out[index++] = '='; - out[index++] = '='; + if (usePadding) { + out[index++] = '='; + out[index] = '='; + } break; case 2: out[index++] = map[(in[end] & 0xff) >> 2]; out[index++] = map[((in[end] & 0x03) << 4) | ((in[end + 1] & 0xff) >> 4)]; out[index++] = map[((in[end + 1] & 0x0f) << 2)]; - out[index++] = '='; + + if (usePadding) { + out[index] = '='; + } break; } - return new String(out, StandardCharsets.US_ASCII); + return out; + } + + private static int outLength(int srclen, boolean doPadding) { + int len; + if (doPadding) { + len = 4 * ((srclen + 2) / 3); + } else { + int n = srclen % 3; + len = 4 * (srclen / 3) + (n == 0 ? 0 : n + 1); + } + return len; } } diff --git a/src/main/java/at/favre/lib/bytes/BaseEncoding.java b/src/main/java/at/favre/lib/bytes/BaseEncoding.java new file mode 100644 index 0000000..8e23588 --- /dev/null +++ b/src/main/java/at/favre/lib/bytes/BaseEncoding.java @@ -0,0 +1,211 @@ +/* + * Copyright 2018 Patrick Favre-Bulle + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package at.favre.lib.bytes; + +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Objects; + +/** + * Encoder which supports arbitrary alphabet and padding. + *

+ * Derived from Google Guava's common/io/ BaseEncoding + *

+ * See: BaseEncoding + */ +final class BaseEncoding implements BinaryToTextEncoding.EncoderDecoder { + private static final char ASCII_MAX = 127; + + static final Alphabet BASE32_RFC4848 = new Alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".toCharArray()); + static final char BASE32_RFC4848_PADDING = '='; + + private final Alphabet alphabet; + private final Character paddingChar; + + @SuppressWarnings("WeakerAccess") + public BaseEncoding(Alphabet alphabet, Character paddingChar) { + this.alphabet = Objects.requireNonNull(alphabet); + this.paddingChar = paddingChar; + } + + private int maxEncodedSize(int bytes) { + return alphabet.charsPerChunk * divide(bytes, alphabet.bytesPerChunk); + } + + @Override + public String encode(byte[] array, ByteOrder byteOrder) { + return encode(array, 0, array.length); + } + + private String encode(byte[] bytes, int off, int len) { + StringBuilder result = new StringBuilder(maxEncodedSize(len)); + try { + encodeTo(result, bytes, off, len); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + return result.toString(); + } + + private void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOException { + Objects.requireNonNull(target); + for (int i = 0; i < len; i += alphabet.bytesPerChunk) { + encodeChunkTo(target, bytes, off + i, Math.min(alphabet.bytesPerChunk, len - i)); + } + } + + private void encodeChunkTo(Appendable target, byte[] bytes, int off, int len) throws IOException { + Objects.requireNonNull(target); + long bitBuffer = 0; + for (int i = 0; i < len; ++i) { + bitBuffer |= bytes[off + i] & 0xFF; + bitBuffer <<= 8; // Add additional zero byte in the end. + } + // Position of first character is length of bitBuffer minus bitsPerChar. + final int bitOffset = (len + 1) * 8 - alphabet.bitsPerChar; + int bitsProcessed = 0; + while (bitsProcessed < len * 8) { + int charIndex = (int) (bitBuffer >>> (bitOffset - bitsProcessed)) & alphabet.mask; + target.append(alphabet.encode(charIndex)); + bitsProcessed += alphabet.bitsPerChar; + } + if (paddingChar != null) { + while (bitsProcessed < alphabet.bytesPerChunk * 8) { + target.append(paddingChar); + bitsProcessed += alphabet.bitsPerChar; + } + } + } + + private int maxDecodedSize(int chars) { + return (int) ((alphabet.bitsPerChar * (long) chars + 7L) / 8L); + } + + private String trimTrailingPadding(CharSequence chars) { + Objects.requireNonNull(chars); + if (paddingChar == null) { + return chars.toString(); + } + int l; + for (l = chars.length() - 1; l >= 0; l--) { + if (chars.charAt(l) != paddingChar) { + break; + } + } + return chars.subSequence(0, l + 1).toString(); + } + + @Override + public byte[] decode(CharSequence encoded) { + encoded = trimTrailingPadding(encoded); + byte[] tmp = new byte[maxDecodedSize(encoded.length())]; + int len = decodeTo(tmp, encoded); + return extract(tmp, len); + } + + private static byte[] extract(byte[] result, int length) { + if (length == result.length) { + return result; + } else { + byte[] trunc = new byte[length]; + System.arraycopy(result, 0, trunc, 0, length); + return trunc; + } + } + + private int decodeTo(byte[] target, CharSequence chars) { + Objects.requireNonNull(target); + chars = trimTrailingPadding(chars); + int bytesWritten = 0; + for (int charIdx = 0; charIdx < chars.length(); charIdx += alphabet.charsPerChunk) { + long chunk = 0; + int charsProcessed = 0; + for (int i = 0; i < alphabet.charsPerChunk; i++) { + chunk <<= alphabet.bitsPerChar; + if (charIdx + i < chars.length()) { + chunk |= alphabet.decode(chars.charAt(charIdx + charsProcessed++)); + } + } + final int minOffset = alphabet.bytesPerChunk * 8 - charsProcessed * alphabet.bitsPerChar; + for (int offset = (alphabet.bytesPerChunk - 1) * 8; offset >= minOffset; offset -= 8) { + target[bytesWritten++] = (byte) ((chunk >>> offset) & 0xFF); + } + } + return bytesWritten; + } + + static final class Alphabet { + // this is meant to be immutable -- don't modify it! + private final char[] chars; + final int mask; + final int bitsPerChar; + final int charsPerChunk; + final int bytesPerChunk; + private final byte[] decodabet; + + Alphabet(char[] chars) { + this.chars = Objects.requireNonNull(chars); + this.bitsPerChar = log2(chars.length); + + /* + * e.g. for base64, bitsPerChar == 6, charsPerChunk == 4, and bytesPerChunk == 3. This makes + * for the smallest chunk size that still has charsPerChunk * bitsPerChar be a multiple of 8. + */ + int gcd = Math.min(8, Integer.lowestOneBit(bitsPerChar)); + this.charsPerChunk = 8 / gcd; + this.bytesPerChunk = bitsPerChar / gcd; + this.mask = chars.length - 1; + + byte[] decodabet = new byte[ASCII_MAX + 1]; + Arrays.fill(decodabet, (byte) -1); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + decodabet[c] = (byte) i; + } + this.decodabet = decodabet; + } + + char encode(int bits) { + return chars[bits]; + } + + int decode(char ch) { + return decodabet[ch]; + } + } + + private static int divide(int p, int q) { + int div = p / q; + int rem = p - q * div; // equal to p % q + + if (rem == 0) { + return div; + } + int signum = 1 | ((p ^ q) >> (Integer.SIZE - 1)); + return signum > 0 ? div + signum : div; + } + + private static int log2(int x) { + return (Integer.SIZE - 1) - Integer.numberOfLeadingZeros(x); + } +} diff --git a/src/main/java/at/favre/lib/bytes/BinaryToTextEncoding.java b/src/main/java/at/favre/lib/bytes/BinaryToTextEncoding.java index 6977abf..01f7654 100644 --- a/src/main/java/at/favre/lib/bytes/BinaryToTextEncoding.java +++ b/src/main/java/at/favre/lib/bytes/BinaryToTextEncoding.java @@ -23,6 +23,7 @@ import java.math.BigInteger; import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; import java.util.Objects; /** @@ -58,7 +59,7 @@ interface Decoder { * @param encoded string * @return byte array represented by given encoded string */ - byte[] decode(String encoded); + byte[] decode(CharSequence encoded); } /** @@ -71,6 +72,8 @@ interface EncoderDecoder extends Encoder, Decoder { * Hex or Base16 */ class Hex implements EncoderDecoder { + private static final char[] LOOKUP_TABLE_LOWER = new char[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66}; + private static final char[] LOOKUP_TABLE_UPPER = new char[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46}; private final boolean upperCase; public Hex() { @@ -83,43 +86,55 @@ public Hex(boolean upperCase) { @Override public String encode(byte[] byteArray, ByteOrder byteOrder) { - StringBuilder sb = new StringBuilder(byteArray.length * 2); + + final char[] buffer = new char[byteArray.length * 2]; + final char[] lookup = upperCase ? LOOKUP_TABLE_UPPER : LOOKUP_TABLE_LOWER; int index; for (int i = 0; i < byteArray.length; i++) { index = (byteOrder == ByteOrder.BIG_ENDIAN) ? i : byteArray.length - i - 1; - char first4Bit = Character.forDigit((byteArray[index] >> 4) & 0xF, 16); - char last4Bit = Character.forDigit((byteArray[index] & 0xF), 16); - if (upperCase) { - first4Bit = Character.toUpperCase(first4Bit); - last4Bit = Character.toUpperCase(last4Bit); - } - sb.append(first4Bit).append(last4Bit); + + buffer[i << 1] = lookup[(byteArray[index] >> 4) & 0xF]; + buffer[(i << 1) + 1] = lookup[(byteArray[index] & 0xF)]; } - return sb.toString(); + return new String(buffer); } @Override - public byte[] decode(String hexString) { - Objects.requireNonNull(hexString, "hex input must not be null"); - if (hexString.length() % 2 != 0) - throw new IllegalArgumentException("invalid hex string, must be mod 2 == 0"); + public byte[] decode(CharSequence hexString) { int start; - if (hexString.startsWith("0x")) { + if (Objects.requireNonNull(hexString).length() > 2 && + hexString.charAt(0) == '0' && hexString.charAt(1) == 'x') { start = 2; } else { start = 0; } + int len = hexString.length(); + boolean isOddLength = len % 2 != 0; + if (isOddLength) { + start--; + } + byte[] data = new byte[(len - start) / 2]; + int first4Bits; + int second4Bits; for (int i = start; i < len; i += 2) { - int first4Bits = Character.digit(hexString.charAt(i), 16); - int second4Bits = Character.digit(hexString.charAt(i + 1), 16); + if (i == start && isOddLength) { + first4Bits = 0; + } else { + first4Bits = Character.digit(hexString.charAt(i), 16); + } + second4Bits = Character.digit(hexString.charAt(i + 1), 16); if (first4Bits == -1 || second4Bits == -1) { - throw new IllegalArgumentException("'" + hexString.charAt(i) + hexString.charAt(i + 1) + "' at index " + i + " is not hex formatted"); + if (i == start && isOddLength) { + throw new IllegalArgumentException("'" + hexString.charAt(i + 1) + "' at index " + (i + 1) + " is not hex formatted"); + } else { + throw new IllegalArgumentException("'" + hexString.charAt(i) + hexString.charAt(i + 1) + "' at index " + i + " is not hex formatted"); + } } data[(i - start) / 2] = (byte) ((first4Bits << 4) + second4Bits); @@ -132,13 +147,25 @@ public byte[] decode(String hexString) { * Simple Base64 encoder */ class Base64Encoding implements EncoderDecoder { + private final boolean urlSafe; + private final boolean padding; + + Base64Encoding() { + this(false, true); + } + + Base64Encoding(boolean urlSafe, boolean padding) { + this.urlSafe = urlSafe; + this.padding = padding; + } + @Override public String encode(byte[] array, ByteOrder byteOrder) { - return Base64.encode((byteOrder == ByteOrder.BIG_ENDIAN) ? array : Bytes.from(array).reverse().array()); + return new String(Base64.encode((byteOrder == ByteOrder.BIG_ENDIAN) ? array : Bytes.from(array).reverse().array(), urlSafe, padding), StandardCharsets.US_ASCII); } @Override - public byte[] decode(String encoded) { + public byte[] decode(CharSequence encoded) { return Base64.decode(encoded); } } @@ -146,22 +173,24 @@ public byte[] decode(String encoded) { /** * Simple radix encoder which internally uses {@link BigInteger#toString(int)} */ - class BaseRadix implements EncoderDecoder { + class BaseRadixNumber implements EncoderDecoder { private final int radix; - BaseRadix(int radix) { + BaseRadixNumber(int radix) { + if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { + throw new IllegalArgumentException("supported radix is between " + Character.MIN_RADIX + " and " + Character.MAX_RADIX); + } this.radix = radix; } @Override public String encode(byte[] array, ByteOrder byteOrder) { - System.out.println("max length " + maxLength(array, radix)); return new BigInteger(1, (byteOrder == ByteOrder.BIG_ENDIAN) ? array : Bytes.from(array).reverse().array()).toString(radix); } @Override - public byte[] decode(String encoded) { - byte[] array = new BigInteger(encoded, radix).toByteArray(); + public byte[] decode(CharSequence encoded) { + byte[] array = new BigInteger(encoded.toString(), radix).toByteArray(); if (array[0] == 0) { byte[] tmp = new byte[array.length - 1]; System.arraycopy(array, 1, tmp, 0, tmp.length); @@ -169,9 +198,5 @@ public byte[] decode(String encoded) { } return array; } - - private int maxLength(byte[] data, int radix) { - return BigInteger.valueOf(2).pow(BigInteger.valueOf(data.length).multiply(BigInteger.valueOf(8)).intValue()).toString(radix).length(); - } } } diff --git a/src/main/java/at/favre/lib/bytes/Bytes.java b/src/main/java/at/favre/lib/bytes/Bytes.java index 3727430..123eeec 100644 --- a/src/main/java/at/favre/lib/bytes/Bytes.java +++ b/src/main/java/at/favre/lib/bytes/Bytes.java @@ -23,10 +23,7 @@ import java.io.*; import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.CharBuffer; -import java.nio.ReadOnlyBufferException; +import java.nio.*; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; @@ -34,7 +31,7 @@ import java.util.*; /** - * Bytes is wrapper class for an byte-array that allows a lot of convenience operations on it: + * "Bytes" is wrapper class for a byte-array that allows a lot of convenience operations on it: *

    *
  • Creation from various source: arrays, primitives, parsed or random
  • *
  • Encoding in many formats: hex, base64, etc.
  • @@ -48,18 +45,24 @@ * It supports byte ordering (little/big endianness). *

    * This class is immutable as long as the internal array is not changed from outside (which can't be assured, when - * using using wrap()). It is possible to create a mutable version (see {@link MutableBytes}). + * using wrap()). It is possible to create a mutable version (see {@link MutableBytes}). *

    * Example: *

    - *     Bytes b = Bytes.from(array);
    + *     Bytes b = Bytes.from(array).mutable();
      *     b.not();
      *     System.out.println(b.encodeHex());
      * 
    + * + *

    Comparable

    + * The implemented comparator treats the bytes as signed bytes. If you want to sort, treating each byte as unsigned, + * use {@link BytesTransformers#sortUnsigned()}. */ @SuppressWarnings("WeakerAccess") public class Bytes implements Comparable, Serializable, Iterable { + private static final Bytes EMPTY = Bytes.wrap(new byte[0]); + /* FACTORY ***************************************************************************************************/ /** @@ -80,6 +83,7 @@ public static Bytes allocate(int length) { * @return new instance */ public static Bytes allocate(int length, byte defaultValue) { + if (length == 0) return empty(); byte[] array = new byte[length]; if (defaultValue != 0) { Arrays.fill(array, defaultValue); @@ -87,6 +91,15 @@ public static Bytes allocate(int length, byte defaultValue) { return wrap(array); } + /** + * Creates a Byte instance with an internal empty byte array. Same as calling {@link #allocate(int)} with 0. + * + * @return the empty instance (always the same reference + */ + public static Bytes empty() { + return EMPTY; + } + /** * Creates a new reference backed by the same byte array. * Inherits all attributes (readonly, etc.) @@ -95,7 +108,7 @@ public static Bytes allocate(int length, byte defaultValue) { * @return new instance */ public static Bytes wrap(Bytes bytes) { - return new Bytes(bytes.internalArray(), bytes.byteOrder); + return wrap(Objects.requireNonNull(bytes, "passed Byte instance must not be null").internalArray(), bytes.byteOrder); } /** @@ -111,7 +124,7 @@ public static Bytes wrap(Bytes bytes) { * @return new instance */ public static Bytes wrapNullSafe(byte[] array) { - return wrap(array != null ? array : new byte[0]); + return array != null ? wrap(array) : empty(); } /** @@ -140,8 +153,7 @@ public static Bytes wrap(byte[] array) { * @return new instance */ public static Bytes wrap(byte[] array, ByteOrder byteOrder) { - Objects.requireNonNull(array, "passed array must not be null"); - return new Bytes(array, byteOrder); + return new Bytes(Objects.requireNonNull(array, "passed array must not be null"), byteOrder); } /** @@ -152,8 +164,7 @@ public static Bytes wrap(byte[] array, ByteOrder byteOrder) { * @return new instance */ public static Bytes from(byte[] byteArrayToCopy) { - Objects.requireNonNull(byteArrayToCopy, "must at least pass a single byte"); - return wrap(Arrays.copyOf(byteArrayToCopy, byteArrayToCopy.length)); + return wrap(Arrays.copyOf(Objects.requireNonNull(byteArrayToCopy, "must at least pass a single byte"), byteArrayToCopy.length)); } /** @@ -166,14 +177,14 @@ public static Bytes from(byte[] byteArrayToCopy) { * @return new instance */ public static Bytes fromNullSafe(byte[] byteArrayToCopy) { - return from(byteArrayToCopy != null ? byteArrayToCopy : new byte[0]); + return byteArrayToCopy != null ? from(byteArrayToCopy) : empty(); } /** * Creates a new instance from a slice of given array * * @param array to slice - * @param offset stat position + * @param offset start position * @param length length * @return new instance */ @@ -191,7 +202,7 @@ public static Bytes from(byte[] array, int offset, int length) { * @return new instance */ public static Bytes from(byte[]... moreArrays) { - return wrap(Util.concat(moreArrays)); + return wrap(Util.Byte.concat(moreArrays)); } /** @@ -217,7 +228,7 @@ public static Bytes from(Bytes... moreBytes) { * @return new instance */ public static Bytes from(Collection bytesCollection) { - return wrap(Util.toArray(bytesCollection)); + return wrap(Util.Converter.toArray(bytesCollection)); } /** @@ -227,7 +238,7 @@ public static Bytes from(Collection bytesCollection) { * @return new instance */ public static Bytes from(Byte[] boxedObjectArray) { - return wrap(Util.toPrimitiveArray(boxedObjectArray)); + return wrap(Util.Converter.toPrimitiveArray(boxedObjectArray)); } /** @@ -249,7 +260,7 @@ public static Bytes from(byte singleByte) { * @return new instance */ public static Bytes from(byte firstByte, byte... moreBytes) { - return wrap(Util.concatVararg(firstByte, moreBytes)); + return wrap(Util.Byte.concatVararg(firstByte, moreBytes)); } /** @@ -285,6 +296,16 @@ public static Bytes from(short short2Byte) { return wrap(ByteBuffer.allocate(2).putShort(short2Byte).array()); } + /** + * Creates a new instance from given 2 byte short array. + * + * @param shortArray to create from + * @return new instance + */ + public static Bytes from(short... shortArray) { + return wrap(Util.Converter.toByteArray(Objects.requireNonNull(shortArray, "must provide at least a single short"))); + } + /** * Creates a new instance from given 4 byte integer. * @@ -302,8 +323,7 @@ public static Bytes from(int integer4byte) { * @return new instance */ public static Bytes from(int... intArray) { - Objects.requireNonNull(intArray, "must provide at least a single int"); - return wrap(Util.toByteArray(intArray)); + return wrap(Util.Converter.toByteArray(Objects.requireNonNull(intArray, "must provide at least a single int"))); } /** @@ -323,8 +343,7 @@ public static Bytes from(long long8byte) { * @return new instance */ public static Bytes from(long... longArray) { - Objects.requireNonNull(longArray, "must provide at least a single long"); - return wrap(Util.toByteArray(longArray)); + return wrap(Util.Converter.toByteArray(Objects.requireNonNull(longArray, "must provide at least a single long"))); } /** @@ -337,6 +356,16 @@ public static Bytes from(float float4byte) { return wrap(ByteBuffer.allocate(4).putFloat(float4byte).array()); } + /** + * Creates a new instance from given float array. + * + * @param floatArray to create from + * @return new instance + */ + public static Bytes from(float... floatArray) { + return wrap(Util.Converter.toByteArray(Objects.requireNonNull(floatArray, "must provide at least a single float"))); + } + /** * Creates a new instance from given 8 byte floating point number (double). * @@ -348,14 +377,46 @@ public static Bytes from(double double8Byte) { } /** - * Creates a new instance from given ByteBuffer. + * Creates a new instance from given double array. + * + * @param doubleArray to create from + * @return new instance + */ + public static Bytes from(double... doubleArray) { + return wrap(Util.Converter.toByteArray(Objects.requireNonNull(doubleArray, "must provide at least a single double"))); + } + + /** + * Creates a new instance from given {@link ByteBuffer}. * Will use the same backing byte array and honour the buffer's byte order. * - * @param buffer to get the byte array from + * @param buffer to get the byte array from (must not be null) * @return new instance */ public static Bytes from(ByteBuffer buffer) { - return wrap(buffer.array(), buffer.order()); + return wrap(Objects.requireNonNull(buffer, "provided byte buffer must not be null").array(), buffer.order()); + } + + /** + * Creates a new instance from given {@link CharBuffer}. + * Will ignore buffer's byte order and use {@link ByteOrder#BIG_ENDIAN} + * + * @param buffer to get the char array from (must not be null) + * @return new instance + */ + public static Bytes from(CharBuffer buffer) { + return from(Objects.requireNonNull(buffer, "provided char buffer must not be null").array()); + } + + /** + * Creates a new instance from given {@link IntBuffer}. + * Will ignore buffer's byte order and use {@link ByteOrder#BIG_ENDIAN} + * + * @param buffer to get the int array from (must not be null) + * @return new instance + */ + public static Bytes from(IntBuffer buffer) { + return from(Objects.requireNonNull(buffer, "provided int buffer must not be null").array()); } /** @@ -369,7 +430,6 @@ public static Bytes from(BitSet set) { } /** - * /** * Creates a new instance from given {@link BigInteger}. * * @param bigInteger to get the byte array from @@ -380,13 +440,25 @@ public static Bytes from(BigInteger bigInteger) { } /** - * Reads given input stream and creates a new instance from read data + * Reads given whole input stream and creates a new instance from read data * * @param stream to read from * @return new instance */ public static Bytes from(InputStream stream) { - return wrap(Util.readFromStream(stream)); + return wrap(Util.File.readFromStream(stream, -1)); + } + + /** + * Reads given input stream up to maxLength and creates a new instance from read data. + * Read maxLength is never longer than stream size (i.e. maxLength is only limiting, not assuring maxLength) + * + * @param stream to read from + * @param maxLength read to this maxLength or end of stream + * @return new instance + */ + public static Bytes from(InputStream stream, int maxLength) { + return wrap(Util.File.readFromStream(stream, maxLength)); } /** @@ -397,7 +469,7 @@ public static Bytes from(InputStream stream) { * @return new instance */ public static Bytes from(DataInput dataInput, int length) { - return wrap(Util.readFromDataInput(dataInput, length)); + return wrap(Util.File.readFromDataInput(dataInput, length)); } /** @@ -410,7 +482,22 @@ public static Bytes from(DataInput dataInput, int length) { * @throws IllegalStateException if file could not be read */ public static Bytes from(File file) { - return wrap(Util.readFromFile(file)); + return wrap(Util.File.readFromFile(file)); + } + + /** + * Reads given file and returns the byte content. Be aware that the whole defined file content will be loaded to + * memory, so be careful what to read in. This uses {@link java.io.RandomAccessFile} under the hood. + * + * @param file to read from + * @param offset byte offset from zero position of the file + * @param length to read from offset + * @return new instance + * @throws IllegalArgumentException if file does not exist + * @throws IllegalStateException if file could not be read + */ + public static Bytes from(File file, int offset, int length) { + return wrap(Util.File.readFromFile(file, offset, length)); } /** @@ -442,9 +529,7 @@ public static Bytes from(CharSequence utf8String, Normalizer.Form form) { * @return new instance */ public static Bytes from(CharSequence string, Charset charset) { - Objects.requireNonNull(string, "provided string must not be null"); - Objects.requireNonNull(charset, "provided charset must not be null"); - return wrap(string.toString().getBytes(charset)); + return wrap(Objects.requireNonNull(string, "provided string must not be null").toString().getBytes(Objects.requireNonNull(charset, "provided charset must not be null"))); } /** @@ -454,7 +539,42 @@ public static Bytes from(CharSequence string, Charset charset) { * @return new instance */ public static Bytes from(char[] charArray) { - return from(CharBuffer.wrap(charArray).toString(), StandardCharsets.UTF_8); + return from(charArray, StandardCharsets.UTF_8); + } + + /** + * Creates a new instance from given char array. The array will be handles like an encoded string + * + * @param charArray to get the internal byte array from + * @param charset charset to be used to decode the char array + * @return new instance + */ + public static Bytes from(char[] charArray, Charset charset) { + return from(charArray, charset, 0, charArray.length); + } + + /** + * Creates a new instance from given char array with given range. The array will be handles like an encoded string + * + * @param charArray to get the internal byte array from + * @param charset charset to be used to decode the char array + * @param offset start position (from given char array not encoded byte array out) + * @param length length in relation to offset (from given char array not encoded byte array out) + * @return new instance + */ + public static Bytes from(char[] charArray, Charset charset, int offset, int length) { + return from(Util.Converter.charToByteArray(charArray, charset, offset, length)); + } + + /** + * Convert UUID to a newly generated 16 byte long array representation. Puts the 8 byte most significant bits and + * 8 byte the least significant bits into a byte array. + * + * @param uuid to convert to array + * @return new instance + */ + public static Bytes from(UUID uuid) { + return wrap(Util.Converter.toBytesFromUUID(Objects.requireNonNull(uuid)).array()); } /** @@ -463,8 +583,8 @@ public static Bytes from(char[] charArray) { * @param binaryString the encoded string * @return decoded instance */ - public static Bytes parseBinary(String binaryString) { - return parse(binaryString, new BinaryToTextEncoding.BaseRadix(2)); + public static Bytes parseBinary(CharSequence binaryString) { + return parseRadix(binaryString, 2); } /** @@ -473,8 +593,8 @@ public static Bytes parseBinary(String binaryString) { * @param octalString the encoded string * @return decoded instance */ - public static Bytes parseOctal(String octalString) { - return parse(octalString, new BinaryToTextEncoding.BaseRadix(8)); + public static Bytes parseOctal(CharSequence octalString) { + return parseRadix(octalString, 8); } /** @@ -483,38 +603,77 @@ public static Bytes parseOctal(String octalString) { * @param decString the encoded string * @return decoded instance */ - public static Bytes parseDec(String decString) { - return parse(decString, new BinaryToTextEncoding.BaseRadix(10)); + public static Bytes parseDec(CharSequence decString) { + return parseRadix(decString, 10); + } + + /** + * Encodes with given radix string representation (e.g. radix 16 would be hex). + * See also {@link BigInteger#toString(int)}. + *

    + * This is usually a number encoding, not a data encoding (i.e. leading zeros are not preserved), but this implementation + * tries to preserve the leading zeros, to keep the in/output byte length size the same, but use at your own risk! + * + * @param radixNumberString the encoded string + * @param radix radix of the String representation (supported are 2-36) + * @return decoded instance + */ + public static Bytes parseRadix(CharSequence radixNumberString, int radix) { + return parse(radixNumberString, new BinaryToTextEncoding.BaseRadixNumber(radix)); } /** - * Parsing of base16/HEX encoded byte arrays. Will accept upper- and lowercase variant and ignores - * possible "0x" prefix. + * Parsing of base16/HEX encoded byte arrays. This is by design a very flexible decoder accepting the following cases: + * + *

      + *
    • Upper- and lowercase a-f (also mixed case)
    • + *
    • Prefix with 0x which will be ignored
    • + *
    • Even and odd number of string length with auto zero padding (i.e. 'E3F' is same as '0E3F')
    • + *
    * * @param hexString the encoded string * @return decoded instance + * @throws IllegalArgumentException if string contains something else than [0-9a-fA-F] */ - public static Bytes parseHex(String hexString) { + public static Bytes parseHex(CharSequence hexString) { return parse(hexString, new BinaryToTextEncoding.Hex()); } + /** + * Parsing of base32/RFC 4648 encoded byte arrays. + *

    + * Uses the RFC 4648 non-hex alphabet, see Base32 alphabet. + * + * @param base32Rfc4648String the encoded string + * @return decoded instance + */ + public static Bytes parseBase32(CharSequence base32Rfc4648String) { + return parse(base32Rfc4648String, new BaseEncoding(BaseEncoding.BASE32_RFC4848, BaseEncoding.BASE32_RFC4848_PADDING)); + } + /** * Parsing of base36 encoded byte arrays. + *

    + * This is usually a number encoding, not a data encoding (i.e. leading zeros are not preserved), but this implementation + * tries to preserve the leading zeros, to keep the in/output byte length size the same. * * @param base36String the encoded string * @return decoded instance + * @deprecated use {@link #parseRadix(CharSequence, int)} with 36 instead; will be removed in v1.0+ */ - public static Bytes parseBase36(String base36String) { - return parse(base36String, new BinaryToTextEncoding.BaseRadix(36)); + @Deprecated + public static Bytes parseBase36(CharSequence base36String) { + return parse(base36String, new BinaryToTextEncoding.BaseRadixNumber(36)); } /** * Parsing of base64 encoded byte arrays. + * Supporting RFC 4648 normal and url safe encoding, with or without padding. * * @param base64String the encoded string * @return decoded instance */ - public static Bytes parseBase64(String base64String) { + public static Bytes parseBase64(CharSequence base64String) { return parse(base64String, new BinaryToTextEncoding.Base64Encoding()); } @@ -525,11 +684,8 @@ public static Bytes parseBase64(String base64String) { * @param decoder the decoder used to decode the string * @return decoded instance */ - public static Bytes parse(String encoded, BinaryToTextEncoding.Decoder decoder) { - Objects.requireNonNull(encoded, "encoded data must not be null"); - Objects.requireNonNull(decoder, "passed decoder instance must no be null"); - - return wrap(decoder.decode(encoded)); + public static Bytes parse(CharSequence encoded, BinaryToTextEncoding.Decoder decoder) { + return wrap(Objects.requireNonNull(decoder, "passed decoder instance must no be null").decode(Objects.requireNonNull(encoded, "encoded data must not be null"))); } /** @@ -542,6 +698,35 @@ public static Bytes random(int length) { return random(length, new SecureRandom()); } + /** + * A new instance with pseudo random bytes using an unsecure random number generator. + * This may be used in e.g. tests. In production code use {@link #random(int)} per default. + *

    + * ONLY USE IN NON-SECURITY RELEVANT CONTEXT! + * + * @param length desired array length + * @return random instance + */ + public static Bytes unsecureRandom(int length) { + return random(length, new Random()); + } + + /** + * A new instance with pseudo random bytes using an unsecure random number generator. + * This may be used in e.g. tests to create predictable numbers. + *

    + * In production code use {@link #random(int)} per default. + *

    + * ONLY USE IN NON-SECURITY RELEVANT CONTEXT! + * + * @param length desired array length + * @param seed used to seed random number generator - using same seed will generate same numbers + * @return random instance + */ + public static Bytes unsecureRandom(int length, long seed) { + return random(length, new Random(seed)); + } + /** * A new instance with random bytes. * @@ -580,7 +765,7 @@ public static Bytes random(int length, Random random) { /* TRANSFORMER **********************************************************************************************/ /** - * Creates a new instance with the current array appended to the provided data (ie. append at the end). + * Creates a new instance with the current array appended to the provided data (i.e. append at the end). *

    * This will create a new byte array internally, so it is not suitable to use as extensive builder pattern - * use {@link ByteBuffer} or {@link java.io.ByteArrayOutputStream} for that. @@ -593,7 +778,7 @@ public Bytes append(Bytes bytes) { } /** - * Creates a new instance with the current array appended to the provided data (ie. append at the end) + * Creates a new instance with the current array appended to the provided data (i.e. append at the end) * * @param singleByte to append * @return appended instance @@ -603,7 +788,7 @@ public Bytes append(byte singleByte) { } /** - * Creates a new instance with the current array appended to the provided data (ie. append at the end) + * Creates a new instance with the current array appended to the provided data (i.e. append at the end) * * @param char2Bytes to append * @return appended instance @@ -613,7 +798,7 @@ public Bytes append(char char2Bytes) { } /** - * Creates a new instance with the current array appended to the provided data (ie. append at the end) + * Creates a new instance with the current array appended to the provided data (i.e. append at the end) * * @param short2Bytes to append * @return appended instance @@ -623,7 +808,7 @@ public Bytes append(short short2Bytes) { } /** - * Creates a new instance with the current array appended to the provided data (ie. append at the end) + * Creates a new instance with the current array appended to the provided data (i.e. append at the end) * * @param integer4Bytes to append * @return appended instance @@ -633,7 +818,7 @@ public Bytes append(int integer4Bytes) { } /** - * Creates a new instance with the current array appended to the provided data (ie. append at the end) + * Creates a new instance with the current array appended to the provided data (i.e. append at the end) * * @param long8Bytes to append * @return appended instance @@ -643,7 +828,19 @@ public Bytes append(long long8Bytes) { } /** - * Creates a new instance with the current array appended to the provided data (ie. append at the end) + * Creates a new instance with the current array appended to the provided data (i.e. append at the end). + * You may use this to append multiple byte arrays without the need for chaining the {@link #append(byte[])} call + * and therefore generating intermediate copies of the byte array, making this approach use less memory. + * + * @param arrays to append + * @return appended instance + */ + public Bytes append(byte[]... arrays) { + return append(Bytes.from(arrays)); + } + + /** + * Creates a new instance with the current array appended to the provided data (i.e. append at the end) * * @param secondArray to append * @return appended instance @@ -653,7 +850,7 @@ public Bytes append(byte[] secondArray) { } /** - * Creates a new instance with the current array appended to the provided data (ie. append at the end) + * Creates a new instance with the current array appended to the provided data (i.e. append at the end) *

    * If given array is null, the nothing will be appended. * @@ -682,9 +879,7 @@ public Bytes append(CharSequence stringUtf8) { * @return appended instance */ public Bytes append(CharSequence string, Charset charset) { - Objects.requireNonNull(charset); - Objects.requireNonNull(string); - return transform(new BytesTransformer.ConcatTransformer(string.toString().getBytes(charset))); + return transform(new BytesTransformer.ConcatTransformer(Objects.requireNonNull(string).toString().getBytes(Objects.requireNonNull(charset)))); } /** @@ -744,7 +939,7 @@ public Bytes and(byte[] secondArray) { * @see Bitwise operators: OR */ public Bytes or(Bytes bytes) { - return and(bytes.internalArray()); + return or(bytes.internalArray()); } /** @@ -781,7 +976,11 @@ public Bytes not() { * @see Bit shifts */ public Bytes leftShift(int shiftCount) { - return transform(new BytesTransformer.ShiftTransformer(shiftCount, BytesTransformer.ShiftTransformer.Type.LEFT_SHIFT)); + return transform(new BytesTransformer.ShiftTransformer( + shiftCount, + BytesTransformer.ShiftTransformer.Type.LEFT_SHIFT, + byteOrder + )); } /** @@ -796,11 +995,15 @@ public Bytes leftShift(int shiftCount) { * @see Bit shifts */ public Bytes rightShift(int shiftCount) { - return transform(new BytesTransformer.ShiftTransformer(shiftCount, BytesTransformer.ShiftTransformer.Type.RIGHT_SHIFT)); + return transform(new BytesTransformer.ShiftTransformer( + shiftCount, + BytesTransformer.ShiftTransformer.Type.RIGHT_SHIFT, + byteOrder + )); } /** - * Returns a Byte whose value is equivalent to this Byte with the designated bit set to newBitValue. Bits start to count from the LSB (ie. Bytes.from(0).switchBit(0,true) == 1) + * Returns a Byte whose value is equivalent to this Byte with the designated bit set to newBitValue. Bits start to count from the LSB (i.e. Bytes.from(0).switchBit(0,true) == 1) * * @param bitPosition not to confuse with byte position * @param newBitValue if true set to 1, 0 otherwise @@ -859,7 +1062,7 @@ public Bytes reverse() { * copy but not the original, the copy will contain {@code (byte)0}. *

    * Resize from LSB or length, so an array [0,1,2,3] resized to 3 will result in [1,2,3] or resized to 5 [0,0,1,2,3]. - * So when a 8 byte value resized to 4 byte will result in the same 32 bit integer value + * So when an 8 byte value resized to 4 byte will result in the same 32-bit integer value * * @param newByteLength the length of the copy to be returned * @return a copy with the desired size or "this" instance if newByteLength == current length @@ -891,6 +1094,36 @@ public Bytes resize(int newByteLength, BytesTransformer.ResizeTransformer.Mode m return transform(new BytesTransformer.ResizeTransformer(newByteLength, mode)); } + /** + * Calculates md5 on the underlying byte array and returns a byte instance containing the hash. + * This hash algorithm SHOULD be supported by every JVM implementation (see + * Javadoc for MessageDigest) + *

    + * Do not use this algorithm in security relevant applications. + * + * @return md5 (16 bytes) hash of internal byte array + * @throws IllegalArgumentException if the message digest algorithm can not be found in the security providers + * @see MD5 + */ + public Bytes hashMd5() { + return hash(BytesTransformer.MessageDigestTransformer.ALGORITHM_MD5); + } + + /** + * Calculates sha1 on the underlying byte array and returns a byte instance containing the hash. + * This hash algorithm SHOULD be supported by every JVM implementation (see + * Javadoc for MessageDigest) + *

    + * Do not use this algorithm in security relevant applications. + * + * @return sha1 (20 bytes) hash of internal byte array + * @throws IllegalArgumentException if the message digest algorithm can not be found in the security providers + * @see Secure Hash Algorithm 1 + */ + public Bytes hashSha1() { + return hash(BytesTransformer.MessageDigestTransformer.ALGORITHM_SHA_1); + } + /** * Calculates sha256 on the underlying byte array and returns a byte instance containing the hash. * @@ -917,10 +1150,10 @@ public Bytes hash(String algorithm) { /** * Generic transformation of this instance. *

    - * This transformation might be done in-place (ie. without copying the internal array and overwriting its old state), + * This transformation might be done in-place (i.e. without copying the internal array and overwriting its old state), * or on a copy of the internal data, depending on the type (e.g. {@link MutableBytes}) and if the operation can be done - * in-place. Therefore the caller has to ensure that certain side-effects, which occur due to the changing of the internal - * data, do not create bugs in his/her code. Usually immutability is prefered, but when handling many or big byte arrays, + * in-place. Therefore, the caller has to ensure that certain side effects, which occur due to the changing of the internal + * data, do not create bugs in his/her code. Usually immutability is preferred, but when handling many or big byte arrays, * mutability enables drastically better performance. * * @param transformer used to transform this instance @@ -948,8 +1181,7 @@ public boolean validateNotOnlyZeros() { * @return true if all validators return true */ public boolean validate(BytesValidator... bytesValidators) { - Objects.requireNonNull(bytesValidators); - return BytesValidators.and(bytesValidators).validate(internalArray()); + return BytesValidators.and(Objects.requireNonNull(bytesValidators)).validate(internalArray()); } /* ATTRIBUTES ************************************************************************************************/ @@ -966,7 +1198,7 @@ public int length() { /** * The bit length of the underlying byte array. * - * @return bit length + * @return the bit length */ public int lengthBit() { return length() * 8; @@ -994,7 +1226,7 @@ public ByteOrder byteOrder() { /** * Checks if instance is mutable * - * @return true if mutable, ie. transformers will change internal array + * @return true if mutable, i.e. transformers will change internal array */ public boolean isMutable() { return false; @@ -1021,14 +1253,41 @@ public boolean contains(byte target) { /** * Returns the index of the first appearance of the value {@code target} in - * {@code array}. + * {@code array}. Same as calling {@link #indexOf(byte, int)} with fromIndex '0'. * * @param target a primitive {@code byte} value * @return the least index {@code i} for which {@code array[i] == target}, or * {@code -1} if no such index exists. */ public int indexOf(byte target) { - return Util.indexOf(internalArray(), target, 0, length()); + return indexOf(target, 0); + } + + /** + * Returns the index of the first appearance of the value {@code target} in + * {@code array} from given start index 'fromIndex'. + * + * @param target a primitive {@code byte} value + * @param fromIndex search from this index + * @return the least index {@code i} for which {@code array[i] == target}, or + * {@code -1} if no such index exists or fromIndex is gt target length. + */ + public int indexOf(byte target, int fromIndex) { + return indexOf(new byte[]{target}, fromIndex); + } + + /** + * Returns the index of the first appearance of the value {@code target} in + * {@code array} from given start index 'fromIndex' to given end index 'toIndex'. + * + * @param target a primitive {@code byte} value + * @param fromIndex search from this index + * @param toIndex search to this index + * @return the least index {@code i} for which {@code array[i] == target}, or + * {@code -1} if no such index exists or fromIndex is gt target length. + */ + public int indexOf(byte target, int fromIndex, int toIndex) { + return indexOf(new byte[]{target}, fromIndex, toIndex); } /** @@ -1044,7 +1303,55 @@ public int indexOf(byte target) { * {@code -1} if no such index exists. */ public int indexOf(byte[] subArray) { - return Util.indexOf(internalArray(), subArray); + return indexOf(subArray, 0); + } + + /** + * Returns the start position of the first occurrence of the specified {@code + * target} within {@code array} from given start index 'fromIndex', or {@code -1} + * if there is no such occurrence. + *

    + * More formally, returns the lowest index {@code i} such that {@code + * java.util.Arrays.copyOfRange(array, i, i + target.length)} contains exactly + * the same elements as {@code target}. + * + * @param subArray the array to search for as a sub-sequence of {@code array} + * @param fromIndex search from this index + * @return the least index {@code i} for which {@code array[i] == target}, or + * {@code -1} if no such index exists. + */ + public int indexOf(byte[] subArray, int fromIndex) { + return Util.Byte.indexOf(internalArray(), subArray, fromIndex, length()); + } + + /** + * Returns the start position of the first occurrence of the specified {@code + * target} within {@code array} from given start index 'fromIndex' to given end + * index 'toIndex', or {@code -1} if there is no such occurrence. + *

    + * More formally, returns the lowest index {@code i} such that {@code + * java.util.Arrays.copyOfRange(array, i, i + target.length)} contains exactly + * the same elements as {@code target}. + * + * @param subArray the array to search for as a sub-sequence of {@code array} + * @param fromIndex search from this index + * @param toIndex search to this index + * @return the least index {@code i} for which {@code array[i] == target}, or + * {@code -1} if no such index exists. + */ + public int indexOf(byte[] subArray, int fromIndex, int toIndex) { + return Util.Byte.indexOf(internalArray(), subArray, fromIndex, toIndex); + } + + /** + * Checks if the given sub array is equal to the start of given array. That is, sub array must be gt or eq + * to the length of the internal array and internal[i] == subArray[i] for i=0..subArray.length-1 + * + * @param subArray to check against the start of the internal array + * @return true if the start of the internal array is eq to given sub array + */ + public boolean startsWith(byte[] subArray) { + return Util.Byte.indexOf(internalArray(), subArray, 0, 1) == 0; } /** @@ -1056,7 +1363,19 @@ public int indexOf(byte[] subArray) { * or {@code -1} if no such index exists. */ public int lastIndexOf(byte target) { - return Util.lastIndexOf(internalArray(), target, 0, length()); + return Util.Byte.lastIndexOf(internalArray(), target, 0, length()); + } + + /** + * Checks if the given sub array is equal to the end of given array. That is, sub array must be gt or eq + * to the length of the internal array and internal[i] == subArray[i] for i=subArray.length...internal.length + * + * @param subArray to check against the end of the internal array + * @return true if the end of the internal array is eq to given sub array + */ + public boolean endsWith(byte[] subArray) { + int startIndex = length() - subArray.length; + return startIndex >= 0 && Util.Byte.indexOf(internalArray(), subArray, startIndex, startIndex + 1) == startIndex; } /** @@ -1064,12 +1383,16 @@ public int lastIndexOf(byte target) { * 1000 0000 has bitAt(0) == false and bitAt(7) == true. * * @param bitIndex the index of the {@code bit} value. - * @return true if bit at given index is set, false otherwise + * @return true if the bit at given index is set, false otherwise * @throws IndexOutOfBoundsException if the {@code bitIndex} argument is negative or not less than the length of this array in bits. */ public boolean bitAt(int bitIndex) { - Util.checkIndexBounds(lengthBit(), bitIndex, 1, "bit"); - return ((byteAt(length() - 1 - (bitIndex / 8)) >>> bitIndex % 8) & 1) != 0; + Util.Validation.checkIndexBounds(lengthBit(), bitIndex, 1, "bit"); + if (byteOrder == ByteOrder.BIG_ENDIAN) { + return ((byteAt(length() - 1 - (bitIndex / 8)) >>> bitIndex % 8) & 1) != 0; + } else { + return ((byteAt(bitIndex / 8) >>> bitIndex % 8) & 1) != 0; + } } /** @@ -1082,7 +1405,7 @@ public boolean bitAt(int bitIndex) { * @throws IndexOutOfBoundsException if the {@code index} argument is negative or not less than the length of this array. */ public byte byteAt(int index) { - Util.checkIndexBounds(length(), index, 1, "byte"); + Util.Validation.checkIndexBounds(length(), index, 1, "byte"); return internalArray()[index]; } @@ -1096,7 +1419,7 @@ public byte byteAt(int index) { * @throws IndexOutOfBoundsException if the {@code index} argument is negative or not less than the length of this array. */ public int unsignedByteAt(int index) { - Util.checkIndexBounds(length(), index, 1, "unsigned byte"); + Util.Validation.checkIndexBounds(length(), index, 1, "unsigned byte"); return 0xff & internalArray()[index]; } @@ -1109,8 +1432,8 @@ public int unsignedByteAt(int index) { * @throws IndexOutOfBoundsException if the {@code index} argument is negative or length is greater than index - 2 */ public char charAt(int index) { - Util.checkIndexBounds(length(), index, 2, "char"); - return ((ByteBuffer) ByteBuffer.wrap(internalArray()).order(byteOrder).position(index)).getChar(); + Util.Validation.checkIndexBounds(length(), index, 2, "char"); + return internalBuffer().position(index).getChar(); } /** @@ -1122,8 +1445,8 @@ public char charAt(int index) { * @throws IndexOutOfBoundsException if the {@code index} argument is negative or length is greater than index - 2 */ public short shortAt(int index) { - Util.checkIndexBounds(length(), index, 2, "short"); - return ((ByteBuffer) ByteBuffer.wrap(internalArray()).order(byteOrder).position(index)).getShort(); + Util.Validation.checkIndexBounds(length(), index, 2, "short"); + return internalBuffer().position(index).getShort(); } /** @@ -1135,8 +1458,8 @@ public short shortAt(int index) { * @throws IndexOutOfBoundsException if the {@code int} argument is negative or length is greater than index - 4 */ public int intAt(int index) { - Util.checkIndexBounds(length(), index, 4, "int"); - return ((ByteBuffer) ByteBuffer.wrap(internalArray()).order(byteOrder).position(index)).getInt(); + Util.Validation.checkIndexBounds(length(), index, 4, "int"); + return internalBuffer().position(index).getInt(); } /** @@ -1148,8 +1471,8 @@ public int intAt(int index) { * @throws IndexOutOfBoundsException if the {@code long} argument is negative or length is greater than index - 8 */ public long longAt(int index) { - Util.checkIndexBounds(length(), index, 8, "long"); - return ((ByteBuffer) ByteBuffer.wrap(internalArray()).order(byteOrder).position(index)).getLong(); + Util.Validation.checkIndexBounds(length(), index, 8, "long"); + return internalBuffer().position(index).getLong(); } /** @@ -1160,13 +1483,25 @@ public long longAt(int index) { * @return the count of given target in the byte array */ public int count(byte target) { - int count = 0; - for (byte b : internalArray()) { - if (b == target) { - count++; - } - } - return count; + return Util.Byte.countByte(internalArray(), target); + } + + /** + * Traverses the internal byte array counts the occurrences of given pattern array. + * This has a time complexity of O(n). + *

    + * Example: + *

      + *
    • Internal Array: [0, 1, 2, 0, 1, 0]
    • + *
    • Pattern Array: [0, 1]
    • + *
    • Count: 2
    • + *
    + * + * @param pattern byte array to count + * @return the count of given target in the byte array + */ + public int count(byte[] pattern) { + return Util.Byte.countByteArray(internalArray(), pattern); } /** @@ -1176,13 +1511,13 @@ public int count(byte target) { * variables. Specifically, assuming for simplicity that each of the microscopic configurations is equally probable, * the entropy of the system is the natural logarithm of that number of configurations, multiplied by the Boltzmann constant kB. *

    - * This implementation requires O(n) time and space complexity. + * This implementation requires O(n) time and O(1) space complexity. * * @return entropy value; higher is more entropy (simply: more different values) * @see Entropy */ public double entropy() { - return new Util.Entropy<>(toList()).entropy(); + return Util.Byte.entropy(internalArray()); } /* CONVERTERS POSSIBLY REUSING THE INTERNAL ARRAY ***************************************************************/ @@ -1199,7 +1534,7 @@ public Bytes duplicate() { /** * Set the byte order or endianness of this instance. Default in Java is {@link ByteOrder#BIG_ENDIAN}. *

    - * This option is important for all encoding and conversation methods. + * This option is important for all encoding and conversion methods. * * @param byteOrder new byteOrder * @return a new instance with the same underlying array and new order, or "this" if order is the same @@ -1214,7 +1549,7 @@ public Bytes byteOrder(ByteOrder byteOrder) { /** * Returns a new read-only byte instance. Read-only means, that there is no direct access to the underlying byte - * array and all transformers will create a copy (ie. immutable) + * array and all transformers will create a copy. * * @return a new instance if not already readonly, or "this" otherwise */ @@ -1270,7 +1605,7 @@ public InputStream inputStream() { /** * The reference of te internal byte-array. This call requires no conversation or additional memory allocation. *

    - * Modifications to this bytes's content will cause the returned + * Modifications to these byte's content will cause the returned * array's content to be modified, and vice versa. * * @return the direct reference of the internal byte array @@ -1296,7 +1631,7 @@ byte[] internalArray() { * @see Binary number */ public String encodeBinary() { - return encode(new BinaryToTextEncoding.BaseRadix(2)); + return encodeRadix(2); } /** @@ -1308,7 +1643,7 @@ public String encodeBinary() { * @see Octal */ public String encodeOctal() { - return encode(new BinaryToTextEncoding.BaseRadix(8)); + return encodeRadix(8); } /** @@ -1320,7 +1655,27 @@ public String encodeOctal() { * @see Decimal */ public String encodeDec() { - return encode(new BinaryToTextEncoding.BaseRadix(10)); + return encodeRadix(10); + } + + /** + * Encodes the internal array in given radix representation (e.g. 2 = binary, 10 = decimal, 16 = hex). + *

    + * This is usually a number encoding, not a data encoding (i.e. leading zeros are not preserved), but this implementation + * tries to preserve the leading zeros, to keep the in/output byte length size the same. To preserve the length padding + * would be required, but is not supported in this implementation. + *

    + * But still full disclaimer: + *

    + * This is NOT recommended for data encoding, only for number encoding + *

    + * See Radix Economy and {@link BigInteger#toString(int)}. + * + * @param radix of the String representation (supported are 2-36) + * @return string in given radix representation + */ + public String encodeRadix(int radix) { + return encode(new BinaryToTextEncoding.BaseRadixNumber(radix)); } /** @@ -1348,6 +1703,21 @@ public String encodeHex(boolean upperCase) { } /** + * Base32 RFC4648 string representation of the internal byte array (not Base32 hex alphabet extension) + *

    + * Example: MZXW6YQ= + *

    + * See RFC 4648 + * + * @return base32 string + */ + public String encodeBase32() { + return encode(new BaseEncoding(BaseEncoding.BASE32_RFC4848, BaseEncoding.BASE32_RFC4848_PADDING)); + } + + /** + * DO NOT USE AS DATA ENCODING, ONLY FOR NUMBERS! + *

    * Base36 (aka Hexatrigesimal) representation. The choice of 36 is convenient in that the digits can be * represented using the Arabic numerals 0–9 and the Latin letters A–Z. This encoding has a space efficiency of 64.6%. *

    @@ -1355,21 +1725,55 @@ public String encodeHex(boolean upperCase) { * * @return base36 string * @see Base36 + * @deprecated use {@link #encodeRadix(int)} instead; will be removed in v1.0+ */ + @Deprecated public String encodeBase36() { - return encode(new BinaryToTextEncoding.BaseRadix(36)); + return encodeRadix(36); } /** * Base64 representation with padding. This is *NOT* the url safe variation. This encoding has a space efficiency of 75%. *

    + * This encoding is RFC 4648 compatible. + *

    * Example: SpT9/x6v7Q== * * @return base64 string * @see Base64 */ public String encodeBase64() { - return encode(new BinaryToTextEncoding.Base64Encoding()); + return encodeBase64(false, true); + } + + /** + * Base64 representation with padding. This is the url safe variation substitution '+' and '/' with '-' and '_' + * respectively. This encoding has a space efficiency of 75%. + *

    + * This encoding is RFC 4648 compatible. + *

    + * Example: SpT9_x6v7Q== + * + * @return base64 url safe string + * @see Base64 + */ + public String encodeBase64Url() { + return encodeBase64(true, true); + } + + /** + * Base64 representation with either padding or without and with or without URL and filename safe alphabet. + * This encoding is RFC 4648 compatible. + *

    + * Example: SpT9/x6v7Q== + * + * @param urlSafe if true will substitute '+' and '/' with '-' and '_' + * @param withPadding if true will add padding the next full byte with '=' + * @return base64 url safe string + * @see Base64 + */ + public String encodeBase64(boolean urlSafe, boolean withPadding) { + return encode(new BinaryToTextEncoding.Base64Encoding(urlSafe, withPadding)); } /** @@ -1389,8 +1793,31 @@ public String encodeUtf8() { * @return encoded string */ public String encodeCharset(Charset charset) { - Objects.requireNonNull(charset, "given charset must not be null"); - return new String(internalArray(), charset); + return new String(internalArray(), Objects.requireNonNull(charset, "given charset must not be null")); + } + + /** + * UTF-8 representation of this byte array as byte array + *

    + * Similar to encodeUtf8().getBytes(StandardCharsets.UTF_8). + * + * @return utf-8 encoded byte array + * @see UTF-8 + */ + public byte[] encodeUtf8ToBytes() { + return encodeCharsetToBytes(StandardCharsets.UTF_8); + } + + /** + * Byte array representation with given charset encoding. + *

    + * Similar to encodeCharset(charset).getBytes(charset). + * + * @param charset the charset the return will be encoded + * @return encoded byte array + */ + public byte[] encodeCharsetToBytes(Charset charset) { + return encodeCharset(charset).getBytes(charset); } /** @@ -1412,26 +1839,19 @@ public String encode(BinaryToTextEncoding.Encoder encoder) { * @return copy of internal array as list */ public List toList() { - return Util.toList(internalArray()); - } - - /** - * @deprecated renamed API, use {@link #toBoxedArray()} instead - will be removed in v1.0+ - * @return see {@link #toBoxedArray()} - */ - @Deprecated - public Byte[] toObjectArray() { - return toBoxedArray(); + return Util.Converter.toList(internalArray()); } /** * Returns a copy of the internal byte-array as boxed primitive array. * This requires a time and space complexity of O(n). + *

    + * Note: this method was previously called toObjectArray() * * @return copy of internal array as object array */ public Byte[] toBoxedArray() { - return Util.toBoxedArray(internalArray()); + return Util.Converter.toBoxedArray(internalArray()); } /** @@ -1447,7 +1867,7 @@ public BitSet toBitSet() { * The internal byte array wrapped in a {@link BigInteger} instance. *

    * If the internal byte order is {@link ByteOrder#LITTLE_ENDIAN}, a copy of the internal - * array will be reversed and used as backing array with the big integer. Otherwise the internal + * array will be reversed and used as backing array with the big integer. Otherwise, the internal * array will be used directly. * * @return big integer @@ -1460,6 +1880,20 @@ public BigInteger toBigInteger() { } } + /** + * Creates a {@link UUID} instance of the internal byte array. This requires the internal array to be exactly 16 bytes. Takes the first + * 8 byte as mostSigBits and the last 8 byte as leastSigBits. There is no validation of version/type, just passes the raw bytes + * to a {@link UUID} constructor. + * + * @return newly created UUID + * @throws IllegalArgumentException if byte array has length not equal to 16 + */ + public UUID toUUID() { + Util.Validation.checkExactLength(length(), 16, "UUID"); + ByteBuffer byteBuffer = buffer(); + return new UUID(byteBuffer.getLong(), byteBuffer.getLong()); + } + /** * If the underlying byte array is exactly 1 byte / 8 bit long, returns signed two-complement * representation for a Java byte value. @@ -1467,11 +1901,11 @@ public BigInteger toBigInteger() { * If you just want to get the first element as {@code byte}, see {@link #byteAt(int)}, using index zero. * * @return the byte representation - * @throws IllegalStateException if byte array has length not equal to 1 + * @throws IllegalArgumentException if byte array has length not equal to 1 * @see Primitive Types */ public byte toByte() { - Util.checkExactLength(length(), 1, "byte"); + Util.Validation.checkExactLength(length(), 1, "byte"); return internalArray()[0]; } @@ -1482,11 +1916,11 @@ public byte toByte() { * If you just want to get the first element as {@code byte}, see {@link #byteAt(int)}, using index zero. * * @return the unsigned byte representation wrapped in an int - * @throws IllegalStateException if byte array has length not equal to 1 + * @throws IllegalArgumentException if byte array has length not equal to 1 * @see Primitive Types */ public int toUnsignedByte() { - Util.checkExactLength(length(), 1, "unsigned byte"); + Util.Validation.checkExactLength(length(), 1, "unsigned byte"); return unsignedByteAt(0); } @@ -1497,11 +1931,11 @@ public int toUnsignedByte() { * If you just want to get the first 2 bytes as {@code char}, see {@link #charAt(int)} using index zero. * * @return the int representation - * @throws IllegalStateException if byte array has length not equal to 2 + * @throws IllegalArgumentException if byte array has length not equal to 2 * @see Primitive Types */ public char toChar() { - Util.checkExactLength(length(), 2, "char"); + Util.Validation.checkExactLength(length(), 2, "char"); return charAt(0); } @@ -1512,11 +1946,11 @@ public char toChar() { * If you just want to get the first 2 bytes as {@code short}, see {@link #shortAt(int)} using index zero. * * @return the int representation - * @throws IllegalStateException if byte array has length not equal to 2 + * @throws IllegalArgumentException if byte array has length not equal to 2 * @see Primitive Types */ public short toShort() { - Util.checkExactLength(length(), 2, "short"); + Util.Validation.checkExactLength(length(), 2, "short"); return shortAt(0); } @@ -1527,14 +1961,33 @@ public short toShort() { * If you just want to get the first 4 bytes as {@code int}, see {@link #intAt(int)} using index zero. * * @return the int representation - * @throws IllegalStateException if byte array has length not equal to 4 + * @throws IllegalArgumentException if byte array has length not equal to 4 * @see Primitive Types */ public int toInt() { - Util.checkExactLength(length(), 4, "int"); + Util.Validation.checkExactLength(length(), 4, "int"); return intAt(0); } + /** + * Converts the internal byte array to an int array, that is, every 4 bytes will be packed into a single int. + *

    + * E.g. 4 bytes will be packed to a length 1 int array: + *

    +     *  [b1, b2, b3, b4] = [int1]
    +     * 
    + *

    + * This conversion respects the internal byte order. Will only work if all bytes can be directly mapped to int, + * which means the byte array length must be dividable by 4 without rest. + * + * @return new int[] instance representing this byte array + * @throws IllegalArgumentException if internal byte length mod 4 != 0 + */ + public int[] toIntArray() { + Util.Validation.checkModLength(length(), 4, "creating an int array"); + return Util.Converter.toIntArray(internalArray(), byteOrder); + } + /** * If the underlying byte array is exactly 8 byte / 64 bit long, return signed two-complement * representation for a Java signed long integer value. The output is dependent on the set {@link #byteOrder()}. @@ -1542,40 +1995,137 @@ public int toInt() { * If you just want to get the first 4 bytes as {@code long}, see {@link #longAt(int)} using index zero. * * @return the long representation - * @throws IllegalStateException if byte array has length not equal to 8 + * @throws IllegalArgumentException if byte array has length not equal to 8 * @see Primitive Types */ public long toLong() { - Util.checkExactLength(length(), 8, "long"); + Util.Validation.checkExactLength(length(), 8, "long"); return longAt(0); } + /** + * Converts the internal byte array to a long array, that is, every 8 bytes will be packed into a single long. + *

    + * E.g. 8 bytes will be packed to a length 1 long array: + *

    +     *  [b1, b2, b3, b4, b5, b6, b7, b8] = [int1]
    +     * 
    + *

    + * This conversion respects the internal byte order. Will only work if all bytes can be directly mapped to long, + * which means the byte array length must be dividable by 8 without rest. + * + * @return new long[] instance representing this byte array + * @throws IllegalArgumentException if internal byte length mod 8 != 0 + */ + public long[] toLongArray() { + Util.Validation.checkModLength(length(), 8, "creating an long array"); + return Util.Converter.toLongArray(internalArray(), byteOrder); + } + /** * If the underlying byte array is exactly 4 byte / 32 bit long, return the * representation for a Java float value. The output is dependent on the set {@link #byteOrder()}. * * @return the float representation - * @throws IllegalStateException if byte array has length not equal to 4 + * @throws IllegalArgumentException if byte array has length not equal to 4 * @see Primitive Types */ public float toFloat() { - Util.checkExactLength(length(), 4, "float"); + Util.Validation.checkExactLength(length(), 4, "float"); return internalBuffer().getFloat(); } + /** + * Converts the internal byte array to a float array, that is, every 4 bytes will be packed into a single float. + *

    + * E.g. 4 bytes will be packed to a length 1 float array: + *

    +     *  [b1, b2, b3, b4] = [float1]
    +     * 
    + *

    + * This conversion respects the internal byte order. Will only work if all bytes can be directly mapped to float, + * which means the byte array length must be dividable by 4 without rest. + * + * @return new float[] instance representing this byte array + * @throws IllegalArgumentException if internal byte length mod 4 != 0 + */ + public float[] toFloatArray() { + Util.Validation.checkModLength(length(), 4, "creating an float array"); + return Util.Converter.toFloatArray(internalArray(), byteOrder); + } + /** * If the underlying byte array is exactly 8 byte / 64 bit long, return the * representation for a Java double value. The output is dependent on the set {@link #byteOrder()}. * * @return the double representation - * @throws IllegalStateException if byte array has length not equal to 8 + * @throws IllegalArgumentException if byte array has length not equal to 8 * @see Primitive Types */ public double toDouble() { - Util.checkExactLength(length(), 8, "double"); + Util.Validation.checkExactLength(length(), 8, "double"); return internalBuffer().getDouble(); } + /** + * Converts the internal byte array to a double array, that is, every 8 bytes will be packed into a single double. + *

    + * E.g. 8 bytes will be packed to a length 1 double array: + *

    +     *  [b1, b2, b3, b4, b5, b6, b7, b8] = [double1]
    +     * 
    + *

    + * This conversion respects the internal byte order. Will only work if all bytes can be directly mapped to double, + * which means the byte array length must be dividable by 8 without rest. + * + * @return new double[] instance representing this byte array + * @throws IllegalArgumentException if internal byte length mod 8 != 0 + */ + public double[] toDoubleArray() { + Util.Validation.checkModLength(length(), 8, "creating an double array"); + return Util.Converter.toDoubleArray(internalArray(), byteOrder); + } + + /** + * Converts the internal byte array to a short array, that is, every 2 bytes will be packed into a single short. + *

    + * E.g. 2 bytes will be packed to a length 1 short array: + *

    +     *  [b1, b2] = [short1]
    +     * 
    + *

    + * This conversion respects the internal byte order. Will only work if all bytes can be directly mapped to short, + * which means the byte array length must be dividable by 2 without rest. + * + * @return new short[] instance representing this byte array + * @throws IllegalArgumentException if internal byte length mod 2 != 0 + */ + public short[] toShortArray() { + Util.Validation.checkModLength(length(), 2, "creating a short array"); + return Util.Converter.toShortArray(internalArray(), byteOrder); + } + + /** + * Decodes the internal byte array to UTF-8 char array. + * This implementation will not internally create a {@link String}. + * + * @return char array + */ + public char[] toCharArray() { + return toCharArray(StandardCharsets.UTF_8); + } + + /** + * Decodes the internal byte array with given charset to a char array. + * This implementation will not internally create a {@link String}. + * + * @param charset to use for decoding + * @return char array + */ + public char[] toCharArray(Charset charset) { + return Util.Converter.byteToCharArray(internalArray(), charset, byteOrder); + } + /** * Compares this bytes instance to another. *

    @@ -1585,7 +2135,7 @@ public double toDouble() { * Pairs of {@code byte} elements are compared as if by invoking * {@link Byte#compare(byte, byte)}. *

    - * Uses {@link ByteBuffer#compareTo(Object)} internally. + * Uses {@link ByteBuffer#compareTo(ByteBuffer)} internally. * * @return A negative integer, zero, or a positive integer as this buffer * is less than, equal to, or greater than the given buffer @@ -1609,7 +2159,7 @@ public boolean equals(Object o) { Bytes bytes = (Bytes) o; if (!Arrays.equals(byteArray, bytes.byteArray)) return false; - return byteOrder != null ? byteOrder.equals(bytes.byteOrder) : bytes.byteOrder == null; + return Objects.equals(byteOrder, bytes.byteOrder); } /** @@ -1626,14 +2176,14 @@ public boolean equals(byte[] anotherArray) { * Compares the inner array with given array. The comparison is done in constant time, therefore * will not break on the first mismatch. This method is useful to prevent some side-channel attacks, * but is slower on average. - * - * This implementation uses the algorithm suggested in https://codahale.com/a-lesson-in-timing-attacks/ + *

    + * This implementation uses the algorithm suggested in a-lesson-in-timing-attacks * * @param anotherArray to compare with * @return true if {@link Arrays#equals(byte[], byte[])} returns true on given and internal array */ public boolean equalsConstantTime(byte[] anotherArray) { - return anotherArray != null && Util.constantTimeEquals(internalArray(), anotherArray); + return anotherArray != null && Util.Byte.constantTimeEquals(internalArray(), anotherArray); } /** @@ -1644,7 +2194,7 @@ public boolean equalsConstantTime(byte[] anotherArray) { * @return true if both array have same length and every byte element is the same */ public boolean equals(Byte[] anotherArray) { - return Util.equals(internalArray(), anotherArray); + return Util.Obj.equals(internalArray(), anotherArray); } /** @@ -1655,7 +2205,7 @@ public boolean equals(Byte[] anotherArray) { * @return true if both array have same length and every byte element is the same */ public boolean equals(ByteBuffer buffer) { - return buffer != null && byteOrder == buffer.order() && ByteBuffer.wrap(internalArray()).order(byteOrder).equals(buffer); + return buffer != null && byteOrder == buffer.order() && internalBuffer().equals(buffer); } /** @@ -1670,20 +2220,18 @@ public boolean equalsContent(Bytes other) { @Override public int hashCode() { - int result = Arrays.hashCode(byteArray); - result = 31 * result + (byteOrder != null ? byteOrder.hashCode() : 0); - return result; + return Util.Obj.hashCode(internalArray(), byteOrder()); } /** - * A memory safe toString implementation, which only shows the byte length and at most 8 bytes preview in hex + * A constant length output toString() implementation, which only shows the byte length and at most 8 bytes preview in hex * representation. * * @return string representation */ @Override public String toString() { - return Util.toString(this); + return Util.Obj.toString(this); } @Override diff --git a/src/main/java/at/favre/lib/bytes/BytesTransformer.java b/src/main/java/at/favre/lib/bytes/BytesTransformer.java index 41111c2..efdd12c 100644 --- a/src/main/java/at/favre/lib/bytes/BytesTransformer.java +++ b/src/main/java/at/favre/lib/bytes/BytesTransformer.java @@ -21,6 +21,7 @@ package at.favre.lib.bytes; +import java.nio.ByteOrder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Objects; @@ -60,10 +61,8 @@ public enum Mode { private final Mode mode; BitWiseOperatorTransformer(byte[] secondArray, Mode mode) { - Objects.requireNonNull(secondArray, "the second byte array must not be null"); - Objects.requireNonNull(mode, "passed bitwise mode must not be null"); - this.secondArray = secondArray; - this.mode = mode; + this.secondArray = Objects.requireNonNull(secondArray, "the second byte array must not be null"); + this.mode = Objects.requireNonNull(mode, "passed bitwise mode must not be null"); } @Override @@ -133,12 +132,12 @@ public enum Type { private final int shiftCount; private final Type type; + private final ByteOrder byteOrder; - ShiftTransformer(int shiftCount, Type type) { - Objects.requireNonNull(type, "passed shift type must not be null"); - + ShiftTransformer(int shiftCount, Type type, ByteOrder byteOrder) { this.shiftCount = shiftCount; - this.type = type; + this.type = Objects.requireNonNull(type, "passed shift type must not be null"); + this.byteOrder = Objects.requireNonNull(byteOrder, "passed byteOrder type must not be null"); } @Override @@ -147,10 +146,10 @@ public byte[] transform(byte[] currentArray, boolean inPlace) { switch (type) { case RIGHT_SHIFT: - return Util.shiftRight(out, shiftCount); + return Util.Byte.shiftRight(out, shiftCount, byteOrder); default: case LEFT_SHIFT: - return Util.shiftLeft(out, shiftCount); + return Util.Byte.shiftLeft(out, shiftCount, byteOrder); } } @@ -169,13 +168,12 @@ final class ConcatTransformer implements BytesTransformer { private final byte[] secondArray; ConcatTransformer(byte[] secondArrays) { - Objects.requireNonNull(secondArrays, "the second byte array must not be null"); - this.secondArray = secondArrays; + this.secondArray = Objects.requireNonNull(secondArrays, "the second byte array must not be null"); } @Override public byte[] transform(byte[] currentArray, boolean inPlace) { - return Util.concat(currentArray, secondArray); + return Util.Byte.concat(currentArray, secondArray); } @Override @@ -191,7 +189,7 @@ final class ReverseTransformer implements BytesTransformer { @Override public byte[] transform(byte[] currentArray, boolean inPlace) { byte[] out = inPlace ? currentArray : Bytes.from(currentArray).array(); - Util.reverse(out, 0, out.length); + Util.Byte.reverse(out, 0, out.length); return out; } @@ -233,7 +231,7 @@ public boolean supportInPlaceTransformation() { * contain identical values. For any indices that are valid in the * copy but not the original, the copy will contain {@code (byte)0}. *

    - * If if the internal array will be grown, zero bytes will be added on the left, + * If the internal array will be increased, zero bytes will be added on the left, * keeping the value the same. */ final class ResizeTransformer implements BytesTransformer { @@ -267,10 +265,12 @@ public byte[] transform(byte[] currentArray, boolean inPlace) { byte[] resizedArray = new byte[newSize]; if (mode == Mode.RESIZE_KEEP_FROM_MAX_LENGTH) { + int max = Math.max(0, Math.abs(newSize - currentArray.length)); + if (newSize > currentArray.length) { - System.arraycopy(currentArray, 0, resizedArray, Math.max(0, Math.abs(newSize - currentArray.length)), Math.min(newSize, currentArray.length)); + System.arraycopy(currentArray, 0, resizedArray, max, currentArray.length); } else { - System.arraycopy(currentArray, Math.max(0, Math.abs(newSize - currentArray.length)), resizedArray, Math.min(0, Math.abs(newSize - currentArray.length)), Math.min(newSize, currentArray.length)); + System.arraycopy(currentArray, max, resizedArray, 0, newSize); } } else { System.arraycopy(currentArray, 0, resizedArray, 0, Math.min(currentArray.length, resizedArray.length)); @@ -326,6 +326,8 @@ public boolean supportInPlaceTransformation() { * Converts to hash */ class MessageDigestTransformer implements BytesTransformer { + static final String ALGORITHM_MD5 = "MD5"; + static final String ALGORITHM_SHA_1 = "SHA-1"; static final String ALGORITHM_SHA_256 = "SHA-256"; private final MessageDigest messageDigest; diff --git a/src/main/java/at/favre/lib/bytes/BytesTransformers.java b/src/main/java/at/favre/lib/bytes/BytesTransformers.java index 656fd19..c1914c0 100644 --- a/src/main/java/at/favre/lib/bytes/BytesTransformers.java +++ b/src/main/java/at/favre/lib/bytes/BytesTransformers.java @@ -1,10 +1,14 @@ package at.favre.lib.bytes; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.security.SecureRandom; -import java.util.*; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Objects; +import java.util.Random; import java.util.zip.CRC32; import java.util.zip.Checksum; import java.util.zip.GZIPInputStream; @@ -13,6 +17,7 @@ /** * Collection of additional {@link BytesTransformer} for more specific use cases */ +@SuppressWarnings("WeakerAccess") public final class BytesTransformers { private BytesTransformers() { @@ -39,7 +44,8 @@ public static BytesTransformer shuffle(Random random) { } /** - * Create a {@link BytesTransformer} which sorts the internal byte array with it's natural ordering. + * Create a {@link BytesTransformer} which sorts the internal byte array with its natural ordering treating + * each byte as signed byte (-128...127). Using inplace sorting, this can be reasonable fast. * * @return transformer */ @@ -47,8 +53,24 @@ public static BytesTransformer sort() { return new SortTransformer(); } + /** + * Create a {@link BytesTransformer} which sorts the internal byte array with its natural ordering treating + * each byte as unsigned byte (0...255). That is, the byte string {@code ff} sorts after {@code 00}. + *

    + * Note: this requires 2 copies of the internal array and a lot of unboxing due to + * the fact that no primitives are not allowed as generic type arguments - so only use on small arrays. + * + * @return transformer + */ + public static BytesTransformer sortUnsigned() { + return new SortTransformer(new SortTransformer.UnsignedByteComparator()); + } + /** * Create a {@link BytesTransformer} which sorts the internal byte array according to given comparator. + *

    + * Note: this requires 2 copies of the internal array and a lot of unboxing due to + * the fact that no primitives are not allowed as generic type arguments - so only use on small arrays. * * @param comparator to sort the bytes * @return transformer @@ -110,6 +132,37 @@ public static BytesTransformer decompressGzip() { return new GzipCompressor(false); } + /** + * Create a {@link BytesTransformer} which returns the HMAC-SHA1 with given key, of the target byte array + * + * @param key to use for HMAC + * @return hmac + */ + public static BytesTransformer hmacSha1(byte[] key) { + return new HmacTransformer(key, HmacTransformer.HMAC_SHA1); + } + + /** + * Create a {@link BytesTransformer} which returns the HMAC-SHA256 with given key, of the target byte array + * + * @param key to use for HMAC + * @return hmac + */ + public static BytesTransformer hmacSha256(byte[] key) { + return new HmacTransformer(key, HmacTransformer.HMAC_SHA256); + } + + /** + * Create a {@link BytesTransformer} which returns the HMAC with given key, algorithm of the target byte array + * + * @param key to use for HMAC + * @param algorithmName e.g. 'HmacSHA256' - check if the algorithm is supported on your JVM/runtime + * @return hmac (length depends on algorithm) + */ + public static BytesTransformer hmac(byte[] key, String algorithmName) { + return new HmacTransformer(key, algorithmName); + } + /** * Shuffles the internal byte array */ @@ -124,7 +177,7 @@ public static final class ShuffleTransformer implements BytesTransformer { @Override public byte[] transform(byte[] currentArray, boolean inPlace) { byte[] out = inPlace ? currentArray : Bytes.from(currentArray).array(); - Util.shuffle(out, random); + Util.Byte.shuffle(out, random); return out; } @@ -132,6 +185,7 @@ public byte[] transform(byte[] currentArray, boolean inPlace) { public boolean supportInPlaceTransformation() { return true; } + } /** @@ -156,9 +210,9 @@ public byte[] transform(byte[] currentArray, boolean inPlace) { return out; } else { //no in-place implementation with comparator - List list = Bytes.wrap(currentArray).toList(); - Collections.sort(list, comparator); - return Bytes.from(list).array(); + Byte[] boxedArray = Bytes.wrap(currentArray).toBoxedArray(); + Arrays.sort(boxedArray, comparator); + return Bytes.from(boxedArray).array(); } } @@ -166,33 +220,33 @@ public byte[] transform(byte[] currentArray, boolean inPlace) { public boolean supportInPlaceTransformation() { return comparator == null; } + + /** + * Converting each byte into unsigned version and comparing it (0...255) vs (-128..127) + */ + static final class UnsignedByteComparator implements Comparator { + @Override + public int compare(Byte o1, Byte o2) { + int byteA = o1 & 0xff; + int byteB = o2 & 0xff; + if (byteA == byteB) return 0; + return byteA < byteB ? -1 : 1; + } + } + } /** * Adds or converts to arbitrary checksum */ public static final class ChecksumTransformer implements BytesTransformer { - /** - * Definitions of the mode - */ - public enum Mode { - /** - * Appends checksum to given byte array - */ - APPEND, - /** - * Transforms byte array and returns only checksum - */ - TRANSFORM - } - private final Checksum checksum; private final Mode mode; private final int checksumLengthByte; ChecksumTransformer(Checksum checksum, Mode mode, int checksumLengthByte) { if (checksumLengthByte <= 0 || checksumLengthByte > 8) - throw new IllegalArgumentException("checksumlength must be between 1 and 8 bytes"); + throw new IllegalArgumentException("checksum length must be between 1 and 8 bytes"); Objects.requireNonNull(checksum, "checksum instance must not be null"); this.checksum = checksum; @@ -216,6 +270,20 @@ public byte[] transform(byte[] currentArray, boolean inPlace) { public boolean supportInPlaceTransformation() { return false; } + + /** + * Definitions of the mode + */ + public enum Mode { + /** + * Appends checksum to given byte array + */ + APPEND, + /** + * Transforms byte array and returns only checksum + */ + TRANSFORM + } } /** @@ -234,64 +302,69 @@ public byte[] transform(byte[] currentArray, boolean inPlace) { } private byte[] decompress(byte[] compressedContent) { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - GZIPInputStream gzipInputStream = null; - byte[] returnBuffer; - try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(Math.max(32, compressedContent.length / 2)); + + try (GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedContent))) { int len; byte[] buffer = new byte[4 * 1024]; - gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedContent)); while ((len = gzipInputStream.read(buffer)) > 0) { bos.write(buffer, 0, len); } - gzipInputStream.close(); - returnBuffer = bos.toByteArray(); - bos.close(); - return returnBuffer; + return bos.toByteArray(); } catch (Exception e) { throw new IllegalStateException("could not decompress gzip", e); - } finally { - try { - bos.close(); - } catch (IOException ignore) { - } - - if (gzipInputStream != null) { - try { - gzipInputStream.close(); - } catch (IOException ignore) { - } - } } } private byte[] compress(byte[] content) { ByteArrayOutputStream bos = new ByteArrayOutputStream(content.length); - GZIPOutputStream gzipOutputStream = null; - byte[] returnBuffer; - try { - gzipOutputStream = new GZIPOutputStream(bos); + + try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(bos)) { gzipOutputStream.write(content); - gzipOutputStream.close(); - returnBuffer = bos.toByteArray(); - bos.close(); - return returnBuffer; } catch (Exception e) { throw new IllegalStateException("could not compress gzip", e); - } finally { - try { - bos.close(); - } catch (IOException ignore) { - } + } - if (gzipOutputStream != null) { - try { - gzipOutputStream.close(); - } catch (IOException ignore) { - } - } + return bos.toByteArray(); + } + + @Override + public boolean supportInPlaceTransformation() { + return false; + } + } + + /** + * HMAC transformer + */ + public static final class HmacTransformer implements BytesTransformer { + static final String HMAC_SHA1 = "HmacSHA1"; + static final String HMAC_SHA256 = "HmacSHA256"; + + private final byte[] secretKey; + private final String macAlgorithmName; + + /** + * Create a new hmac transformer + * + * @param secretKey to use as key + * @param macAlgorithmName hash algorithm e.g. 'HmacSHA256' + */ + HmacTransformer(byte[] secretKey, String macAlgorithmName) { + this.macAlgorithmName = macAlgorithmName; + this.secretKey = secretKey; + } + + @Override + public byte[] transform(byte[] currentArray, boolean inPlace) { + try { + Mac mac = Mac.getInstance(macAlgorithmName); + mac.init(new SecretKeySpec(secretKey, macAlgorithmName)); + return mac.doFinal(currentArray); + } catch (Exception e) { + throw new IllegalArgumentException(e); } } diff --git a/src/main/java/at/favre/lib/bytes/BytesValidators.java b/src/main/java/at/favre/lib/bytes/BytesValidators.java index 479dd18..0d0f30f 100644 --- a/src/main/java/at/favre/lib/bytes/BytesValidators.java +++ b/src/main/java/at/favre/lib/bytes/BytesValidators.java @@ -27,6 +27,7 @@ /** * Util and easy access for {@link BytesValidators} */ +@SuppressWarnings("WeakerAccess") public final class BytesValidators { private BytesValidators() { @@ -36,7 +37,7 @@ private BytesValidators() { * Checks the length of a byte array * * @param byteLength to check against - * @return true if longer or equal to given value + * @return validator that returns true if longer or equal to given value */ public static BytesValidator atLeast(int byteLength) { return new BytesValidator.Length(byteLength, BytesValidator.Length.Mode.GREATER_OR_EQ_THAN); @@ -46,7 +47,7 @@ public static BytesValidator atLeast(int byteLength) { * Checks the length of a byte array * * @param byteLength to check against - * @return true if smaller or equal to given value + * @return validator that returns true if smaller or equal to given value */ public static BytesValidator atMost(int byteLength) { return new BytesValidator.Length(byteLength, BytesValidator.Length.Mode.SMALLER_OR_EQ_THAN); @@ -56,7 +57,7 @@ public static BytesValidator atMost(int byteLength) { * Checks the length of a byte array * * @param byteLength to check against - * @return true if equal to given value + * @return validator that returns true if equal to given value */ public static BytesValidator exactLength(int byteLength) { return new BytesValidator.Length(byteLength, BytesValidator.Length.Mode.EXACT); @@ -66,7 +67,7 @@ public static BytesValidator exactLength(int byteLength) { * Checks individual byte content * * @param refByte to check against - * @return true if array only consists of refByte + * @return validator that returns true if array only consists of refByte */ public static BytesValidator onlyOf(byte refByte) { return new BytesValidator.IdenticalContent(refByte, BytesValidator.IdenticalContent.Mode.ONLY_OF); @@ -76,7 +77,7 @@ public static BytesValidator onlyOf(byte refByte) { * Checks individual byte content * * @param refByte to check against - * @return true if array has at least one byte that is not refByte + * @return validator that returns true if array has at least one byte that is not refByte */ public static BytesValidator notOnlyOf(byte refByte) { return new BytesValidator.IdenticalContent(refByte, BytesValidator.IdenticalContent.Mode.NOT_ONLY_OF); @@ -86,7 +87,7 @@ public static BytesValidator notOnlyOf(byte refByte) { * Checks if the internal byte array starts with given bytes * * @param startsWithBytes the supposed prefix - * @return true all startsWithBytes match the first bytes in the internal array + * @return validator that returns true all startsWithBytes match the first bytes in the internal array */ public static BytesValidator startsWith(byte... startsWithBytes) { return new BytesValidator.PrePostFix(true, startsWithBytes); @@ -96,7 +97,7 @@ public static BytesValidator startsWith(byte... startsWithBytes) { * Checks if the internal byte array ends with given bytes * * @param endsWithBytes the supposed postfix - * @return true all startsWithBytes match the first bytes in the internal array + * @return validator that returns true all startsWithBytes match the first bytes in the internal array */ public static BytesValidator endsWith(byte... endsWithBytes) { return new BytesValidator.PrePostFix(false, endsWithBytes); @@ -106,18 +107,17 @@ public static BytesValidator endsWith(byte... endsWithBytes) { * Checks individual byte content * * @param refByte to check against - * @return true if array has no value refByte + * @return validator that returns true if array has no value refByte */ public static BytesValidator noneOf(byte refByte) { return new BytesValidator.IdenticalContent(refByte, BytesValidator.IdenticalContent.Mode.NONE_OF); } - /** * This will execute all passed validators and returns true if at least one returns true (i.e. OR concatenation) * * @param validators at least one validator must be passed - * @return true if at least one validator returns true + * @return validator that returns true if at least one validator returns true */ public static BytesValidator or(BytesValidator... validators) { return new BytesValidator.Logical(Arrays.asList(validators), BytesValidator.Logical.Operator.OR); @@ -127,7 +127,7 @@ public static BytesValidator or(BytesValidator... validators) { * This will execute all passed validators and returns true if all return true (i.e. AND concatenation) * * @param validators at least one validator must be passed - * @return true if all return true + * @return validator that returns true if all return true */ public static BytesValidator and(BytesValidator... validators) { return new BytesValidator.Logical(Arrays.asList(validators), BytesValidator.Logical.Operator.AND); diff --git a/src/main/java/at/favre/lib/bytes/MutableBytes.java b/src/main/java/at/favre/lib/bytes/MutableBytes.java index 808bd23..270ae29 100644 --- a/src/main/java/at/favre/lib/bytes/MutableBytes.java +++ b/src/main/java/at/favre/lib/bytes/MutableBytes.java @@ -32,12 +32,33 @@ * Adds additional mutator, which may change the internal array in-place, like {@link #wipe()} */ @SuppressWarnings("WeakerAccess") -public final class MutableBytes extends Bytes { +public final class MutableBytes extends Bytes implements AutoCloseable { MutableBytes(byte[] byteArray, ByteOrder byteOrder) { super(byteArray, byteOrder, new Factory()); } + /** + * Creates a new instance with an empty array filled with zeros. + * + * @param length of the internal array + * @return new instance + */ + public static MutableBytes allocate(int length) { + return allocate(length, (byte) 0); + } + + /** + * Creates a new instance with an empty array filled with given defaultValue + * + * @param length of the internal array + * @param defaultValue to fill with + * @return new instance + */ + public static MutableBytes allocate(int length, byte defaultValue) { + return Bytes.allocate(length, defaultValue).mutable(); + } + @Override public boolean isMutable() { return true; @@ -48,11 +69,21 @@ public boolean isMutable() { * * @param newArray used to overwrite internal * @return this instance - * @throws IndexOutOfBoundsException if newArray.length > internal length + * @throws IndexOutOfBoundsException if newArray.length() > internal length */ public MutableBytes overwrite(byte[] newArray) { - overwrite(newArray, 0); - return this; + return overwrite(newArray, 0); + } + + /** + * Uses given Bytes array to overwrite internal array + * + * @param newBytes used to overwrite internal + * @return this instance + * @throws IndexOutOfBoundsException if newArray.length() > internal length + */ + public MutableBytes overwrite(Bytes newBytes) { + return overwrite(newBytes, 0); } /** @@ -61,7 +92,7 @@ public MutableBytes overwrite(byte[] newArray) { * @param newArray used to overwrite internal * @param offsetInternalArray index of the internal array to start overwriting * @return this instance - * @throws IndexOutOfBoundsException if newArray.length + offsetInternalArray > internal length + * @throws IndexOutOfBoundsException if newArray.length() + offsetInternalArray > internal length */ public MutableBytes overwrite(byte[] newArray, int offsetInternalArray) { Objects.requireNonNull(newArray, "must provide non-null array as source"); @@ -69,6 +100,18 @@ public MutableBytes overwrite(byte[] newArray, int offsetInternalArray) { return this; } + /** + * Uses given Bytes array to overwrite internal array. + * + * @param newBytes used to overwrite internal + * @param offsetInternalArray index of the internal array to start overwriting + * @return this instance + * @throws IndexOutOfBoundsException if newBytes.length() + offsetInternalArray > internal length + */ + public MutableBytes overwrite(Bytes newBytes, int offsetInternalArray) { + return overwrite(Objects.requireNonNull(newBytes, "must provide non-null array as source").array(), offsetInternalArray); + } + /** * Sets new byte to given index * @@ -87,8 +130,7 @@ public MutableBytes setByteAt(int index, byte newByte) { * @return this instance */ public MutableBytes wipe() { - fill((byte) 0); - return this; + return fill((byte) 0); } /** @@ -108,8 +150,7 @@ public MutableBytes fill(byte fillByte) { * @return this instance */ public MutableBytes secureWipe() { - secureWipe(new SecureRandom()); - return this; + return secureWipe(new SecureRandom()); } /** @@ -119,13 +160,39 @@ public MutableBytes secureWipe() { * @return this instance */ public MutableBytes secureWipe(SecureRandom random) { - Objects.requireNonNull(random, "must be non-null random"); + Objects.requireNonNull(random, "random param must not be null"); if (length() > 0) { random.nextBytes(internalArray()); } return this; } + /** + * Convert this instance to an immutable version with the same reference of the internal array and byte-order. + * If the mutable instance is kept, it can be used to alter the internal array of the just created instance, so be + * aware. + * + * @return immutable version of this instance + */ + public Bytes immutable() { + return Bytes.wrap(internalArray(), byteOrder()); + } + + @Override + public int hashCode() { + return Util.Obj.hashCode(internalArray(), byteOrder()); + } + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + public void close() { + secureWipe(); + } + /** * Factory creating mutable byte types */ diff --git a/src/main/java/at/favre/lib/bytes/ReadOnlyBytes.java b/src/main/java/at/favre/lib/bytes/ReadOnlyBytes.java index 4959c64..5baefee 100644 --- a/src/main/java/at/favre/lib/bytes/ReadOnlyBytes.java +++ b/src/main/java/at/favre/lib/bytes/ReadOnlyBytes.java @@ -34,7 +34,7 @@ public final class ReadOnlyBytes extends Bytes { /** - * Creates a new immutable instance + * Creates a new read-only instance * * @param byteArray internal byte array * @param byteOrder the internal byte order - this is used to interpret given array, not to change it diff --git a/src/main/java/at/favre/lib/bytes/Util.java b/src/main/java/at/favre/lib/bytes/Util.java index d3a8c74..ac234bb 100644 --- a/src/main/java/at/favre/lib/bytes/Util.java +++ b/src/main/java/at/favre/lib/bytes/Util.java @@ -22,7 +22,9 @@ package at.favre.lib.bytes; import java.io.*; -import java.nio.ByteBuffer; +import java.nio.*; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; import java.nio.file.Files; import java.util.*; @@ -30,477 +32,1176 @@ * Common Util methods to convert or modify byte arrays */ final class Util { - private Util() { - } /** - * Returns the values from each provided byteArray combined into a single byteArray. - * For example, {@code append(new byte[] {a, b}, new byte[] {}, new - * byte[] {c}} returns the byteArray {@code {a, b, c}}. - * - * @param arrays zero or more {@code byte} arrays - * @return a single byteArray containing all the values from the source arrays, in - * order + * Util methods related general purpose byte utility. */ - static byte[] concat(byte[]... arrays) { - int length = 0; - for (byte[] array : arrays) { - length += array.length; - } - byte[] result = new byte[length]; - int pos = 0; - for (byte[] array : arrays) { - System.arraycopy(array, 0, result, pos, array.length); - pos += array.length; - } - return result; - } + static final class Byte { + private Byte() { + } - /** - * Returns the index of the first appearance of the value {@code target} in - * {@code array}. - * - * @param array an array of {@code byte} values, possibly empty - * @param target a primitive {@code byte} value - * @return the least index {@code i} for which {@code array[i] == target}, or - * {@code -1} if no such index exists. - */ - static int indexOf(byte[] array, byte target, int start, int end) { - for (int i = start; i < end; i++) { - if (array[i] == target) { + /** + * Returns the values from each provided byteArray combined into a single byteArray. + * For example, {@code append(new byte[] {a, b}, new byte[] {}, new + * byte[] {c}} returns the byteArray {@code {a, b, c}}. + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param arrays zero or more {@code byte} arrays + * @return a single byteArray containing all the values from the source arrays, in + * order + */ + static byte[] concat(byte[]... arrays) { + int length = 0; + for (byte[] array : arrays) { + length += array.length; + } + byte[] result = new byte[length]; + int pos = 0; + for (byte[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + /** + * Combines a single argument with a vararg to a single array + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param firstByte first arg + * @param moreBytes varargs + * @return array containing all args + */ + static byte[] concatVararg(byte firstByte, byte[] moreBytes) { + if (moreBytes == null) { + return new byte[]{firstByte}; + } else { + return concat(new byte[]{firstByte}, moreBytes); + } + } + + /** + * Returns the start position of the first occurrence of the specified {@code + * target} within {@code array}, or {@code -1} if there is no such occurrence. + *

    + *

    More formally, returns the lowest index {@code i} such that {@code + * java.util.Arrays.copyOfRange(array, i, i + target.length)} contains exactly + * the same elements as {@code target}. + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n*m)
    • + *
    • Space Complexity: O(1)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + static int indexOf(byte[] array, byte[] target, int start, int end) { + Objects.requireNonNull(array, "array must not be null"); + Objects.requireNonNull(target, "target must not be null"); + if (target.length == 0 || start < 0) { + return -1; + } + + outer: + for (int i = start; i < Math.min(end, array.length - target.length + 1); i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } return i; } + return -1; } - return -1; - } - /** - * Returns the start position of the first occurrence of the specified {@code - * target} within {@code array}, or {@code -1} if there is no such occurrence. - *

    - *

    More formally, returns the lowest index {@code i} such that {@code - * java.util.Arrays.copyOfRange(array, i, i + target.length)} contains exactly - * the same elements as {@code target}. - * - * @param array the array to search for the sequence {@code target} - * @param target the array to search for as a sub-sequence of {@code array} - */ - public static int indexOf(byte[] array, byte[] target) { - Objects.requireNonNull(array, "array must not be null"); - Objects.requireNonNull(target, "target must not be null"); - if (target.length == 0) { - return 0; - } - - outer: - for (int i = 0; i < array.length - target.length + 1; i++) { - for (int j = 0; j < target.length; j++) { - if (array[i + j] != target[j]) { - continue outer; + /** + * Returns the index of the last appearance of the value {@code target} in + * {@code array}. + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(1)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param array an array of {@code byte} values, possibly empty + * @param target a primitive {@code byte} value + * @return the greatest index {@code i} for which {@code array[i] == target}, + * or {@code -1} if no such index exists. + */ + static int lastIndexOf(byte[] array, byte target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; } } - return i; + return -1; } - return -1; - } - /** - * Returns the index of the last appearance of the value {@code target} in - * {@code array}. - * - * @param array an array of {@code byte} values, possibly empty - * @param target a primitive {@code byte} value - * @return the greatest index {@code i} for which {@code array[i] == target}, - * or {@code -1} if no such index exists. - */ - static int lastIndexOf(byte[] array, byte target, int start, int end) { - for (int i = end - 1; i >= start; i--) { - if (array[i] == target) { - return i; + /** + * Counts the occurrence of target in the subject array + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(1)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param array to count in + * @param target to count + * @return number of times target is in subject + */ + static int countByte(byte[] array, byte target) { + int count = 0; + for (byte b : array) { + if (b == target) { + count++; + } } + return count; } - return -1; - } - /** - * Copies a collection of {@code Byte} instances into a new array of - * primitive {@code byte} values. - *

    - *

    Elements are copied from the argument collection as if by {@code - * collection.toArray()}. Calling this method is as thread-safe as calling - * that method. - * - * @param collection a collection of {@code Byte} objects - * @return an array containing the same values as {@code collection}, in the - * same order, converted to primitives - * @throws NullPointerException if {@code collection} or any of its elements - * is null - */ - static byte[] toArray(Collection collection) { - Object[] boxedArray = collection.toArray(); - int len = boxedArray.length; - byte[] array = new byte[len]; - for (int i = 0; i < len; i++) { - array[i] = (Byte) boxedArray[i]; - } - return array; - } + /** + * Counts the times given pattern (i.e. an array) can be found in given array + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n*m)
    • + *
    • Space Complexity: O(1)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param array to count in + * @param pattern to match in array + * @return number of times pattern is in subject + */ + static int countByteArray(byte[] array, byte[] pattern) { + Objects.requireNonNull(pattern, "pattern must not be null"); + if (pattern.length == 0 || array.length == 0) { + return 0; + } + int count = 0; + outer: + for (int i = 0; i < array.length - pattern.length + 1; i++) { + for (int j = 0; j < pattern.length; j++) { + if (array[i + j] != pattern[j]) { + continue outer; + } + } + count++; + } + return count; + } - /** - * Converts given array to list of boxed bytes. Will create a new list - * and not reuse the array reference. - * - * @param array to convert - * @return list with same length and content as array - */ - static List toList(byte[] array) { - List list = new ArrayList<>(array.length); - for (byte b : array) { - list.add(b); + /** + * Simple Durstenfeld shuffle. + * This will shuffle given array and will not make a copy, so beware. + *

    + * See: Yates_shuffle + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(1)
    • + *
    • Alters Parameters: true
    • + *
    + *

    + * + * @param array to shuffle + * @param random used to derive entropy - use {@link java.security.SecureRandom} instance if you want this to be secure + */ + static void shuffle(byte[] array, Random random) { + for (int i = array.length - 1; i > 0; i--) { + int index = random.nextInt(i + 1); + byte a = array[index]; + array[index] = array[i]; + array[i] = a; + } } - return list; - } - /** - * Converts this primitive array to an boxed object array. - * Will create a new array and not reuse the array reference. - * - * @param array to convert - * @return new array - */ - static Byte[] toBoxedArray(byte[] array) { - Byte[] objectArray = new Byte[array.length]; - for (int i = 0; i < array.length; i++) { - objectArray[i] = array[i]; + /** + * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive. This is equivalent to {@code + * Collections.reverse(Bytes.asList(array).subList(fromIndex, toIndex))}, but is likely to be more + * efficient. + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(1)
    • + *
    • Alters Parameters: true
    • + *
    + *

    + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + */ + static void reverse(byte[] array, int fromIndex, int toIndex) { + Objects.requireNonNull(array); + for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { + byte tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } } - return objectArray; - } - /** - * Converts this object array to an primitives type array. - * Will create a new array and not reuse the array reference. - * - * @param objectArray to convert - * @return new array - */ - static byte[] toPrimitiveArray(Byte[] objectArray) { - byte[] primitivesArray = new byte[objectArray.length]; - for (int i = 0; i < objectArray.length; i++) { - primitivesArray[i] = objectArray[i]; + /** + * Light shift of whole byte array by shiftBitCount bits. + * This method will alter the input byte array. + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(1)
    • + *
    • Alters Parameters: true
    • + *
    + *

    + * + * @param byteArray to shift + * @param shiftBitCount how many bits to shift + * @param byteOrder endianness of given byte array + * @return shifted byte array + */ + static byte[] shiftLeft(byte[] byteArray, int shiftBitCount, ByteOrder byteOrder) { + final int shiftMod = shiftBitCount % 8; + final byte carryMask = (byte) ((1 << shiftMod) - 1); + final int offsetBytes = (shiftBitCount / 8); + + int sourceIndex; + if (byteOrder == ByteOrder.BIG_ENDIAN) { + for (int i = 0; i < byteArray.length; i++) { + sourceIndex = i + offsetBytes; + if (sourceIndex >= byteArray.length) { + byteArray[i] = 0; + } else { + byte src = byteArray[sourceIndex]; + byte dst = (byte) (src << shiftMod); + if (sourceIndex + 1 < byteArray.length) { + dst |= byteArray[sourceIndex + 1] >>> (8 - shiftMod) & carryMask & 0xff; + } + byteArray[i] = dst; + } + } + } else { + for (int i = byteArray.length - 1; i >= 0; i--) { + sourceIndex = i - offsetBytes; + if (sourceIndex < 0) { + byteArray[i] = 0; + } else { + byte src = byteArray[sourceIndex]; + byte dst = (byte) (src << shiftMod); + if (sourceIndex - 1 >= 0) { + dst |= byteArray[sourceIndex - 1] >>> (8 - shiftMod) & carryMask & 0xff; + } + byteArray[i] = dst; + } + } + } + return byteArray; } - return primitivesArray; - } - /** - * Creates a byte array from given int array. - * The resulting byte array will have length intArray * 4. - * - * @param intArray to convert - * @return resulting byte array - */ - static byte[] toByteArray(int[] intArray) { - byte[] primitivesArray = new byte[intArray.length * 4]; - for (int i = 0; i < intArray.length; i++) { - byte[] intBytes = ByteBuffer.allocate(4).putInt(intArray[i]).array(); - System.arraycopy(intBytes, 0, primitivesArray, (i * 4), intBytes.length); + /** + * Unsigned/logical right shift of whole byte array by shiftBitCount bits. + * This method will alter the input byte array. + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(1)
    • + *
    • Alters Parameters: true
    • + *
    + *

    + * + * @param byteArray to shift + * @param shiftBitCount how many bits to shift + * @param byteOrder endianness of given byte array + * @return shifted byte array + */ + static byte[] shiftRight(byte[] byteArray, int shiftBitCount, ByteOrder byteOrder) { + final int shiftMod = shiftBitCount % 8; + final byte carryMask = (byte) (0xFF << (8 - shiftMod)); + final int offsetBytes = (shiftBitCount / 8); + + int sourceIndex; + if (byteOrder == ByteOrder.BIG_ENDIAN) { + for (int i = byteArray.length - 1; i >= 0; i--) { + sourceIndex = i - offsetBytes; + if (sourceIndex < 0) { + byteArray[i] = 0; + } else { + byte src = byteArray[sourceIndex]; + byte dst = (byte) ((0xff & src) >>> shiftMod); + if (sourceIndex - 1 >= 0) { + dst |= byteArray[sourceIndex - 1] << (8 - shiftMod) & carryMask & 0xff; + } + byteArray[i] = dst; + } + } + } else { + for (int i = 0; i < byteArray.length; i++) { + sourceIndex = i + offsetBytes; + if (sourceIndex >= byteArray.length) { + byteArray[i] = 0; + } else { + byte src = byteArray[sourceIndex]; + byte dst = (byte) ((0xff & src) >>> shiftMod); + if (sourceIndex + 1 < byteArray.length) { + dst |= byteArray[sourceIndex + 1] << (8 - shiftMod) & carryMask & 0xff; + } + byteArray[i] = dst; + } + } + } + return byteArray; } - return primitivesArray; - } - /** - * Creates a byte array from given long array. - * The resulting byte array will have length longArray * 8 - * - * @param longArray to convert - * @return resulting byte array - */ - static byte[] toByteArray(long[] longArray) { - byte[] primitivesArray = new byte[longArray.length * 8]; - for (int i = 0; i < longArray.length; i++) { - byte[] longBytes = ByteBuffer.allocate(8).putLong(longArray[i]).array(); - System.arraycopy(longBytes, 0, primitivesArray, (i * 8), longBytes.length); + /** + * See https://codahale.com/a-lesson-in-timing-attacks/ + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(1)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param array to check for equals + * @param anotherArray to check against array + * @return if both arrays have the same length and same length for every index + */ + static boolean constantTimeEquals(byte[] array, byte[] anotherArray) { + if (anotherArray == null || array.length != anotherArray.length) return false; + + int result = 0; + for (int i = 0; i < array.length; i++) { + result |= array[i] ^ anotherArray[i]; + } + return result == 0; } - return primitivesArray; - } - /** - * Simple Durstenfeld shuffle - *

    - * See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm - * - * @param array - * @param random - */ - static void shuffle(byte[] array, Random random) { - for (int i = array.length - 1; i > 0; i--) { - int index = random.nextInt(i + 1); - byte a = array[index]; - array[index] = array[i]; - array[i] = a; + /** + * Calculates the entropy factor of a byte array. + *

    + * This implementation will not create a copy of the internal array and will only internally initialize + * an int array with 256 elements as temporary buffer. + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(1)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param array to calculate the entropy from + * @return entropy factor, higher means higher entropy + */ + static double entropy(byte[] array) { + final int[] buffer = new int[256]; + Arrays.fill(buffer, -1); + + for (byte element : array) { + int unsigned = 0xff & element; + if (buffer[unsigned] == -1) { + buffer[unsigned] = 0; + } + buffer[unsigned]++; + } + + double entropy = 0; + for (int count : buffer) { + if (count == -1) continue; + double prob = (double) count / array.length; + entropy -= prob * (Math.log(prob) / Math.log(2)); + } + return entropy; } } /** - * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive. This is equivalent to {@code - * Collections.reverse(Bytes.asList(array).subList(fromIndex, toIndex))}, but is likely to be more - * efficient. - * - * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or - * {@code toIndex > fromIndex} + * Util method related converting byte arrays to other types. */ - static void reverse(byte[] array, int fromIndex, int toIndex) { - Objects.requireNonNull(array); - for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { - byte tmp = array[i]; - array[i] = array[j]; - array[j] = tmp; + static final class Converter { + private Converter() { } - } - private static final int BUF_SIZE = 0x1000; // 4K + /** + * Copies a collection of {@code Byte} instances into a new array of + * primitive {@code byte} values. + *

    + *

    Elements are copied from the argument collection as if by {@code + * collection.toArray()}. Calling this method is as thread-safe as calling + * that method. + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param collection a collection of {@code Byte} objects + * @return an array containing the same values as {@code collection}, in the + * same order, converted to primitives + * @throws NullPointerException if {@code collection} or any of its elements + * is null + */ + static byte[] toArray(Collection collection) { + final int len = collection.size(); + final byte[] array = new byte[len]; + int i = 0; + for (java.lang.Byte b : collection) { + array[i] = b; + i++; + } + return array; + } - /** - * Read all bytes, buffered, from given input stream - * - * @param inputStream to read from - * @return all bytes from the stream - */ - static byte[] readFromStream(InputStream inputStream) { - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] buf = new byte[BUF_SIZE]; - while (true) { - int r = inputStream.read(buf); - if (r == -1) { - break; + /** + * Converts this primitive array to a boxed object array. + * Will create a new array and not reuse the array reference. + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param array to convert + * @return new array + */ + static java.lang.Byte[] toBoxedArray(byte[] array) { + java.lang.Byte[] objectArray = new java.lang.Byte[array.length]; + for (int i = 0; i < array.length; i++) { + objectArray[i] = array[i]; + } + return objectArray; + } + + /** + * Converts given array to list of boxed bytes. Will create a new list + * and not reuse the array reference. + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param array to convert + * @return list with same length and content as array + */ + static List toList(byte[] array) { + List list = new ArrayList<>(array.length); + for (byte b : array) { + list.add(b); + } + return list; + } + + /** + * Converts this object array to a primitives type array. + * Will create a new array and not reuse the array reference. + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param objectArray to convert + * @return new array + */ + static byte[] toPrimitiveArray(java.lang.Byte[] objectArray) { + byte[] primitivesArray = new byte[objectArray.length]; + for (int i = 0; i < objectArray.length; i++) { + primitivesArray[i] = objectArray[i]; + } + return primitivesArray; + } + + /** + * Creates a byte array from given short array. + * The resulting byte array will have length shortArray * 2. + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param shortArray to convert + * @return resulting byte array + */ + static byte[] toByteArray(short[] shortArray) { + byte[] primitivesArray = new byte[shortArray.length * 2]; + ByteBuffer buffer = ByteBuffer.allocate(2); + for (int i = 0; i < shortArray.length; i++) { + buffer.clear(); + byte[] shortBytes = buffer.putShort(shortArray[i]).array(); + System.arraycopy(shortBytes, 0, primitivesArray, (i * 2), shortBytes.length); + } + return primitivesArray; + } + + /** + * Creates a byte array from given int array. + * The resulting byte array will have length intArray * 4. + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param intArray to convert + * @return resulting byte array + */ + static byte[] toByteArray(int[] intArray) { + byte[] primitivesArray = new byte[intArray.length * 4]; + ByteBuffer buffer = ByteBuffer.allocate(4); + for (int i = 0; i < intArray.length; i++) { + buffer.clear(); + byte[] intBytes = buffer.putInt(intArray[i]).array(); + System.arraycopy(intBytes, 0, primitivesArray, (i * 4), intBytes.length); + } + return primitivesArray; + } + + /** + * Creates a byte array from given float array. + * The resulting byte array will have length floatArray * 4. + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param floatArray to convert + * @return resulting byte array + */ + static byte[] toByteArray(float[] floatArray) { + byte[] primitivesArray = new byte[floatArray.length * 4]; + ByteBuffer buffer = ByteBuffer.allocate(4); + for (int i = 0; i < floatArray.length; i++) { + buffer.clear(); + byte[] floatBytes = buffer.putFloat(floatArray[i]).array(); + System.arraycopy(floatBytes, 0, primitivesArray, (i * 4), floatBytes.length); + } + return primitivesArray; + } + + /** + * Creates a byte array from given long array. + * The resulting byte array will have length longArray * 8 + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param longArray to convert + * @return resulting byte array + */ + static byte[] toByteArray(long[] longArray) { + byte[] primitivesArray = new byte[longArray.length * 8]; + ByteBuffer buffer = ByteBuffer.allocate(8); + for (int i = 0; i < longArray.length; i++) { + buffer.clear(); + byte[] longBytes = buffer.putLong(longArray[i]).array(); + System.arraycopy(longBytes, 0, primitivesArray, (i * 8), longBytes.length); + } + return primitivesArray; + } + + /** + * Creates a byte array from given double array. + * The resulting byte array will have length doubleArray * 8. + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param doubleArray to convert + * @return resulting byte array + */ + static byte[] toByteArray(double[] doubleArray) { + byte[] primitivesArray = new byte[doubleArray.length * 8]; + ByteBuffer buffer = ByteBuffer.allocate(8); + for (int i = 0; i < doubleArray.length; i++) { + buffer.clear(); + byte[] doubleBytes = buffer.putDouble(doubleArray[i]).array(); + System.arraycopy(doubleBytes, 0, primitivesArray, (i * 8), doubleBytes.length); + } + return primitivesArray; + } + + /** + * Converts a char array to a byte array with given charset and range + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param charArray to get the byte array from + * @param charset charset to be used to decode the char array + * @param offset to start reading the char array from (must be smaller than length and gt 0) + * @param length from offset (must be between 0 and charArray.length()) + * @return byte array of encoded chars + */ + static byte[] charToByteArray(char[] charArray, Charset charset, int offset, int length) { + if (offset < 0 || offset > charArray.length) + throw new IllegalArgumentException("offset must be gt 0 and smaller than array length"); + if (length < 0 || length > charArray.length) + throw new IllegalArgumentException("length must be at least 1 and less than array length"); + if (offset + length > charArray.length) + throw new IllegalArgumentException("length + offset must be smaller than array length"); + + if (length == 0) return new byte[0]; + + CharBuffer charBuffer = CharBuffer.wrap(charArray); + + if (offset != 0 || length != charBuffer.remaining()) { + charBuffer = charBuffer.subSequence(offset, offset + length); + } + + ByteBuffer bb = charset.encode(charBuffer); + if (bb.capacity() != bb.limit()) { + byte[] bytes = new byte[bb.remaining()]; + bb.get(bytes); + return bytes; + } + return bb.array(); + } + + /** + * Convert given byte array in given encoding to char array + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param bytes as data source + * @param charset of the byte array + * @param byteOrder the order of the bytes array + * @return char array + */ + static char[] byteToCharArray(byte[] bytes, Charset charset, ByteOrder byteOrder) { + Objects.requireNonNull(bytes, "bytes must not be null"); + Objects.requireNonNull(charset, "charset must not be null"); + + try { + CharBuffer charBuffer = charset.newDecoder().decode(ByteBuffer.wrap(bytes).order(byteOrder)); + if (charBuffer.capacity() != charBuffer.limit()) { + char[] compacted = new char[charBuffer.remaining()]; + charBuffer.get(compacted); + return compacted; } - out.write(buf, 0, r); + return charBuffer.array(); + } catch (CharacterCodingException e) { + throw new IllegalStateException(e); } - return out.toByteArray(); - } catch (Exception e) { - throw new IllegalStateException("could not read from input stream", e); } - } - /** - * Read all bytes until length from given byte array. - * - * @param dataInput to read from - * @return all bytes from the dataInput - */ - static byte[] readFromDataInput(DataInput dataInput, int length) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try { - byte[] buf; - int remaining = length; - for (int i = 0; i < length; i++) { - buf = new byte[Math.min(remaining, BUF_SIZE)]; - dataInput.readFully(buf); - out.write(buf); - remaining -= buf.length; - } - return out.toByteArray(); - } catch (Exception e) { - throw new IllegalStateException("could not read from data input", e); + /** + * Converts the byte array to an int array. This will spread 4 bytes into a single int: + * + *
    +         *     [b1, b2, b3, b4] = [int1]
    +         * 
    + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param bytes to convert to int array, must be % 4 == 0 to work correctly + * @param byteOrder of the byte array + * @return int array + */ + static int[] toIntArray(byte[] bytes, ByteOrder byteOrder) { + IntBuffer buffer = ByteBuffer.wrap(bytes).order(byteOrder).asIntBuffer(); + int[] array = new int[buffer.remaining()]; + buffer.get(array); + return array; } - } - /** - * Combines a single argument with a vararg to a single array - * - * @param firstByte first arg - * @param moreBytes varargs - * @return array containing all args - */ - static byte[] concatVararg(byte firstByte, byte[] moreBytes) { - if (moreBytes == null) { - return new byte[]{firstByte}; - } else { - return concat(new byte[]{firstByte}, moreBytes); + /** + * Converts the byte array to a long array. This will spread 8 bytes into a single long: + * + *
    +         *     [b1, b2, b3, b4, b5, b6, b7, b8] = [long1]
    +         * 
    + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param bytes to convert to long array, must be % 8 == 0 to work correctly + * @param byteOrder of the byte array + * @return long array + */ + static long[] toLongArray(byte[] bytes, ByteOrder byteOrder) { + LongBuffer buffer = ByteBuffer.wrap(bytes).order(byteOrder).asLongBuffer(); + long[] array = new long[buffer.remaining()]; + buffer.get(array); + return array; } - } - /** - * Reads all bytes from a file - * - * @param file the file to read - * @return byte content - */ - static byte[] readFromFile(File file) { - if (file == null || !file.exists() || !file.isFile()) { - throw new IllegalArgumentException("file must not be null, has to exist and must be a file (not a directory) " + file); + /** + * Converts the byte array to a float array. This will spread 4 bytes into a single float: + * + *
    +         *     [b1, b2, b3, b4] = [float1]
    +         * 
    + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param bytes to convert to float array, must be % 4 == 0 to work correctly + * @param byteOrder of the byte array + * @return float array + */ + static float[] toFloatArray(byte[] bytes, ByteOrder byteOrder) { + FloatBuffer buffer = ByteBuffer.wrap(bytes).order(byteOrder).asFloatBuffer(); + float[] array = new float[buffer.remaining()]; + buffer.get(array); + return array; } - try { - return Files.readAllBytes(file.toPath()); - } catch (IOException e) { - throw new IllegalStateException("could not read from file", e); + /** + * Converts the byte array to a double array. This will spread 8 bytes into a single double: + * + *
    +         *     [b1, b2, b3, b4, b5, b6, b7, b8] = [double1]
    +         * 
    + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param bytes to convert to double array, must be % 8 == 0 to work correctly + * @param byteOrder of the byte array + * @return double array + */ + static double[] toDoubleArray(byte[] bytes, ByteOrder byteOrder) { + DoubleBuffer buffer = ByteBuffer.wrap(bytes).order(byteOrder).asDoubleBuffer(); + double[] array = new double[buffer.remaining()]; + buffer.get(array); + return array; } - } - /** - * Shows the length and a preview of max 8 bytes of the given byte - * - * @param bytes to convert to string - * @return string representation - */ - static String toString(Bytes bytes) { - String preview; - if (bytes.isEmpty()) { - preview = ""; - } else if (bytes.length() > 8) { - preview = "(0x" + bytes.copy(0, 4).encodeHex() + "..." + bytes.copy(bytes.length() - 4, 4).encodeHex() + ")"; - } else { - preview = "(0x" + bytes.encodeHex() + ")"; + /** + * Converts the byte array to a short array. This will spread 2 bytes into a single short: + * + *
    +         *     [b1, b2] = [short1]
    +         * 
    + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(n)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param bytes to convert to short array, must be % 2 == 0 to work correctly + * @param byteOrder of the byte array + * @return short array + */ + static short[] toShortArray(byte[] bytes, ByteOrder byteOrder) { + ShortBuffer buffer = ByteBuffer.wrap(bytes).order(byteOrder).asShortBuffer(); + short[] array = new short[buffer.remaining()]; + buffer.get(array); + return array; } - return bytes.length() + " " + (bytes.length() == 1 ? "byte" : "bytes") + " " + preview; + /** + * Convert UUID to a newly generated 16 byte long array representation. Puts the 8 byte most significant bits and + * 8 byte least-significant bits into a byte array. + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(1)
    • + *
    • Space Complexity: O(1)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param uuid to convert to array + * @return buffer containing the 16 bytes + */ + static ByteBuffer toBytesFromUUID(UUID uuid) { + ByteBuffer bb = ByteBuffer.allocate(16); + bb.putLong(uuid.getMostSignificantBits()); + bb.putLong(uuid.getLeastSignificantBits()); + return bb; + } } /** - * Light shift of whole byte array by shiftBitCount bits. - * This method will alter the input byte array. + * Util method related to Object class methods. */ - static byte[] shiftLeft(byte[] byteArray, int shiftBitCount) { - final int shiftMod = shiftBitCount % 8; - final byte carryMask = (byte) ((1 << shiftMod) - 1); - final int offsetBytes = (shiftBitCount / 8); - - int sourceIndex; - for (int i = 0; i < byteArray.length; i++) { - sourceIndex = i + offsetBytes; - if (sourceIndex >= byteArray.length) { - byteArray[i] = 0; - } else { - byte src = byteArray[sourceIndex]; - byte dst = (byte) (src << shiftMod); - if (sourceIndex + 1 < byteArray.length) { - dst |= byteArray[sourceIndex + 1] >>> (8 - shiftMod) & carryMask; + static final class Obj { + private Obj() { + } + + /** + * Equals method comparing 2 byte arrays. + * This utilizes a quick return of the array differs on any given property so not suitable + * for security relevant checks. See {@link Util.Byte#constantTimeEquals(byte[], byte[])} + * for that. + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(1)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param obj subject a + * @param anotherArray subject b to compare to a + * @return if a.len == b.len and for every 0..len a[i] == b[i] + */ + static boolean equals(byte[] obj, java.lang.Byte[] anotherArray) { + if (anotherArray == null) return false; + if (obj.length != anotherArray.length) return false; + for (int i = 0; i < obj.length; i++) { + if (anotherArray[i] == null || obj[i] != anotherArray[i]) { + return false; } - byteArray[i] = dst; } + return true; } - return byteArray; - } - /** - * Unsigned/logical right shift of whole byte array by shiftBitCount bits. - * This method will alter the input byte array. - */ - static byte[] shiftRight(byte[] byteArray, int shiftBitCount) { - final int shiftMod = shiftBitCount % 8; - final byte carryMask = (byte) (0xFF << (8 - shiftMod)); - final int offsetBytes = (shiftBitCount / 8); - - int sourceIndex; - for (int i = byteArray.length - 1; i >= 0; i--) { - sourceIndex = i - offsetBytes; - if (sourceIndex < 0) { - byteArray[i] = 0; + /** + * Hashcode implementation for a byte array and given byte order + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(n)
    • + *
    • Space Complexity: O(1)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param byteArray to calculate hashCode of + * @param byteOrder to calculate hashCode of + * @return hashCode + */ + static int hashCode(byte[] byteArray, ByteOrder byteOrder) { + int result = Arrays.hashCode(byteArray); + result = 31 * result + (byteOrder != null ? byteOrder.hashCode() : 0); + return result; + } + + /** + * Shows the length and a preview of max 8 bytes of the given byte + * + *

    + * Analysis + *

      + *
    • Time Complexity: O(1)
    • + *
    • Space Complexity: O(1)
    • + *
    • Alters Parameters: false
    • + *
    + *

    + * + * @param bytes to convert to string + * @return string representation + */ + static String toString(Bytes bytes) { + String preview; + if (bytes.isEmpty()) { + preview = ""; + } else if (bytes.length() > 8) { + preview = "(0x" + bytes.copy(0, 4).encodeHex() + "..." + bytes.copy(bytes.length() - 4, 4).encodeHex() + ")"; } else { - byte src = byteArray[sourceIndex]; - byte dst = (byte) ((0xff & src) >>> shiftMod); - if (sourceIndex - 1 >= 0) { - dst |= byteArray[sourceIndex - 1] << (8 - shiftMod) & carryMask; - } - byteArray[i] = dst; + preview = "(0x" + bytes.encodeHex() + ")"; } + + return bytes.length() + " " + (bytes.length() == 1 ? "byte" : "bytes") + " " + preview; } - return byteArray; } - static void checkIndexBounds(int length, int index, int primitiveLength, String type) { - if (index < 0 || index + primitiveLength > length) { - throw new IndexOutOfBoundsException("cannot get " + type + " from index out of bounds: " + index); + /** + * Util method related check and validate byte arrays. + */ + static final class Validation { + private Validation() { } - } - static void checkExactLength(int length, int expectedLength, String type) { - if (length != expectedLength) { - throw new IllegalStateException("cannot convert to " + type + " if length != " + expectedLength + " bytes"); + /** + * Check if a length of a primitive (e.g. int = 4 byte) fits in given length from given start index. + * Throws exception with descriptive exception message. + * + * @param length of the whole array + * @param index to start from array length + * @param primitiveLength length of the primitive type to check + * @param type for easier debugging the human-readable type of the checked primitive + * to put in exception message + * @throws IndexOutOfBoundsException if index + primitiveLength > length + */ + static void checkIndexBounds(int length, int index, int primitiveLength, String type) { + if (index < 0 || index + primitiveLength > length) { + throw new IndexOutOfBoundsException("cannot get " + type + " from index out of bounds: " + index); + } } - } - static boolean equals(byte[] obj, Byte[] anotherArray) { - if (anotherArray == null) return false; - if (obj.length != anotherArray.length) return false; - for (int i = 0; i < obj.length; i++) { - if (anotherArray[i] == null || obj[i] != anotherArray[i]) { - return false; + /** + * Check if given length is an expected length. + * Throws exception with descriptive exception message. + * + * @param length of the whole array + * @param expectedLength how length is expected + * @param type for easier debugging the human-readable type of the checked primitive + * to put in exception message + * @throws IllegalArgumentException if length != expectedLength + */ + static void checkExactLength(int length, int expectedLength, String type) { + if (length != expectedLength) { + throw new IllegalArgumentException("cannot convert to " + type + " if length != " + expectedLength + " bytes (was " + length + ")"); } } - return true; - } - /** - * See https://codahale.com/a-lesson-in-timing-attacks/ - */ - static boolean constantTimeEquals(byte[] obj, byte[] anotherArray) { - if (anotherArray == null || obj.length != anotherArray.length) return false; + /** + * Checks if given length is divisible by mod factor (with zero rest). + * This can be used to check of a byte array can be converted to an e.g. int array which is + * multiples of 4. + * + * @param length of the byte array + * @param modFactor to divide the length + * @param errorSubject human-readable message of the exact error subject + * @throws IllegalArgumentException if length % modFactor != 0 + */ + static void checkModLength(int length, int modFactor, String errorSubject) { + if (length % modFactor != 0) { + throw new IllegalArgumentException("Illegal length for " + errorSubject + ". Byte array length must be multiple of " + modFactor + ", length was " + length); + } + } - int result = 0; - for (int i = 0; i < obj.length; i++) { - result |= obj[i] ^ anotherArray[i]; + /** + * Check if the file exists and is a file. + * + * @param file to check + * @throws IllegalArgumentException if either file is null, does not exist or is not a file + */ + private static void checkFileExists(java.io.File file) { + if (file == null || !file.exists() || !file.isFile()) { + throw new IllegalArgumentException("file must not be null, has to exist and must be a file (not a directory) " + file); + } } - return result == 0; } - /* - ================================================================================================= - Copyright 2011 Twitter, Inc. - ------------------------------------------------------------------------------------------------- - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this work except in compliance with the License. - You may obtain a copy of the License in the LICENSE file, or at: - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - ================================================================================================= - */ - /** - * Class that calculates the entropy factor - * - * @param + * Util method related file operations. */ - static final class Entropy { - private final Map map = new HashMap<>(); - private int total = 0; + static final class File { + private static final int BUF_SIZE = 0x1000; // 4K - private double Log2(double n) { - return Math.log(n) / Math.log(2); + private File() { } - public Entropy(Iterable elements) { - for (T element : elements) { - if (!map.containsKey(element)) { - map.put(element, 0); + /** + * Read bytes, buffered, from given input stream. Pass -1 to read the whole stream or limit with length + * parameter. + * + * @param inputStream to read from + * @param maxLengthToRead how many bytes to read from input stream; pass -1 to read whole stream + * @return all bytes from the stream (possibly limited by maxLengthToRead); output length is never longer than stream size + */ + static byte[] readFromStream(InputStream inputStream, final int maxLengthToRead) { + final boolean readWholeStream = maxLengthToRead == -1; + int remaining = maxLengthToRead; + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(readWholeStream ? 32 : maxLengthToRead); + byte[] buf = new byte[0]; + while (readWholeStream || remaining > 0) { + int bufSize = Math.min(BUF_SIZE, readWholeStream ? BUF_SIZE : remaining); + if (buf.length != bufSize) { + buf = new byte[bufSize]; + } + int r = inputStream.read(buf); + if (r == -1) { + break; + } + remaining -= r; + out.write(buf, 0, r); } - map.put(element, map.get(element) + 1); - total++; + return out.toByteArray(); + } catch (Exception e) { + throw new IllegalStateException("could not read from input stream", e); } } - public double entropy() { - double entropy = 0; - for (int count : map.values()) { - double prob = (double) count / total; - entropy -= prob * Log2(prob); + /** + * Read all bytes until length from given byte array. + * + * @param dataInput to read from + * @return all bytes from the dataInput + */ + static byte[] readFromDataInput(DataInput dataInput, int length) { + ByteArrayOutputStream out = new ByteArrayOutputStream(length); + try { + byte[] buf; + int remaining = length; + for (int i = 0; i < length; i++) { + buf = new byte[Math.min(remaining, BUF_SIZE)]; + dataInput.readFully(buf); + out.write(buf); + remaining -= buf.length; + } + return out.toByteArray(); + } catch (Exception e) { + throw new IllegalStateException("could not read from data input", e); + } + } + + /** + * Reads all bytes from a file + * + * @param file the file to read + * @return byte content + */ + static byte[] readFromFile(java.io.File file) { + Validation.checkFileExists(file); + + try { + return Files.readAllBytes(file.toPath()); + } catch (IOException e) { + throw new IllegalStateException("could not read from file", e); } - return entropy; } + + /** + * Reads bytes from file with given offset and max length + * + * @param file to read bytes from + * @param offset to read + * @param length from offset + * @return byte array with length + */ + static byte[] readFromFile(java.io.File file, int offset, int length) { + Validation.checkFileExists(file); + try (RandomAccessFile raf = new RandomAccessFile(file, "r")) { + raf.seek(offset); + return readFromDataInput(raf, length); + } catch (Exception e) { + throw new IllegalStateException("could not read from random access file", e); + } + } + + } + + private Util() { } /** * A simple iterator for the bytes class, which does not support remove */ - static final class BytesIterator implements Iterator { + static final class BytesIterator implements Iterator { private final byte[] array; /** * Index of element to be returned by subsequent call to next. @@ -517,10 +1218,10 @@ public boolean hasNext() { } @Override - public Byte next() { + public java.lang.Byte next() { try { int i = cursor; - Byte next = array[i]; + java.lang.Byte next = array[i]; cursor = i + 1; return next; } catch (IndexOutOfBoundsException e) { diff --git a/src/test/java/at/favre/lib/bytes/ABytesTest.java b/src/test/java/at/favre/lib/bytes/ABytesTest.java index e117913..ce4ab40 100644 --- a/src/test/java/at/favre/lib/bytes/ABytesTest.java +++ b/src/test/java/at/favre/lib/bytes/ABytesTest.java @@ -55,7 +55,7 @@ public abstract class ABytesTest { String example_hex_twentyfour; @Before - public void setUp() throws Exception { + public void setUp() { example_bytes_empty = new byte[0]; example_bytes_one = new byte[]{0x67}; example_hex_one = "67"; diff --git a/src/test/java/at/favre/lib/bytes/AUtilTest.java b/src/test/java/at/favre/lib/bytes/AUtilTest.java new file mode 100644 index 0000000..ee5112e --- /dev/null +++ b/src/test/java/at/favre/lib/bytes/AUtilTest.java @@ -0,0 +1,7 @@ +package at.favre.lib.bytes; + +abstract class AUtilTest { + static final byte[] EMPTY = {}; + static final byte[] ARRAY1 = {(byte) 1}; + static final byte[] ARRAY234 = {(byte) 2, (byte) 3, (byte) 4}; +} diff --git a/src/test/java/at/favre/lib/bytes/Base64Test.java b/src/test/java/at/favre/lib/bytes/Base64Test.java index 1251815..846b3f1 100644 --- a/src/test/java/at/favre/lib/bytes/Base64Test.java +++ b/src/test/java/at/favre/lib/bytes/Base64Test.java @@ -23,7 +23,8 @@ import org.junit.Test; -import static org.junit.Assert.*; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertNull; /** * Test cases from rfc4648 @@ -48,16 +49,15 @@ public void decode() { @Test public void encode() { - assertEquals("", Base64.encode("".getBytes())); - assertEquals("Zg==", Base64.encode("f".getBytes())); - assertEquals("Zm8=", Base64.encode("fo".getBytes())); - assertEquals("Zm9v", Base64.encode("foo".getBytes())); - assertEquals("Zm9vYg==", Base64.encode("foob".getBytes())); - assertEquals("Zm9vYmE=", Base64.encode("fooba".getBytes())); - assertEquals("Zm9vYmFy", Base64.encode("foobar".getBytes())); - assertEquals("aQo=", Base64.encode("i\n".getBytes())); - assertEquals("aSA=", Base64.encode("i ".getBytes())); - + assertArrayEquals(Bytes.from("").array(), Base64.encode("".getBytes())); + assertArrayEquals(Bytes.from("Zg==").array(), Base64.encode("f".getBytes())); + assertArrayEquals(Bytes.from("Zm8=").array(), Base64.encode("fo".getBytes())); + assertArrayEquals(Bytes.from("Zm9v").array(), Base64.encode("foo".getBytes())); + assertArrayEquals(Bytes.from("Zm9vYg==").array(), Base64.encode("foob".getBytes())); + assertArrayEquals(Bytes.from("Zm9vYmE=").array(), Base64.encode("fooba".getBytes())); + assertArrayEquals(Bytes.from("Zm9vYmFy").array(), Base64.encode("foobar".getBytes())); + assertArrayEquals(Bytes.from("aQo=").array(), Base64.encode("i\n".getBytes())); + assertArrayEquals(Bytes.from("aSA=").array(), Base64.encode("i ".getBytes())); } } diff --git a/src/test/java/at/favre/lib/bytes/BinaryToTextEncodingTest.java b/src/test/java/at/favre/lib/bytes/BinaryToTextEncodingTest.java index 4691e00..526d35e 100644 --- a/src/test/java/at/favre/lib/bytes/BinaryToTextEncodingTest.java +++ b/src/test/java/at/favre/lib/bytes/BinaryToTextEncodingTest.java @@ -21,7 +21,6 @@ package at.favre.lib.bytes; -import org.junit.Ignore; import org.junit.Test; import java.nio.ByteOrder; @@ -41,74 +40,125 @@ public void decodeHexShouldFail() { new BinaryToTextEncoding.Hex(false).decode("AAI="); } + @Test + public void testBase16Reference() { + BinaryToTextEncoding.EncoderDecoder base16Encoding = new BinaryToTextEncoding.Hex(true); + // see: https://tools.ietf.org/html/rfc4648 + assertEquals("", base16Encoding.encode(Bytes.from("").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("66", base16Encoding.encode(Bytes.from("f").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("666F", base16Encoding.encode(Bytes.from("fo").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("666F6F", base16Encoding.encode(Bytes.from("foo").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("666F6F62", base16Encoding.encode(Bytes.from("foob").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("666F6F6261", base16Encoding.encode(Bytes.from("fooba").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("666F6F626172", base16Encoding.encode(Bytes.from("foobar").array(), ByteOrder.BIG_ENDIAN)); + + assertArrayEquals(Bytes.from("").array(), base16Encoding.decode("")); + assertArrayEquals(Bytes.from("f").array(), base16Encoding.decode("66")); + assertArrayEquals(Bytes.from("fo").array(), base16Encoding.decode("666F")); + assertArrayEquals(Bytes.from("foo").array(), base16Encoding.decode("666F6F")); + assertArrayEquals(Bytes.from("foob").array(), base16Encoding.decode("666F6F62")); + assertArrayEquals(Bytes.from("fooba").array(), base16Encoding.decode("666F6F6261")); + assertArrayEquals(Bytes.from("foobar").array(), base16Encoding.decode("666F6F626172")); + } + @Test public void encodeBaseRadix() { - assertEquals("100211", new BinaryToTextEncoding.BaseRadix(16).encode(new byte[]{16, 2, 17}, ByteOrder.BIG_ENDIAN)); - assertEquals("110210", new BinaryToTextEncoding.BaseRadix(16).encode(new byte[]{16, 2, 17}, ByteOrder.LITTLE_ENDIAN)); - assertNotEquals(new BinaryToTextEncoding.BaseRadix(2).encode(new byte[]{1, 2, 3}, ByteOrder.LITTLE_ENDIAN), new BinaryToTextEncoding.BaseRadix(2).encode(new byte[]{1, 2, 3}, ByteOrder.BIG_ENDIAN)); + assertEquals("100211", new BinaryToTextEncoding.BaseRadixNumber(16).encode(new byte[]{16, 2, 17}, ByteOrder.BIG_ENDIAN)); + assertEquals("110210", new BinaryToTextEncoding.BaseRadixNumber(16).encode(new byte[]{16, 2, 17}, ByteOrder.LITTLE_ENDIAN)); + assertNotEquals(new BinaryToTextEncoding.BaseRadixNumber(2).encode(new byte[]{1, 2, 3}, ByteOrder.LITTLE_ENDIAN), new BinaryToTextEncoding.BaseRadixNumber(2).encode(new byte[]{1, 2, 3}, ByteOrder.BIG_ENDIAN)); } @Test public void encodeDecodeRadix() { - for (int i = 0; i < 32; i++) { - Bytes rnd = Bytes.random(i); + int leadingZeroHits = 0; + int encodings = 0; + for (int i = 0; i < 64; i++) { + Bytes rnd = Bytes.random(i % 256); System.out.println("\n\nNEW TEST: " + i + " bytes\n"); - for (int j = 16; j < 36; j++) { - BinaryToTextEncoding.EncoderDecoder encoding = new BinaryToTextEncoding.BaseRadix(j); + for (int j = 2; j <= 36; j++) { + encodings++; + BinaryToTextEncoding.EncoderDecoder encoding = new BinaryToTextEncoding.BaseRadixNumber(j); String encodedBigEndian = encoding.encode(rnd.array(), ByteOrder.BIG_ENDIAN); byte[] decoded = encoding.decode(encodedBigEndian); System.out.println("radix" + j + ":\t" + encodedBigEndian); System.out.println("orig :\t" + rnd.encodeHex()); System.out.println("enc :\t" + Bytes.wrap(decoded).encodeHex()); - assertArrayEquals(rnd.array(), decoded); + + + if (rnd.length() <= 0 || rnd.byteAt(0) != 0) { + assertArrayEquals(rnd.array(), decoded); + } else { //since this is a number, we allow different lengths due to leading zero + leadingZeroHits++; + assertArrayEquals(rnd.resize(rnd.length() - 1).array(), decoded); + } } } + System.out.println(leadingZeroHits + " leading zero mismatches of " + encodings + " encodings"); } @Test - @Ignore("should fix") public void encodeDecodeRadixZeros() { Bytes bytes = Bytes.wrap(new byte[]{0, 0, 0, 0}); - BinaryToTextEncoding.EncoderDecoder encoding = new BinaryToTextEncoding.BaseRadix(36); + BinaryToTextEncoding.EncoderDecoder encoding = new BinaryToTextEncoding.BaseRadixNumber(36); String encodedBigEndian = encoding.encode(bytes.array(), ByteOrder.BIG_ENDIAN); byte[] decoded = encoding.decode(encodedBigEndian); System.out.println("radix36:\t" + encodedBigEndian); System.out.println("orig :\t" + bytes.encodeHex()); System.out.println("enc :\t" + Bytes.wrap(decoded).encodeHex()); - assertArrayEquals(bytes.array(), decoded); + assertArrayEquals(new byte[]{}, decoded); } @Test - public void encodeDecodeBase64() { - for (int i = 4; i < 32; i += 4) { - Bytes rnd = Bytes.random(i); - BinaryToTextEncoding.EncoderDecoder encoding = new BinaryToTextEncoding.Base64Encoding(); - String encodedBigEndian = encoding.encode(rnd.array(), ByteOrder.BIG_ENDIAN); - byte[] decoded = encoding.decode(encodedBigEndian); - assertEquals(rnd, Bytes.wrap(decoded)); + public void encodeDecodeBase64Random() { + BinaryToTextEncoding.EncoderDecoder encoderPad = new BinaryToTextEncoding.Base64Encoding(false, true); + BinaryToTextEncoding.EncoderDecoder encoderUrlPad = new BinaryToTextEncoding.Base64Encoding(true, true); + BinaryToTextEncoding.EncoderDecoder encoderNoPad = new BinaryToTextEncoding.Base64Encoding(false, false); + + for (int i = 0; i < 32; i += 4) { + testRndEncodeDecode(encoderPad, i); + testRndEncodeDecode(encoderUrlPad, i); + testRndEncodeDecode(encoderNoPad, i); } } + @Test + public void testBase64Reference() { + BinaryToTextEncoding.EncoderDecoder base64Encoding = new BinaryToTextEncoding.Base64Encoding(); + // see: https://tools.ietf.org/html/rfc4648 + assertEquals("", base64Encoding.encode(Bytes.from("").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("Zg==", base64Encoding.encode(Bytes.from("f").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("Zm8=", base64Encoding.encode(Bytes.from("fo").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("Zm9v", base64Encoding.encode(Bytes.from("foo").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("Zm9vYg==", base64Encoding.encode(Bytes.from("foob").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("Zm9vYmE=", base64Encoding.encode(Bytes.from("fooba").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("Zm9vYmFy", base64Encoding.encode(Bytes.from("foobar").array(), ByteOrder.BIG_ENDIAN)); + + assertArrayEquals(Bytes.from("").array(), base64Encoding.decode("")); + assertArrayEquals(Bytes.from("f").array(), base64Encoding.decode("Zg==")); + assertArrayEquals(Bytes.from("fo").array(), base64Encoding.decode("Zm8=")); + assertArrayEquals(Bytes.from("foo").array(), base64Encoding.decode("Zm9v")); + assertArrayEquals(Bytes.from("foob").array(), base64Encoding.decode("Zm9vYg==")); + assertArrayEquals(Bytes.from("fooba").array(), base64Encoding.decode("Zm9vYmE=")); + assertArrayEquals(Bytes.from("foobar").array(), base64Encoding.decode("Zm9vYmFy")); + } + @Test public void encodeDecodeHex() { for (int i = 4; i < 32; i += 4) { - Bytes rnd = Bytes.random(i); - BinaryToTextEncoding.EncoderDecoder encoding = new BinaryToTextEncoding.Hex(); - String encodedBigEndian = encoding.encode(rnd.array(), ByteOrder.BIG_ENDIAN); - byte[] decoded = encoding.decode(encodedBigEndian); - assertEquals(rnd, Bytes.wrap(decoded)); + testRndEncodeDecode(new BinaryToTextEncoding.Hex(), i); + testRndEncodeDecode(new BinaryToTextEncoding.Hex(true), i); } } @Test(expected = IllegalArgumentException.class) public void decodeInvalidRadix16() { - new BinaryToTextEncoding.BaseRadix(16).decode("AAI="); + new BinaryToTextEncoding.BaseRadixNumber(16).decode("AAI="); } @Test(expected = IllegalArgumentException.class) public void decodeInvalidRadix36() { - new BinaryToTextEncoding.BaseRadix(36).decode("AAI="); + new BinaryToTextEncoding.BaseRadixNumber(36).decode("AAI="); } @Test @@ -128,4 +178,77 @@ public void decodeInvalidBase64() { public void decodeHalfInvalidBase64() { new BinaryToTextEncoding.Base64Encoding().decode("EAI`"); } + + @Test(expected = IllegalArgumentException.class) + public void encodeRadixIllegalTooHigh2() { + new BinaryToTextEncoding.BaseRadixNumber(38); + } + + @Test(expected = IllegalArgumentException.class) + public void encodeRadixIllegalTooHigh() { + new BinaryToTextEncoding.BaseRadixNumber(37); + } + + @Test(expected = IllegalArgumentException.class) + public void encodeRadixIllegalTooLow() { + new BinaryToTextEncoding.BaseRadixNumber(1); + } + + @Test(expected = IllegalArgumentException.class) + public void encodeRadixIllegalTooLow2() { + new BinaryToTextEncoding.BaseRadixNumber(0); + } + + @Test + public void testEncodeDecodeRndBase32() { + BaseEncoding base32Encoding = new BaseEncoding(BaseEncoding.BASE32_RFC4848, BaseEncoding.BASE32_RFC4848_PADDING); + for (int i = 0; i < 128; i++) { + testRndEncodeDecode(base32Encoding, i); + } + } + + private byte[] testRndEncodeDecode(BinaryToTextEncoding.EncoderDecoder encoder, int dataLength) { + Bytes rnd = Bytes.random(dataLength); + String encoded = encoder.encode(rnd.array(), ByteOrder.BIG_ENDIAN); + byte[] decoded = encoder.decode(encoded); + assertEquals(rnd, Bytes.wrap(decoded)); + return decoded; + } + + @Test + public void testBase32Reference() { + BinaryToTextEncoding.EncoderDecoder base32Encoding = new BaseEncoding(BaseEncoding.BASE32_RFC4848, BaseEncoding.BASE32_RFC4848_PADDING); + // see: https://tools.ietf.org/html/rfc4648 + assertEquals("", base32Encoding.encode(Bytes.from("").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("MY======", base32Encoding.encode(Bytes.from("f").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("MZXQ====", base32Encoding.encode(Bytes.from("fo").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("MZXW6===", base32Encoding.encode(Bytes.from("foo").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("MZXW6YQ=", base32Encoding.encode(Bytes.from("foob").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("MZXW6YTB", base32Encoding.encode(Bytes.from("fooba").array(), ByteOrder.BIG_ENDIAN)); + assertEquals("MZXW6YTBOI======", base32Encoding.encode(Bytes.from("foobar").array(), ByteOrder.BIG_ENDIAN)); + + assertArrayEquals(Bytes.from("").array(), base32Encoding.decode("")); + assertArrayEquals(Bytes.from("f").array(), base32Encoding.decode("MY======")); + assertArrayEquals(Bytes.from("fo").array(), base32Encoding.decode("MZXQ====")); + assertArrayEquals(Bytes.from("foo").array(), base32Encoding.decode("MZXW6===")); + assertArrayEquals(Bytes.from("foob").array(), base32Encoding.decode("MZXW6YQ=")); + assertArrayEquals(Bytes.from("fooba").array(), base32Encoding.decode("MZXW6YTB")); + assertArrayEquals(Bytes.from("foobar").array(), base32Encoding.decode("MZXW6YTBOI======")); + } + + @Test + public void testBase64BigData() { + for (int i = 0; i < 5; i++) { + byte[] out = testRndEncodeDecode(new BinaryToTextEncoding.Base64Encoding(), 1024 * 1024); + System.out.println(out.length); + } + } + + @Test + public void testBase32BigData() { + for (int i = 0; i < 5; i++) { + byte[] out = testRndEncodeDecode(new BaseEncoding(BaseEncoding.BASE32_RFC4848, BaseEncoding.BASE32_RFC4848_PADDING), 1024 * 1024); + System.out.println(out.length); + } + } } diff --git a/src/test/java/at/favre/lib/bytes/BytesByteOrderTest.java b/src/test/java/at/favre/lib/bytes/BytesByteOrderTest.java index 663a1a3..71d137f 100644 --- a/src/test/java/at/favre/lib/bytes/BytesByteOrderTest.java +++ b/src/test/java/at/favre/lib/bytes/BytesByteOrderTest.java @@ -31,7 +31,7 @@ public class BytesByteOrderTest extends ABytesTest { @Test - public void miscInput() throws Exception { + public void miscInput() { testOrder(Bytes.from(350)); testOrder(Bytes.from(172863182736L)); testOrder(Bytes.from(example_bytes_one)); @@ -52,82 +52,82 @@ private void testOrder(Bytes bytes) { } @Test - public void encodeBinary() throws Exception { + public void encodeBinary() { Bytes b = Bytes.from(example_bytes_four); assertNotEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).encodeBinary(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).encodeBinary()); assertEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).encodeBinary(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).reverse().encodeBinary()); } @Test - public void encodeOct() throws Exception { + public void encodeOct() { Bytes b = Bytes.from(example_bytes_four); assertNotEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).encodeOctal(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).encodeOctal()); assertEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).encodeOctal(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).reverse().encodeOctal()); } @Test - public void encodeDec() throws Exception { + public void encodeDec() { Bytes b = Bytes.from(example_bytes_four); assertNotEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).encodeDec(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).encodeDec()); assertEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).encodeDec(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).reverse().encodeDec()); } @Test - public void encodeHex() throws Exception { + public void encodeHex() { Bytes b = Bytes.from(example_bytes_two); assertNotEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).encodeHex(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).encodeHex()); assertEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).encodeHex(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).reverse().encodeHex()); } @Test - public void encodeBase36() throws Exception { + public void encodeBase36() { Bytes b = Bytes.from(example_bytes_four); assertNotEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).encodeBase36(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).encodeBase36()); assertEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).encodeBase36(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).reverse().encodeBase36()); } @Test - public void encodeBase64() throws Exception { + public void encodeBase64() { Bytes b = Bytes.from(example_bytes_four); assertNotEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).encodeBase64(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).encodeBase64()); assertEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).encodeBase64(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).reverse().encodeBase64()); } @Test - public void toByte() throws Exception { + public void toByte() { Bytes b = Bytes.from(example_bytes_one); assertEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).toByte(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).toByte()); } @Test - public void toChar() throws Exception { + public void toChar() { Bytes b = Bytes.from(example_bytes_two); assertNotEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).toChar(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).toChar()); } @Test - public void toShort() throws Exception { + public void toShort() { Bytes b = Bytes.from(example_bytes_two); assertNotEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).toShort(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).toShort()); } @Test - public void toInt() throws Exception { + public void toInt() { Bytes b = Bytes.from(example_bytes_four); assertNotEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).toInt(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).toInt()); } @Test - public void toLong() throws Exception { + public void toLong() { Bytes b = Bytes.from(example_bytes_eight); assertNotEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).toLong(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).toLong()); } @Test - public void bigInteger() throws Exception { + public void bigInteger() { Bytes b = Bytes.from(example_bytes_four); assertNotEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).toBigInteger(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).toBigInteger()); assertEquals(b.byteOrder(ByteOrder.BIG_ENDIAN).toBigInteger(), b.byteOrder(ByteOrder.LITTLE_ENDIAN).reverse().toBigInteger()); } -} \ No newline at end of file +} diff --git a/src/test/java/at/favre/lib/bytes/BytesConstructorTests.java b/src/test/java/at/favre/lib/bytes/BytesConstructorTests.java index b485c2e..5cf5058 100644 --- a/src/test/java/at/favre/lib/bytes/BytesConstructorTests.java +++ b/src/test/java/at/favre/lib/bytes/BytesConstructorTests.java @@ -26,24 +26,26 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.io.File; -import java.io.FileOutputStream; +import java.io.*; import java.math.BigInteger; import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.CharBuffer; +import java.nio.IntBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; import java.text.Normalizer; import java.util.Arrays; import java.util.BitSet; import java.util.LinkedList; +import java.util.UUID; import static org.junit.Assert.*; public class BytesConstructorTests extends ABytesTest { @Rule - public TemporaryFolder testFolder = new TemporaryFolder(); + public final TemporaryFolder testFolder = new TemporaryFolder(); @Test public void wrapTest() { @@ -65,6 +67,11 @@ public void wrapTest() { assertEquals(0, bNullSafe1.length()); } + @Test(expected = NullPointerException.class) + public void wrapByteNull_shouldThrow() { + Bytes.wrap((Bytes) null); + } + @Test(expected = NullPointerException.class) public void wrapTestNullExpected() { Bytes.wrap((byte[]) null); @@ -72,7 +79,19 @@ public void wrapTestNullExpected() { @Test public void wrapTestNullSafe() { - Bytes.wrapNullSafe(null); + assertSame(Bytes.empty(), Bytes.wrapNullSafe(null)); + } + + @Test + public void empty() { + assertEquals(0, Bytes.empty().length()); + assertEquals(Bytes.allocate(0), Bytes.empty()); + assertArrayEquals(new byte[0], Bytes.empty().array()); + assertSame(Bytes.empty(), Bytes.empty()); + + Bytes empty = Bytes.empty(); + empty = empty.byteOrder(ByteOrder.LITTLE_ENDIAN); + assertNotSame(Bytes.empty(), empty); } @Test @@ -160,6 +179,19 @@ public void fromShort() { assertEquals(test, Bytes.from(test).toShort()); } + @Test(expected = NullPointerException.class) + public void fromShortArray_empty_shouldThrow() { + Bytes.from((short[]) null); + } + + @Test + public void fromShortArray() { + assertArrayEquals(new byte[]{0, 1, 0, 2}, Bytes.from((short) 1, (short) 2).array()); + assertArrayEquals(Bytes.from(Bytes.from((short) 32767), Bytes.from((short) 6761), Bytes.from((short) -8517)).array(), Bytes.from((short) 32767, (short) 6761, (short) -8517).array()); + assertArrayEquals(Bytes.from(Bytes.from((short) 1678), Bytes.from((short) -223), Bytes.from((short) 11114)).array(), Bytes.from((short) 1678, (short) -223, (short) 11114).array()); + assertArrayEquals(new byte[]{114, -123, 35, 53, 0, 0, 56, -70}, Bytes.from((short) 29317, (short) 9013, (short) 0, (short) 14522).array()); + } + @Test public void fromInt() { int test = 722837193; @@ -169,6 +201,11 @@ public void fromInt() { assertEquals(test, Bytes.from(test).toInt()); } + @Test(expected = NullPointerException.class) + public void fromIntArray_empty_shouldThrow() { + Bytes.from((int[]) null); + } + @Test public void fromIntArray() { assertArrayEquals(new byte[]{0, 0, 0, 1, 0, 0, 0, 2}, Bytes.from(1, 2).array()); @@ -177,6 +214,13 @@ public void fromIntArray() { assertArrayEquals(new byte[]{0, 11, 30, 55, 0, 0, 35, 53, 0, 0, 0, 0, 0, 0, 56, -70}, Bytes.from(728631, 9013, 0, 14522).array()); } + @Test + public void fromIntBuffer() { + assertArrayEquals(new byte[]{0, 0, 0, 1, 0, 0, 0, 2}, Bytes.from(IntBuffer.wrap(new int[]{1, 2})).array()); + assertArrayEquals(Bytes.from(Bytes.from(871193), Bytes.from(6761), Bytes.from(-917656)).array(), Bytes.from(IntBuffer.wrap(new int[]{871193, 6761, -917656})).array()); + assertArrayEquals(Bytes.empty().array(), Bytes.from(IntBuffer.allocate(0)).array()); + } + @Test public void fromLong() { long test = 172283719283L; @@ -186,6 +230,11 @@ public void fromLong() { assertEquals(test, Bytes.from(test).toLong()); } + @Test(expected = NullPointerException.class) + public void fromLongArray_empty_shouldThrow() { + Bytes.from((long[]) null); + } + @Test public void fromLongArray() { assertArrayEquals(new byte[]{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2}, Bytes.from(new long[]{1, 2}).array()); @@ -203,6 +252,22 @@ public void fromFloat() { assertEquals(test, Bytes.from(test).toFloat(), 0.01); } + @Test(expected = NullPointerException.class) + public void fromFloatArray_empty_shouldThrow() { + Bytes.from((float[]) null); + } + + @Test + public void fromFloatArray() { + assertArrayEquals(new byte[]{0, 0, 0, 0, 0, 0, 0, 0}, Bytes.from(0f, 0f).array()); + assertArrayEquals(new byte[]{0, 0, 0, 0, 63, -128, 0, 0}, Bytes.from(0f, 1f).array()); + assertArrayEquals(new byte[]{0, 0, 0, 0, -65, -128, 0, 0}, Bytes.from(0f, -1f).array()); + assertArrayEquals(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Bytes.from(0f, 0f, 0f).array()); + assertArrayEquals(new byte[]{66, -105, 0, 0, 71, 119, 46, 31}, Bytes.from(75.5f, 63278.123f).array()); + assertArrayEquals(Bytes.from(Bytes.from(78239.934978f), Bytes.from(-82736.65178f), Bytes.from(0.12879316287461f)).array(), + Bytes.from(78239.934978f, -82736.65178f, 0.12879316287461f).array()); + } + @Test public void fromDouble() { double test = 3423423.8923423974123; @@ -212,6 +277,21 @@ public void fromDouble() { assertEquals(test, Bytes.from(test).toDouble(), 0.01); } + @Test(expected = NullPointerException.class) + public void fromDoubleArray_empty_shouldThrow() { + Bytes.from((double[]) null); + } + + @Test + public void fromDoubleArray() { + assertArrayEquals(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Bytes.from(0.0, 0.0).array()); + assertArrayEquals(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 63, -16, 0, 0, 0, 0, 0, 0}, Bytes.from(0.0, 1.0).array()); + assertArrayEquals(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, -65, -16, 0, 0, 0, 0, 0, 0}, Bytes.from(0.0, -1.0).array()); + assertArrayEquals(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Bytes.from(0, 0, 0).array()); + assertArrayEquals(Bytes.from(Bytes.from(78239.934978), Bytes.from(-82736.65178), Bytes.from(0.12879316287461)).array(), + Bytes.from(78239.934978, -82736.65178, 0.12879316287461).array()); + } + @Test public void fromByteBuffer() { checkByteBuffer(example_bytes_empty); @@ -241,6 +321,22 @@ public void fromString() { checkString("7asdh#ö01^^`´dµ@€", StandardCharsets.ISO_8859_1); } + @Test + public void encodeCharsetToBytes() { + byte[][] testVectors = new byte[][]{example_bytes_seven, example_bytes_one, example_bytes_two, example2_bytes_seven, example_bytes_twentyfour}; + + for (byte[] testVector : testVectors) { + System.out.println(new String(testVector, StandardCharsets.ISO_8859_1)); + System.out.println(new String(Bytes.wrap(testVector).encodeCharsetToBytes(StandardCharsets.ISO_8859_1), StandardCharsets.ISO_8859_1)); + System.out.println(new String(testVector, StandardCharsets.UTF_8)); + System.out.println(new String(Bytes.wrap(testVector).encodeCharsetToBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8)); + + assertArrayEquals(new String(testVector, StandardCharsets.ISO_8859_1).getBytes(StandardCharsets.ISO_8859_1), Bytes.wrap(testVector).encodeCharsetToBytes(StandardCharsets.ISO_8859_1)); + assertArrayEquals(new String(testVector, StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8), Bytes.wrap(testVector).encodeCharsetToBytes(StandardCharsets.UTF_8)); + assertArrayEquals(new String(testVector, StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8), Bytes.wrap(testVector).encodeUtf8ToBytes()); + } + } + @Test public void fromCharArray() { checkCharArray(""); @@ -248,6 +344,49 @@ public void fromCharArray() { checkCharArray("\t"); checkCharArray("a"); checkCharArray("12345678abcdefjkl"); + checkCharArray("é_,,(8áàäöü#+_ ,,mµ"); + + String s1 = "oaisdj`ßß__.#äöü_- *aé"; + assertArrayEquals(String.valueOf(s1.toCharArray()).getBytes(StandardCharsets.ISO_8859_1), Bytes.from(s1.toCharArray(), StandardCharsets.ISO_8859_1).array()); + assertArrayEquals(String.valueOf(s1.toCharArray()).getBytes(StandardCharsets.UTF_16), Bytes.from(s1.toCharArray(), StandardCharsets.UTF_16).array()); + assertArrayEquals(String.valueOf(s1.toCharArray()).getBytes(StandardCharsets.UTF_8), Bytes.from(s1.toCharArray(), StandardCharsets.UTF_8).array()); + assertArrayEquals(String.valueOf(s1.substring(0, 1).toCharArray()).getBytes(StandardCharsets.UTF_8), Bytes.from(s1.toCharArray(), StandardCharsets.UTF_8, 0, 1).array()); + assertArrayEquals(String.valueOf(s1.substring(3, 7).toCharArray()).getBytes(StandardCharsets.UTF_8), Bytes.from(s1.toCharArray(), StandardCharsets.UTF_8, 3, 4).array()); + assertArrayEquals(Bytes.empty().array(), Bytes.from(CharBuffer.allocate(0)).array()); + } + + @Test + public void toCharArray() { + String unicodeString = "|µ€@7é8ahslishalsdalöskdḼơᶉëᶆ ȋṕšᶙṁ ḍỡḽǭᵳ ʂǐť ӓṁệẗ, ĉṓɲṩḙċ"; + assertArrayEquals(Bytes.from(unicodeString.toCharArray()).array(), Bytes.from(Bytes.from(unicodeString).toCharArray()).array()); + + checkToCharArray(unicodeString, StandardCharsets.UTF_8); + checkToCharArray(unicodeString, StandardCharsets.UTF_16); + checkToCharArray(unicodeString, StandardCharsets.UTF_16BE); + + String asciiString = "asciiASCIIString1234$%&"; + + checkToCharArray(asciiString, StandardCharsets.UTF_8); + checkToCharArray(asciiString, StandardCharsets.UTF_16); + checkToCharArray(asciiString, StandardCharsets.US_ASCII); + checkToCharArray(asciiString, StandardCharsets.ISO_8859_1); + } + + private void checkToCharArray(String string, Charset charset) { + byte[] b0 = String.valueOf(string.toCharArray()).getBytes(charset); + char[] charArray = Bytes.from(b0).toCharArray(charset); + assertEquals(string, new String(charArray)); + assertArrayEquals(string.toCharArray(), Bytes.from(string.toCharArray(), charset).toCharArray(charset)); + } + + @Test(expected = NullPointerException.class) + public void toCharArrayShouldThroughNullPointer() { + Bytes.allocate(4).toCharArray(null); + } + + @Test(expected = NullPointerException.class) + public void fromMultipleBytes_empty_shouldThrow() { + Bytes.from((Bytes[]) null); } @Test @@ -271,8 +410,12 @@ private void checkString(String string, Charset charset) { } private void checkCharArray(String s) { - Bytes b = Bytes.from(s.toCharArray()); - assertArrayEquals(String.valueOf(s.toCharArray()).getBytes(StandardCharsets.UTF_8), b.array()); + Bytes b1 = Bytes.from(s.toCharArray()); + Bytes b2 = Bytes.from(s.toCharArray(), StandardCharsets.UTF_8); + Bytes b3 = Bytes.from(CharBuffer.wrap(s.toCharArray())); + assertArrayEquals(String.valueOf(s.toCharArray()).getBytes(StandardCharsets.UTF_8), b1.array()); + assertArrayEquals(String.valueOf(s.toCharArray()).getBytes(StandardCharsets.UTF_8), b2.array()); + assertArrayEquals(String.valueOf(s.toCharArray()).getBytes(StandardCharsets.UTF_8), b3.array()); } @Test @@ -284,12 +427,26 @@ public void fromInputStream() { checkInputStream(example_bytes_eight); checkInputStream(example_bytes_sixteen); checkInputStream(Bytes.random(32 * 987).array()); + checkInputStream(Bytes.random(1020 * 1104).array()); } private void checkInputStream(byte[] array) { assertArrayEquals(array, Bytes.from(new ByteArrayInputStream(array)).array()); } + @Test + public void fromInputStreamLimited() { + Bytes data = Bytes.random(1090 * 1003); + assertArrayEquals(data.resize(5123, BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_ZERO_INDEX).array(), Bytes.from(new ByteArrayInputStream(data.array()), 5123).array()); + + assertArrayEquals(new byte[0], Bytes.from(new ByteArrayInputStream(example_bytes_sixteen), 0).array()); + assertArrayEquals(new byte[]{0x7E}, Bytes.from(new ByteArrayInputStream(example_bytes_sixteen), 1).array()); + assertArrayEquals(new byte[]{0x7E, (byte) 0xD1}, Bytes.from(new ByteArrayInputStream(example_bytes_sixteen), 2).array()); + assertArrayEquals(new byte[]{0x7E, (byte) 0xD1, (byte) 0xFD}, Bytes.from(new ByteArrayInputStream(example_bytes_sixteen), 3).array()); + assertArrayEquals(new byte[]{0x7E, (byte) 0xD1, (byte) 0xFD, (byte) 0xAA}, Bytes.from(new ByteArrayInputStream(example_bytes_sixteen), 4).array()); + assertArrayEquals(example_bytes_sixteen, Bytes.from(new ByteArrayInputStream(example_bytes_sixteen), 128).array()); + } + @Test public void fromDataInput() { checkDataInput(example_bytes_one); @@ -302,12 +459,12 @@ public void fromDataInput() { } private void checkDataInput(byte[] array) { - assertArrayEquals(array, Bytes.from(new DataInputStream(new ByteArrayInputStream(array)), array.length).array()); + assertArrayEquals(array, Bytes.from((DataInput) new DataInputStream(new ByteArrayInputStream(array)), array.length).array()); } @Test(expected = IllegalStateException.class) public void fromDataInputShouldThrowException() { - Bytes.from(new DataInputStream(new ByteArrayInputStream(example_bytes_one)), 2); + Bytes.from((DataInput) new DataInputStream(new ByteArrayInputStream(example_bytes_one)), 2); } @Test @@ -321,8 +478,8 @@ public void fromList() { } private void checkList(byte[] array) { - Bytes bList = Bytes.from(Util.toList(array)); - Bytes bLinkedList = Bytes.from(new LinkedList<>(Util.toList(array))); + Bytes bList = Bytes.from(Util.Converter.toList(array)); + Bytes bLinkedList = Bytes.from(new LinkedList<>(Util.Converter.toList(array))); assertArrayEquals(array, bList.array()); assertArrayEquals(array, bLinkedList.array()); } @@ -339,10 +496,10 @@ public void fromVariousBytes() { assertArrayEquals(new byte[1], Bytes.from((byte) 0).array()); assertArrayNotEquals(new byte[0], Bytes.from((byte) 1).array()); - assertArrayEquals(Util.concat(example_bytes_one, example_bytes_one, example_bytes_one), Bytes.from(example_bytes_one, example_bytes_one, example_bytes_one).array()); - assertArrayEquals(Util.concat(example_bytes_two, example_bytes_seven), Bytes.from(example_bytes_two, example_bytes_seven).array()); - assertArrayEquals(Util.concat(example_bytes_one, example_bytes_sixteen), Bytes.from(example_bytes_one, example_bytes_sixteen).array()); - assertArrayNotEquals(Util.concat(example_bytes_sixteen, example_bytes_one), Bytes.from(example_bytes_one, example_bytes_sixteen).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_one, example_bytes_one, example_bytes_one), Bytes.from(example_bytes_one, example_bytes_one, example_bytes_one).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_two, example_bytes_seven), Bytes.from(example_bytes_two, example_bytes_seven).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_one, example_bytes_sixteen), Bytes.from(example_bytes_one, example_bytes_sixteen).array()); + assertArrayNotEquals(Util.Byte.concat(example_bytes_sixteen, example_bytes_one), Bytes.from(example_bytes_one, example_bytes_sixteen).array()); assertArrayEquals(new byte[]{1, 2, 3}, Bytes.from((byte) 1, (byte) 2, (byte) 3).array()); assertArrayEquals(new byte[2], Bytes.from((byte) 0, (byte) 0).array()); @@ -353,6 +510,21 @@ public void fromVariousBytes() { assertArrayEquals(example_bytes_sixteen, Bytes.fromNullSafe(example_bytes_sixteen).array()); } + @Test(expected = NullPointerException.class) + public void fromArray_empty_shouldThrow() { + Bytes.from((byte[]) null); + } + + @Test(expected = NullPointerException.class) + public void wrapArrayByteOrder_empty_shouldThrow() { + Bytes.wrap(null, ByteOrder.BIG_ENDIAN); + } + + @Test(expected = NullPointerException.class) + public void fromPartByte_empty_shouldThrow() { + Bytes.from((byte[]) null, 0, 1); + } + @Test public void fromPartByte() { assertArrayEquals(new byte[]{example_bytes_four[1]}, Bytes.from(example_bytes_four, 1, 1).array()); @@ -389,7 +561,44 @@ public void fromFileCannotRead() throws Exception { try { Bytes.from(tempFile); fail(); - } catch (IllegalStateException e) { + } catch (IllegalStateException ignored) { + } + } + + @Test + public void fromFileOffset() throws Exception { + File tempFile = testFolder.newFile("out-test2.txt"); + Bytes bytes = Bytes.wrap(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}); + try (FileOutputStream stream = new FileOutputStream(tempFile)) { + stream.write(bytes.array()); + } + + for (int lenI = 1; lenI < bytes.length() + 1; lenI++) { + for (int offsetI = 0; offsetI < bytes.length(); offsetI++) { + if (offsetI + lenI > bytes.length()) break; + assertEquals(bytes.copy(offsetI, lenI), Bytes.from(tempFile, offsetI, lenI)); + } + } + assertEquals(Bytes.from(tempFile), Bytes.from(tempFile, 0, (int) tempFile.length())); + } + + @Test + public void fromFileOffsetWithIllegalOffsetOrLength() throws Exception { + File tempFile = testFolder.newFile("fromFileOffsetWithIllegalOffsetOrLength.txt"); + try (FileOutputStream stream = new FileOutputStream(tempFile)) { + stream.write(new byte[]{0, 1, 2, 3}); + } + + try { + Bytes.from(tempFile, 0, 5); + fail(); + } catch (IllegalStateException ignored) { + } + + try { + Bytes.from(tempFile, 5, 1); + fail(); + } catch (IllegalStateException ignored) { } } @@ -399,4 +608,44 @@ public void fromObjectArray() { Bytes b = Bytes.from(objectArray); assertArrayEquals(new byte[]{0x01, 0x02, 0x03, 0x04}, b.array()); } + + @Test + public void fromUUID() { + testUUID(UUID.fromString("00000000-0000-0000-0000-000000000000")); + testUUID(UUID.fromString("123e4567-e89b-42d3-a456-556642440000")); + testUUID(UUID.fromString("e8e3db08-dc39-48ea-a3db-08dc3958eafb")); + testUUID(UUID.randomUUID()); + } + + private void testUUID(UUID uuid) { + Bytes b = Bytes.from(uuid); + assertEquals(16, b.length()); + assertEquals(uuid, b.toUUID()); + } + + @Test(expected = NullPointerException.class) + public void fromUUIDNullArgument() { + Bytes.from((UUID) null); + } + + @Test + public void createSecureRandom() { + assertNotEquals(Bytes.random(16), Bytes.random(16)); + } + + @Test + public void createSecureRandomWithExplicitSecureRandom() { + assertNotEquals(Bytes.random(16, new SecureRandom()), Bytes.random(16, new SecureRandom())); + } + + @Test + public void createUnsecureRandom() { + assertNotEquals(Bytes.unsecureRandom(128), Bytes.unsecureRandom(128)); + } + + @Test + public void createUnsecureRandomWithSeed() { + assertEquals(Bytes.unsecureRandom(128, 4L), Bytes.unsecureRandom(128, 4L)); + assertNotEquals(Bytes.unsecureRandom(4, 3L), Bytes.unsecureRandom(4, 4L)); + } } diff --git a/src/test/java/at/favre/lib/bytes/BytesMiscTest.java b/src/test/java/at/favre/lib/bytes/BytesMiscTest.java index 7bc0b1b..a57398d 100644 --- a/src/test/java/at/favre/lib/bytes/BytesMiscTest.java +++ b/src/test/java/at/favre/lib/bytes/BytesMiscTest.java @@ -52,13 +52,26 @@ private void testToString(Bytes bytes) { @Test public void testHashcode() { - assertEquals(Bytes.wrap(example_bytes_seven).hashCode(), Bytes.from(example_bytes_seven).hashCode()); - assertEquals(Bytes.wrap(example2_bytes_seven).hashCode(), Bytes.from(example2_bytes_seven).hashCode()); - assertNotEquals(Bytes.wrap(example_bytes_seven).hashCode(), Bytes.wrap(example2_bytes_seven).hashCode()); - assertNotEquals(Bytes.wrap(example_bytes_eight).hashCode(), Bytes.wrap(example2_bytes_seven).hashCode()); - assertNotEquals(0, Bytes.wrap(example2_bytes_seven).hashCode()); + Bytes instance = Bytes.from(example_bytes_seven); + assertEquals(instance.hashCode(), instance.hashCode()); + assertNotEquals(0, instance.hashCode()); + assertEquals(Bytes.from(example_bytes_seven).hashCode(), Bytes.from(example_bytes_seven).hashCode()); + assertEquals(Bytes.from(example2_bytes_seven).hashCode(), Bytes.from(example2_bytes_seven).hashCode()); + assertNotEquals(Bytes.from(example_bytes_seven).hashCode(), Bytes.from(example2_bytes_seven).hashCode()); + assertNotEquals(Bytes.from(example_bytes_eight).hashCode(), Bytes.from(example2_bytes_seven).hashCode()); + assertNotEquals(0, Bytes.from(example2_bytes_seven).hashCode()); } + @Test + public void testHashcode_changing() { + MutableBytes instance = Bytes.from(example_bytes_seven).mutable(); + assertEquals(instance.hashCode(), Bytes.from(example_bytes_seven).hashCode()); + + instance.setByteAt(0, (byte) 0x4B); + assertNotEquals(instance.hashCode(), Bytes.from(example_bytes_seven).hashCode()); + } + + @SuppressWarnings("SimplifiableJUnitAssertion") @Test public void testEquals() { assertTrue(Bytes.wrap(new byte[0]).equals(Bytes.wrap(new byte[0]))); @@ -117,14 +130,14 @@ public void testCompareTo() { assertTrue(-1 >= Bytes.from(b1).compareTo(Bytes.from(b2))); assertTrue(1 <= Bytes.from(b2).compareTo(Bytes.from(b1))); - assertTrue(0 == Bytes.from(b1).compareTo(Bytes.from(b1))); + assertEquals(0, Bytes.from(b1).compareTo(Bytes.from(b1))); byte[] bOne = new byte[]{0x01}; byte[] bTwo = new byte[]{0x02}; assertTrue(-1 >= Bytes.from(bOne).compareTo(Bytes.from(bTwo))); assertTrue(1 <= Bytes.from(bTwo).compareTo(Bytes.from(bOne))); - assertTrue(0 == Bytes.from(bOne).compareTo(Bytes.from(bOne))); + assertEquals(0, Bytes.from(bOne).compareTo(Bytes.from(bOne))); } @Test @@ -147,6 +160,7 @@ public void testIsEmpty() { assertFalse(Bytes.from(example_bytes_seven).isEmpty()); } + @SuppressWarnings("SimplifiableJUnitAssertion") @Test public void containsTest() { assertEquals(false, Bytes.allocate(0).contains((byte) 0xFD)); @@ -165,14 +179,76 @@ public void indexOfByte() { assertEquals(-1, Bytes.from(example_bytes_seven).indexOf((byte) 0x00)); } + @Test + public void indexOfByteFromIndex() { + assertEquals(-1, Bytes.allocate(0).indexOf((byte) 0xFD, 0)); + assertEquals(-1, Bytes.allocate(0).indexOf((byte) 0xFD, 100)); + assertEquals(5, Bytes.allocate(128).indexOf((byte) 0x00, 5)); + assertEquals(2, Bytes.from(example_bytes_sixteen).indexOf((byte) 0xFD, 0)); + assertEquals(10, Bytes.from(example_bytes_sixteen).indexOf((byte) 0xFD, 5)); + } + + @Test + public void indexOfByteFromIndexToIndex() { + assertEquals(4, Bytes.from(example_bytes_seven).indexOf((byte) 0x1E, 0, 7)); + assertEquals(4, Bytes.from(example_bytes_seven).indexOf((byte) 0x1E, 3, 5)); + assertEquals(-1, Bytes.from(example_bytes_seven).indexOf((byte) 0x1E, 0, 3)); + assertEquals(-1, Bytes.from(example_bytes_seven).indexOf((byte) 0x1E, 6, 7)); + assertEquals(-1, Bytes.from(example_bytes_seven).indexOf((byte) 0xCA, 0, 7)); + } + @Test public void indexOfArray() { assertEquals(-1, Bytes.allocate(0).indexOf(new byte[]{(byte) 0xFD})); - assertEquals(0, Bytes.allocate(1).indexOf(new byte[0])); + assertEquals(-1, Bytes.allocate(1).indexOf(new byte[0])); assertEquals(2, Bytes.from(example_bytes_seven).indexOf(new byte[]{(byte) 0xFD, (byte) 0xFF})); assertEquals(-1, Bytes.from(example_bytes_seven).indexOf(new byte[]{(byte) 0xFD, (byte) 0x00})); } + @Test + public void indexOfArrayFromIndex() { + assertEquals(-1, Bytes.allocate(0).indexOf(new byte[]{(byte) 0xFD}, 0)); + assertEquals(-1, Bytes.allocate(1).indexOf(new byte[0], 0)); + assertEquals(-1, Bytes.from(example_bytes_seven).indexOf(new byte[]{(byte) 0xFD, (byte) 0xFF}, 8)); + assertEquals(2, Bytes.from(new byte[]{(byte) 0x8E, (byte) 0xD1, (byte) 0x8E, (byte) 0xD1, 0x12, (byte) 0xAF, (byte) 0x78, 0x09, 0x1E, (byte) 0xD1, (byte) 0xFD, (byte) 0xAA, 0x12}).indexOf(new byte[]{(byte) 0x8E, (byte) 0xD1}, 1)); + } + + @Test + public void indexOfArrayFromIndexToIndex() { + assertEquals(4, Bytes.from(example_bytes_seven).indexOf(new byte[] { (byte) 0x1E, (byte) 0xAF }, 0, 7)); + assertEquals(4, Bytes.from(example_bytes_seven).indexOf(new byte[] { (byte) 0x1E, (byte) 0xAF }, 3, 5)); + assertEquals(4, Bytes.from(example_bytes_seven).indexOf(new byte[] { (byte) 0x1E, (byte) 0xAF, (byte) 0xED }, 4, 5)); + assertEquals(-1, Bytes.from(example_bytes_seven).indexOf(new byte[] { (byte) 0x1E, (byte) 0xAF }, 0, 3)); + assertEquals(-1, Bytes.from(example_bytes_seven).indexOf(new byte[] { (byte) 0x1E, (byte) 0xAF }, 6, 7)); + assertEquals(-1, Bytes.from(example_bytes_seven).indexOf(new byte[] { (byte) 0xCA, (byte) 0xFE }, 0, 7)); + } + + @Test + public void startsWidth() { + assertFalse(Bytes.allocate(0).startsWith(new byte[1])); + assertTrue(Bytes.allocate(1).startsWith(new byte[1])); + assertTrue(Bytes.allocate(128).startsWith(new byte[1])); + assertTrue(Bytes.allocate(128).startsWith(new byte[128])); + assertTrue(Bytes.from(example_bytes_seven).startsWith(new byte[]{0x4A})); + assertTrue(Bytes.from(example_bytes_seven).startsWith(new byte[]{0x4A, (byte) 0x94})); + assertTrue(Bytes.from(example_bytes_seven).startsWith(new byte[]{0x4A, (byte) 0x94, (byte) 0xFD})); + assertFalse(Bytes.from(example_bytes_seven).startsWith(new byte[]{0x4A, (byte) 0x94, (byte) 0x1D})); + assertTrue(Bytes.from(example_bytes_seven).startsWith(Bytes.from(example_bytes_seven).array())); + assertFalse(Bytes.from(example_bytes_seven).startsWith(Bytes.from(example_bytes_seven).append(0x30).array())); + } + + @Test + public void endsWith() { + assertTrue(Bytes.from(example_bytes_seven).endsWith(new byte[]{(byte) 0xFF, 0x1E, (byte) 0xAF, (byte) 0xED})); + assertTrue(Bytes.from(example_bytes_seven).endsWith(new byte[]{0x1E, (byte) 0xAF, (byte) 0xED})); + assertTrue(Bytes.from(example_bytes_seven).endsWith(new byte[]{(byte) 0xAF, (byte) 0xED})); + assertTrue(Bytes.from(example_bytes_seven).endsWith(new byte[]{(byte) 0xED})); + assertFalse(Bytes.allocate(0).endsWith(new byte[1])); + assertTrue(Bytes.allocate(1).endsWith(new byte[1])); + assertTrue(Bytes.allocate(128).endsWith(new byte[1])); + assertTrue(Bytes.allocate(128).endsWith(new byte[128])); + } + @Test public void lastIndexOf() { assertEquals(-1, Bytes.allocate(0).lastIndexOf((byte) 0xFD)); @@ -215,6 +291,12 @@ public void bitAt() { fail(); } catch (IndexOutOfBoundsException ignored) { } + + Bytes bytes = Bytes.wrap(new byte[]{1, 0, 2, 0}).byteOrder(ByteOrder.LITTLE_ENDIAN); + assertTrue(bytes.bitAt(0)); + assertTrue(bytes.bitAt(17)); + assertFalse(bytes.bitAt(8)); + assertFalse(bytes.bitAt(31)); } @Test @@ -326,6 +408,15 @@ public void intAt() { } } + @Test + public void intAtLittleEndian() { + assertEquals(16777216, Bytes.wrap(new byte[]{0, 0, 0, 1}, ByteOrder.LITTLE_ENDIAN).intAt(0)); + assertEquals(1, Bytes.wrap(new byte[]{0, 0, 0, 1}, ByteOrder.BIG_ENDIAN).intAt(0)); + assertEquals(134217728, Bytes.wrap(new byte[]{0, 0, 0, 0b00001000}, ByteOrder.LITTLE_ENDIAN).intAt(0)); + assertEquals(524288, Bytes.wrap(new byte[]{0, 0, 0b00001000, 0}, ByteOrder.LITTLE_ENDIAN).intAt(0)); + assertEquals(8388608, Bytes.wrap(new byte[]{0, 0, (byte) 0b10000000, 0}, ByteOrder.LITTLE_ENDIAN).intAt(0)); + } + @Test public void longAt() { assertEquals(0, Bytes.allocate(8).longAt(0)); @@ -349,6 +440,16 @@ public void longAt() { } } + @Test + public void longAtLittleEndian() { + assertEquals(72057594037927936L, Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 0, 1}, ByteOrder.LITTLE_ENDIAN).longAt(0)); + assertEquals(1, Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 0, 1}, ByteOrder.BIG_ENDIAN).longAt(0)); + assertEquals(576460752303423488L, Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 0, 0b00001000}, ByteOrder.LITTLE_ENDIAN).longAt(0)); + assertEquals(2251799813685248L, Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 0b00001000, 0}, ByteOrder.LITTLE_ENDIAN).longAt(0)); + assertEquals(36028797018963968L, Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, (byte) 0b10000000, 0}, ByteOrder.LITTLE_ENDIAN).longAt(0)); + assertEquals(549755813888L, Bytes.wrap(new byte[]{0, 0, 0, 0, (byte) 0b10000000, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).longAt(0)); + } + @Test public void primitiveAtLittleEndian() { assertEquals(576460752303423488L, Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 0, 0b00001000}).byteOrder(ByteOrder.LITTLE_ENDIAN).longAt(0)); //2^59 @@ -375,6 +476,30 @@ public void count() { assertEquals(1, Bytes.from(example_bytes_seven).count((byte) 0xAF)); } + @Test + public void countByteArray() { + assertEquals(0, Bytes.allocate(0).count(new byte[0])); + assertEquals(0, Bytes.allocate(1).count(new byte[0])); + assertEquals(0, Bytes.allocate(128).count(new byte[0])); + assertEquals(128, Bytes.allocate(128).count(new byte[]{0})); + assertEquals(3, Bytes.from(example_bytes_twentyfour).count(new byte[]{(byte) 0xFD})); + assertEquals(3, Bytes.from(example_bytes_twentyfour).count(new byte[]{(byte) 0xD1})); + assertEquals(0, Bytes.from(example_bytes_twentyfour).count(new byte[]{(byte) 0x22})); + assertEquals(1, Bytes.from(example_bytes_eight).count(new byte[]{(byte) 0xAF})); + assertEquals(0, Bytes.from(example_bytes_eight).count(new byte[]{(byte) 0xAF, 0x00})); + assertEquals(0, Bytes.from(example_bytes_eight).count(new byte[]{(byte) 0xED})); + assertEquals(0, Bytes.from(example_bytes_eight).count(new byte[]{(byte) 0x22})); + assertEquals(2, Bytes.from(new byte[]{0, 1, 2, 3, 0, 1, 0}).count(new byte[]{0, 1})); + assertEquals(1, Bytes.from(new byte[]{0, 1, 2, 3, 0, 1, 0}).count(new byte[]{0, 1, 2})); + assertEquals(1, Bytes.from(new byte[]{0, 1, 2, 3, 0, 1, 0}).count(new byte[]{0, 1, 2, 3})); + assertEquals(0, Bytes.from(new byte[]{0, 1, 2, 3, 0, 1, 0}).count(new byte[]{0, 1, 2, 0})); + } + + @Test(expected = NullPointerException.class) + public void countByteArrayShouldCheckArgument() { + Bytes.allocate(1).count(null); + } + @Test public void entropy() { assertEquals(0, Bytes.allocate(0).entropy(), 0.1d); @@ -416,7 +541,7 @@ public void readOnly() { try { Bytes.from(example_bytes_twentyfour).readOnly().array(); fail(); - } catch (ReadOnlyBufferException e) { + } catch (ReadOnlyBufferException ignored) { } Bytes b = Bytes.from(example_bytes_twentyfour).readOnly(); diff --git a/src/test/java/at/favre/lib/bytes/BytesParseAndEncodingTest.java b/src/test/java/at/favre/lib/bytes/BytesParseAndEncodingTest.java index ecfe522..4af006f 100644 --- a/src/test/java/at/favre/lib/bytes/BytesParseAndEncodingTest.java +++ b/src/test/java/at/favre/lib/bytes/BytesParseAndEncodingTest.java @@ -31,26 +31,52 @@ public class BytesParseAndEncodingTest extends ABytesTest { private byte[] encodingExample; @Before - public void setUp() throws Exception { + public void setUp() { encodingExample = new byte[]{0x4A, (byte) 0x94, (byte) 0xFD, (byte) 0xFF, 0x1E, (byte) 0xAF, (byte) 0xED}; } @Test - public void parseHex() throws Exception { + public void parseHex() { byte[] defaultArray = new byte[]{(byte) 0xA0, (byte) 0xE1}; assertArrayEquals(defaultArray, Bytes.parseHex("0xA0E1").array()); assertArrayEquals(defaultArray, Bytes.parseHex("A0E1").array()); assertArrayEquals(defaultArray, Bytes.parseHex("a0e1").array()); assertArrayEquals(defaultArray, Bytes.parseHex(Bytes.parseHex("A0E1").encodeHex()).array()); + assertArrayEquals(defaultArray, Bytes.parseHex(Bytes.parseHex("a0E1").encodeHex()).array()); + } + + @Test + public void parseHexOddNumberStrings() { + assertArrayEquals(new byte[]{(byte) 0x00}, Bytes.parseHex("0").array()); + assertArrayEquals(new byte[]{(byte) 0x0E}, Bytes.parseHex("E").array()); + assertArrayEquals(new byte[]{(byte) 0x0A, (byte) 0x0E}, Bytes.parseHex("A0E").array()); + assertArrayEquals(new byte[]{(byte) 0x03, (byte) 0xEA, (byte) 0x0E}, Bytes.parseHex("3EA0E").array()); + assertArrayEquals(new byte[]{(byte) 0x00, (byte) 0xF3, (byte) 0xEA, (byte) 0x0E}, Bytes.parseHex("0F3EA0E").array()); + assertArrayEquals(new byte[]{(byte) 0x0A, (byte) 0xD0, (byte) 0xF3, (byte) 0xEA, (byte) 0x0E}, Bytes.parseHex("AD0F3EA0E").array()); + } + + @Test(expected = IllegalArgumentException.class) + public void parseHexIllegalChars() { + Bytes.parseHex("AX"); + } + + @Test(expected = IllegalArgumentException.class) + public void parseHexIllegalChars2() { + Bytes.parseHex("XX"); } @Test(expected = IllegalArgumentException.class) - public void parseHexInvalid() throws Exception { - Bytes.parseHex("A0E"); + public void parseHexIllegalChars3() { + Bytes.parseHex("Y"); + } + + @Test(expected = IllegalArgumentException.class) + public void parseHexIllegalChars4() { + Bytes.parseHex("F3El0E"); } @Test - public void encodeHex() throws Exception { + public void encodeHex() { byte[] defaultArray = new byte[]{(byte) 0xA0, (byte) 0xE1}; assertEquals("a0e1", Bytes.from(defaultArray).encodeHex()); assertEquals("A0E1", Bytes.from(defaultArray).encodeHex(true)); @@ -59,60 +85,139 @@ public void encodeHex() throws Exception { } @Test - public void parseBase64() throws Exception { + public void parseBase64() { assertArrayEquals(encodingExample, Bytes.parseBase64("SpT9/x6v7Q==").array()); + assertArrayEquals(encodingExample, Bytes.parseBase64("SpT9/x6v7Q").array()); + assertArrayEquals(encodingExample, Bytes.parseBase64("SpT9_x6v7Q==").array()); + assertArrayEquals(encodingExample, Bytes.parseBase64("SpT9_x6v7Q").array()); } @Test(expected = IllegalArgumentException.class) - public void parseBase64Invalid() throws Exception { + public void parseBase64Invalid() { Bytes.parseBase64("☕"); } @Test - public void encodeBase64() throws Exception { + public void encodeBase64() { + assertEquals("", Bytes.from(new byte[0]).encodeBase64()); + assertEquals("AA==", Bytes.from(new byte[1]).encodeBase64()); assertEquals("SpT9/x6v7Q==", Bytes.from(encodingExample).encodeBase64()); } @Test - public void encodeBinary() throws Exception { + public void encodeBase64Url() { + assertEquals("", Bytes.from(new byte[0]).encodeBase64Url()); + assertEquals("AA==", Bytes.from(new byte[1]).encodeBase64Url()); + assertEquals("SpT9_x6v7Q==", Bytes.from(encodingExample).encodeBase64Url()); + } + + @Test + public void encodeBase64WithConfig() { + assertEquals("", Bytes.from(new byte[0]).encodeBase64(true, true)); + assertEquals("AA==", Bytes.from(new byte[1]).encodeBase64(true, true)); + assertEquals("SpT9_x6v7Q==", Bytes.from(encodingExample).encodeBase64(true, true)); + + assertEquals("", Bytes.from(new byte[0]).encodeBase64(true, false)); + assertEquals("AA", Bytes.from(new byte[1]).encodeBase64(true, false)); + assertEquals("SpT9_x6v7Q", Bytes.from(encodingExample).encodeBase64(true, false)); + + assertEquals("", Bytes.from(new byte[0]).encodeBase64(false, true)); + assertEquals("AA==", Bytes.from(new byte[1]).encodeBase64(false, true)); + assertEquals("SpT9/x6v7Q==", Bytes.from(encodingExample).encodeBase64(false, true)); + + assertEquals("", Bytes.from(new byte[0]).encodeBase64(false, false)); + assertEquals("AA", Bytes.from(new byte[1]).encodeBase64(false, false)); + assertEquals("SpT9/x6v7Q", Bytes.from(encodingExample).encodeBase64(false, false)); + } + + @Test + public void parseBase32() { + assertArrayEquals(encodingExample, Bytes.parseBase32("JKKP37Y6V7WQ====").array()); + } + + @Test + public void encodeBase32() { + assertEquals("JKKP37Y6V7WQ====", Bytes.from(encodingExample).encodeBase32()); + } + + @Test + public void encodeBinary() { byte[] defaultArray = new byte[]{(byte) 0xA0, (byte) 0xE1}; assertEquals("1010000011100001", Bytes.from(defaultArray).encodeBinary()); assertEquals("1001010100101001111110111111111000111101010111111101101", Bytes.from(encodingExample).encodeBinary()); } @Test - public void parseOctal() throws Exception { + public void parseOctal() { assertArrayEquals(encodingExample, Bytes.parseOctal("1124517677707527755").array()); } @Test - public void encodeOctal() throws Exception { + public void encodeOctal() { byte[] defaultArray = new byte[]{(byte) 0xA0, (byte) 0xE1}; assertEquals("120341", Bytes.from(defaultArray).encodeOctal()); assertEquals("1124517677707527755", Bytes.from(encodingExample).encodeOctal()); } @Test - public void parseDec() throws Exception { + public void parseDec() { assertArrayEquals(encodingExample, Bytes.parseDec("20992966904426477").array()); } @Test - public void encodeDec() throws Exception { + public void encodeDec() { byte[] defaultArray = new byte[]{(byte) 0xA0, (byte) 0xE1}; assertEquals("41185", Bytes.from(defaultArray).encodeDec()); assertEquals("20992966904426477", Bytes.from(encodingExample).encodeDec()); } @Test - public void parseBase36() throws Exception { + public void parseBase36() { assertArrayEquals(encodingExample, Bytes.parseBase36("5qpdvuwjvu5").array()); } @Test - public void encodeBase36() throws Exception { + public void encodeBase36() { byte[] defaultArray = new byte[]{(byte) 0xA0, (byte) 0xE1, (byte) 0x13}; assertEquals("69zbn", Bytes.from(defaultArray).encodeBase36()); assertEquals("5qpdvuwjvu5", Bytes.from(encodingExample).encodeBase36()); } -} \ No newline at end of file + + @Test + public void parseRadix() { + assertArrayEquals(encodingExample, Bytes.parseRadix("1001010100101001111110111111111000111101010111111101101", 2).array()); + assertArrayEquals(encodingExample, Bytes.parseRadix("10202221221221000222101012210121012", 3).array()); + assertArrayEquals(encodingExample, Bytes.parseRadix("1022211033313333013222333231", 4).array()); + assertArrayEquals(encodingExample, Bytes.parseRadix("134003042232210013121402", 5).array()); + assertArrayEquals(encodingExample, Bytes.parseRadix("542412151505231515005", 6).array()); + assertArrayEquals(encodingExample, Bytes.parseRadix("1124517677707527755", 8).array()); + assertArrayEquals(encodingExample, Bytes.parseRadix("20992966904426477", 10).array()); + assertArrayEquals(encodingExample, Bytes.parseRadix("4a94fdff1eafed", 16).array()); + assertArrayEquals(encodingExample, Bytes.parseRadix("5iibpp5dgpgp", 26).array()); + assertArrayEquals(encodingExample, Bytes.parseRadix("5qpdvuwjvu5", 36).array()); + } + + @Test + public void encodeRadix() { + assertEquals("1001010100101001111110111111111000111101010111111101101", Bytes.from(encodingExample).encodeRadix(2)); + assertEquals("10202221221221000222101012210121012", Bytes.from(encodingExample).encodeRadix(3)); + assertEquals("1022211033313333013222333231", Bytes.from(encodingExample).encodeRadix(4)); + assertEquals("134003042232210013121402", Bytes.from(encodingExample).encodeRadix(5)); + assertEquals("542412151505231515005", Bytes.from(encodingExample).encodeRadix(6)); + assertEquals("1124517677707527755", Bytes.from(encodingExample).encodeRadix(8)); + assertEquals("20992966904426477", Bytes.from(encodingExample).encodeRadix(10)); + assertEquals("4a94fdff1eafed", Bytes.from(encodingExample).encodeRadix(16)); + assertEquals("5iibpp5dgpgp", Bytes.from(encodingExample).encodeRadix(26)); + assertEquals("5qpdvuwjvu5", Bytes.from(encodingExample).encodeRadix(36)); + } + + @Test(expected = IllegalArgumentException.class) + public void encodeRadixIllegalTooHigh() { + Bytes.from(encodingExample).encodeRadix(37); + } + + @Test(expected = IllegalArgumentException.class) + public void encodeRadixIllegalTooLow() { + Bytes.from(encodingExample).encodeRadix(1); + } +} diff --git a/src/test/java/at/favre/lib/bytes/BytesSharedDataConverterTest.java b/src/test/java/at/favre/lib/bytes/BytesSharedDataConverterTest.java index b429a11..359a96c 100644 --- a/src/test/java/at/favre/lib/bytes/BytesSharedDataConverterTest.java +++ b/src/test/java/at/favre/lib/bytes/BytesSharedDataConverterTest.java @@ -30,7 +30,7 @@ public class BytesSharedDataConverterTest extends ABytesTest { @Test - public void array() throws Exception { + public void array() { assertArrayEquals(new byte[0], Bytes.from(new byte[0]).array()); assertArrayEquals(example_bytes_one, Bytes.from(example_bytes_one).array()); assertArrayEquals(example_bytes_two, Bytes.from(example_bytes_two).array()); @@ -41,7 +41,7 @@ public void array() throws Exception { } @Test - public void bigInteger() throws Exception { + public void bigInteger() { assertArrayEquals(example_bytes_one, Bytes.from(example_bytes_one).toBigInteger().toByteArray()); assertArrayEquals(example_bytes_two, Bytes.from(example_bytes_two).toBigInteger().toByteArray()); assertArrayEquals(example_bytes_four, Bytes.from(example_bytes_four).toBigInteger().toByteArray()); @@ -51,7 +51,7 @@ public void bigInteger() throws Exception { } @Test - public void buffer() throws Exception { + public void buffer() { assertEquals(ByteBuffer.wrap(new byte[0]), Bytes.from(new byte[0]).buffer()); assertEquals(ByteBuffer.wrap(example_bytes_one), Bytes.from(example_bytes_one).buffer()); assertEquals(ByteBuffer.wrap(example_bytes_two), Bytes.from(example_bytes_two).buffer()); @@ -62,7 +62,7 @@ public void buffer() throws Exception { } @Test - public void duplicate() throws Exception { + public void duplicate() { Bytes b = Bytes.from(example_bytes_sixteen); Bytes b2 = b.duplicate(); assertNotSame(b, b2); @@ -70,10 +70,10 @@ public void duplicate() throws Exception { } @Test - public void inputStream() throws Exception { - assertArrayEquals(example_bytes_one, Util.readFromStream(Bytes.from(example_bytes_one).inputStream())); - assertArrayEquals(example_bytes_two, Util.readFromStream(Bytes.from(example_bytes_two).inputStream())); - assertArrayEquals(example_bytes_four, Util.readFromStream(Bytes.from(example_bytes_four).inputStream())); - assertArrayEquals(example_bytes_sixteen, Util.readFromStream(Bytes.from(example_bytes_sixteen).inputStream())); + public void inputStream() { + assertArrayEquals(example_bytes_one, Util.File.readFromStream(Bytes.from(example_bytes_one).inputStream(), -1)); + assertArrayEquals(example_bytes_two, Util.File.readFromStream(Bytes.from(example_bytes_two).inputStream(), -1)); + assertArrayEquals(example_bytes_four, Util.File.readFromStream(Bytes.from(example_bytes_four).inputStream(), -1)); + assertArrayEquals(example_bytes_sixteen, Util.File.readFromStream(Bytes.from(example_bytes_sixteen).inputStream(), -1)); } -} \ No newline at end of file +} diff --git a/src/test/java/at/favre/lib/bytes/BytesToConvertOtherTypesTest.java b/src/test/java/at/favre/lib/bytes/BytesToConvertOtherTypesTest.java index 90bf1da..efa1559 100644 --- a/src/test/java/at/favre/lib/bytes/BytesToConvertOtherTypesTest.java +++ b/src/test/java/at/favre/lib/bytes/BytesToConvertOtherTypesTest.java @@ -23,6 +23,7 @@ import org.junit.Test; +import java.nio.ByteOrder; import java.util.List; import static org.junit.Assert.*; @@ -45,6 +46,7 @@ private void checkArray(byte[] array) { for (int i = 0; i < array.length; i++) { assertEquals(byteArray[i], Byte.valueOf(array[i])); } + assertArrayEquals(byteArray, Bytes.from(array).toBoxedArray()); } @Test @@ -84,7 +86,7 @@ public void toByte() { try { Bytes.from(example_bytes_two).toByte(); fail(); - } catch (IllegalStateException ignored) { + } catch (IllegalArgumentException ignored) { } } @@ -97,7 +99,7 @@ public void toUnsignedByte() { try { Bytes.from(example_bytes_two).toUnsignedByte(); fail(); - } catch (IllegalStateException ignored) { + } catch (IllegalArgumentException ignored) { } } @@ -110,12 +112,12 @@ public void toChar() { try { Bytes.from(new byte[3]).toChar(); fail(); - } catch (IllegalStateException ignored) { + } catch (IllegalArgumentException ignored) { } try { Bytes.from(new byte[1]).toChar(); fail(); - } catch (IllegalStateException ignored) { + } catch (IllegalArgumentException ignored) { } } @@ -128,12 +130,12 @@ public void toShort() { try { Bytes.from(new byte[3]).toShort(); fail(); - } catch (IllegalStateException ignored) { + } catch (IllegalArgumentException ignored) { } try { Bytes.from(new byte[1]).toShort(); fail(); - } catch (IllegalStateException ignored) { + } catch (IllegalArgumentException ignored) { } } @@ -151,12 +153,12 @@ public void toInt() { try { Bytes.from(new byte[5]).toInt(); fail(); - } catch (IllegalStateException ignored) { + } catch (IllegalArgumentException ignored) { } try { Bytes.from(new byte[3]).toInt(); fail(); - } catch (IllegalStateException ignored) { + } catch (IllegalArgumentException ignored) { } } @@ -171,12 +173,12 @@ public void toLong() { try { Bytes.from(new byte[9]).toLong(); fail(); - } catch (IllegalStateException ignored) { + } catch (IllegalArgumentException ignored) { } try { Bytes.from(new byte[7]).toLong(); fail(); - } catch (IllegalStateException ignored) { + } catch (IllegalArgumentException ignored) { } } @@ -191,12 +193,12 @@ public void toFloat() { try { Bytes.from(new byte[5]).toFloat(); fail(); - } catch (IllegalStateException ignored) { + } catch (IllegalArgumentException ignored) { } try { Bytes.from(new byte[3]).toFloat(); fail(); - } catch (IllegalStateException ignored) { + } catch (IllegalArgumentException ignored) { } } @@ -211,12 +213,231 @@ public void toDouble() { try { Bytes.from(new byte[9]).toDouble(); fail(); - } catch (IllegalStateException ignored) { + } catch (IllegalArgumentException ignored) { } try { Bytes.from(new byte[7]).toDouble(); fail(); - } catch (IllegalStateException ignored) { + } catch (IllegalArgumentException ignored) { } } -} \ No newline at end of file + + @Test(expected = IllegalArgumentException.class) + public void testToUUIDToLong() { + Bytes.random(17).toUUID(); + } + + @Test(expected = IllegalArgumentException.class) + public void testToUUIDToShort() { + Bytes.random(15).toUUID(); + } + + @Test(expected = IllegalArgumentException.class) + public void testToUUIDEmpty() { + Bytes.allocate(0).toUUID(); + } + + @Test + public void testToIntArray() { + assertArrayEquals(new int[]{1}, Bytes.wrap(new byte[]{0, 0, 0, 1}).toIntArray()); + assertArrayEquals(new int[]{257}, Bytes.wrap(new byte[]{0, 0, 1, 1}).toIntArray()); + assertArrayEquals(new int[]{65_793}, Bytes.wrap(new byte[]{0, 1, 1, 1}).toIntArray()); + assertArrayEquals(new int[]{16_843_009}, Bytes.wrap(new byte[]{1, 1, 1, 1}).toIntArray()); + assertArrayEquals(new int[]{571_211_845}, Bytes.wrap(new byte[]{34, 12, 0, 69}).toIntArray()); + assertArrayEquals(new int[]{1_290_429_439}, Bytes.wrap(new byte[]{76, (byte) 234, 99, (byte) 255}).toIntArray()); + + assertArrayEquals(new int[]{1, 1}, Bytes.wrap(new byte[]{0, 0, 0, 1, 0, 0, 0, 1}).toIntArray()); + assertArrayEquals(new int[]{257, 1}, Bytes.wrap(new byte[]{0, 0, 1, 1, 0, 0, 0, 1}).toIntArray()); + assertArrayEquals(new int[]{257, 65_793, 1}, Bytes.wrap(new byte[]{0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1}).toIntArray()); + } + + @Test(expected = IllegalArgumentException.class) + public void testToIntArrayNotMod4Was5Byte() { + Bytes.wrap(new byte[]{1, 0, 0, 0, 1}).toIntArray(); + } + + @Test(expected = IllegalArgumentException.class) + public void testToIntArrayNotMod4Only3Byte() { + Bytes.wrap(new byte[]{0, 0, 1}).toIntArray(); + } + + @Test + public void testToIntEmptyArray() { + assertArrayEquals(new int[0], Bytes.empty().toIntArray()); + } + + @Test + public void testToIntArrayLittleEndian() { + assertArrayEquals(new int[]{1}, Bytes.wrap(new byte[]{1, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toIntArray()); + assertArrayEquals(new int[]{257}, Bytes.wrap(new byte[]{1, 1, 0, 0}, ByteOrder.LITTLE_ENDIAN).toIntArray()); + assertArrayEquals(new int[]{1_290_429_439}, Bytes.wrap(new byte[]{(byte) 255, 99, (byte) 234, 76}, ByteOrder.LITTLE_ENDIAN).toIntArray()); + + assertArrayEquals(new int[]{1, 1}, Bytes.wrap(new byte[]{1, 0, 0, 0, 1, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toIntArray()); + assertArrayEquals(new int[]{257, 1}, Bytes.wrap(new byte[]{1, 1, 0, 0, 1, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toIntArray()); + assertArrayEquals(new int[]{257, 65_793, 1}, Bytes.wrap(new byte[]{1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toIntArray()); + } + + @Test + public void testToLongArray() { + assertArrayEquals(new long[]{1}, Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 0, 1}).toLongArray()); + assertArrayEquals(new long[]{257}, Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 1, 1}).toLongArray()); + assertArrayEquals(new long[]{3329575617569751109L}, Bytes.wrap(new byte[]{46, 53, 7, 98, 34, 12, 0, 69}).toLongArray()); + assertArrayEquals(new long[]{-7124130559744646145L}, Bytes.wrap(new byte[]{(byte) 157, 34, 1, 0, 76, (byte) 234, 99, (byte) 255}).toLongArray()); + + assertArrayEquals(new long[]{1, 1}, Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1}).toLongArray()); + assertArrayEquals(new long[]{257, 1}, Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1}).toLongArray()); + assertArrayEquals(new long[]{1099511628033L, 281474976776449L, 1}, Bytes.wrap(new byte[]{ + 0, 0, 1, 0, 0, 0, 1, 1, + 0, 1, 0, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 1}).toLongArray()); + } + + @Test(expected = IllegalArgumentException.class) + public void testToLongArrayNotMod4Was9Byte() { + Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 1}).toLongArray(); + } + + @Test(expected = IllegalArgumentException.class) + public void testToLongArrayNotMod4Only7Byte() { + Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 1}).toLongArray(); + } + + @Test + public void testToLongEmptyArray() { + assertArrayEquals(new long[0], Bytes.empty().toLongArray()); + } + + @Test + public void testToLongArrayLittleEndian() { + assertArrayEquals(new long[]{1}, Bytes.wrap(new byte[]{1, 0, 0, 0, 0, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toLongArray()); + assertArrayEquals(new long[]{257}, Bytes.wrap(new byte[]{1, 1, 0, 0, 0, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toLongArray()); + assertArrayEquals(new long[]{3329575617569751109L}, Bytes.wrap(new byte[]{69, 0, 12, 34, 98, 7, 53, 46}, ByteOrder.LITTLE_ENDIAN).toLongArray()); + assertArrayEquals(new long[]{-7124130559744646145L}, Bytes.wrap(new byte[]{(byte) 255, 99, (byte) 234, 76, 0, 1, 34, (byte) 157}, ByteOrder.LITTLE_ENDIAN).toLongArray()); + + assertArrayEquals(new long[]{1, 1}, Bytes.wrap(new byte[]{1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toLongArray()); + assertArrayEquals(new long[]{257, 1}, Bytes.wrap(new byte[]{1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toLongArray()); + assertArrayEquals(new long[]{1099511628033L, 281474976776449L, 1}, Bytes.wrap(new byte[]{ + 1, 1, 0, 0, 0, 1, 0, 0, + 1, 1, 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 0, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toLongArray()); + } + + @Test + public void testToFloatArray() { + assertArrayEquals(new float[]{1.4E-45f}, Bytes.wrap(new byte[]{0, 0, 0, 1}).toFloatArray(), 0.01f); + assertArrayEquals(new float[]{3.6E-43f}, Bytes.wrap(new byte[]{0, 0, 1, 1}).toFloatArray(), 0.01f); + assertArrayEquals(new float[]{9.2196E-41f}, Bytes.wrap(new byte[]{0, 1, 1, 1}).toFloatArray(), 0.01f); + assertArrayEquals(new float[]{2.3694278E-38f}, Bytes.wrap(new byte[]{1, 1, 1, 1}).toFloatArray(), 0.01f); + assertArrayEquals(new float[]{1.897368E-18f}, Bytes.wrap(new byte[]{34, 12, 0, 69}).toFloatArray(), 0.01f); + assertArrayEquals(new float[]{1.22888184E8f}, Bytes.wrap(new byte[]{76, (byte) 234, 99, (byte) 255}).toFloatArray(), 0.01f); + + assertArrayEquals(new float[]{1.4E-45f, 1.4E-45f}, Bytes.wrap(new byte[]{0, 0, 0, 1, 0, 0, 0, 1}).toFloatArray(), 0.01f); + assertArrayEquals(new float[]{3.6E-43f, 1.4E-45f}, Bytes.wrap(new byte[]{0, 0, 1, 1, 0, 0, 0, 1}).toFloatArray(), 0.01f); + assertArrayEquals(new float[]{3.6E-43f, 9.2196E-41f, 1.4E-45f}, Bytes.wrap(new byte[]{0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1}).toFloatArray(), 0.01f); + } + + @Test(expected = IllegalArgumentException.class) + public void testToFloatArrayNotMod4Was5Byte() { + Bytes.wrap(new byte[]{1, 0, 0, 0, 1}).toFloatArray(); + } + + @Test(expected = IllegalArgumentException.class) + public void testToFloatArrayNotMod4Only3Byte() { + Bytes.wrap(new byte[]{0, 0, 1}).toFloatArray(); + } + + @Test + public void testToFloatEmptyArray() { + assertArrayEquals(new float[0], Bytes.empty().toFloatArray(), 0.01f); + } + + @Test + public void testToFloatArrayLittleEndian() { + assertArrayEquals(new float[]{1.4E-45f}, Bytes.wrap(new byte[]{1, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toFloatArray(), 0.01f); + assertArrayEquals(new float[]{3.6E-43f}, Bytes.wrap(new byte[]{1, 1, 0, 0}, ByteOrder.LITTLE_ENDIAN).toFloatArray(), 0.01f); + assertArrayEquals(new float[]{1.22888184E8f}, Bytes.wrap(new byte[]{(byte) 255, 99, (byte) 234, 76}, ByteOrder.LITTLE_ENDIAN).toFloatArray(), 0.01f); + + assertArrayEquals(new float[]{1.4E-45f, 1.4E-45f}, Bytes.wrap(new byte[]{1, 0, 0, 0, 1, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toFloatArray(), 0.01f); + assertArrayEquals(new float[]{3.6E-43f, 1.4E-45f}, Bytes.wrap(new byte[]{1, 1, 0, 0, 1, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toFloatArray(), 0.01f); + assertArrayEquals(new float[]{3.6E-43f, 9.2196E-41f, 1.4E-45f}, Bytes.wrap(new byte[]{1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toFloatArray(), 0.01f); + } + + @Test + public void testToDoubleArray() { + assertArrayEquals(new double[]{1.4E-45}, Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 0, 1}).toDoubleArray(), 0.01); + assertArrayEquals(new double[]{3.6E-43}, Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 1, 1}).toDoubleArray(), 0.01); + assertArrayEquals(new double[]{4.228405109821336E-86}, Bytes.wrap(new byte[]{46, 53, 7, 98, 34, 12, 0, 69}).toDoubleArray(), 0.01); + assertArrayEquals(new double[]{-2.385279556059394E-168}, Bytes.wrap(new byte[]{(byte) 157, 34, 1, 0, 76, (byte) 234, 99, (byte) 255}).toDoubleArray(), 0.01); + + assertArrayEquals(new double[]{1.4E-45, 1.4E-45}, Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1}).toDoubleArray(), 0.01); + assertArrayEquals(new double[]{3.6E-43, 1.4E-45}, Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1}).toDoubleArray(), 0.01); + assertArrayEquals(new double[]{5.43230922614E-312, 1.39067116189206E-309, 1.4E-45}, Bytes.wrap(new byte[]{ + 0, 0, 1, 0, 0, 0, 1, 1, + 0, 1, 0, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 1}).toDoubleArray(), 0.01); + } + + @Test(expected = IllegalArgumentException.class) + public void testToDoubleArrayNotMod4Was9Byte() { + Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 1}).toDoubleArray(); + } + + @Test(expected = IllegalArgumentException.class) + public void testToDoubleArrayNotMod4Only7Byte() { + Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 1}).toDoubleArray(); + } + + @Test + public void testToDoubleEmptyArray() { + assertArrayEquals(new double[0], Bytes.empty().toDoubleArray(), 0.01); + } + + @Test + public void testToDoubleArrayLittleEndian() { + assertArrayEquals(new double[]{1.4E-45}, Bytes.wrap(new byte[]{1, 0, 0, 0, 0, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toDoubleArray(), 0.01); + assertArrayEquals(new double[]{3.6E-43}, Bytes.wrap(new byte[]{1, 1, 0, 0, 0, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toDoubleArray(), 0.01); + assertArrayEquals(new double[]{4.228405109821336E-86}, Bytes.wrap(new byte[]{69, 0, 12, 34, 98, 7, 53, 46}, ByteOrder.LITTLE_ENDIAN).toDoubleArray(), 0.01); + assertArrayEquals(new double[]{-2.385279556059394E-168}, Bytes.wrap(new byte[]{(byte) 255, 99, (byte) 234, 76, 0, 1, 34, (byte) 157}, ByteOrder.LITTLE_ENDIAN).toDoubleArray(), 0.01); + + assertArrayEquals(new double[]{1.4E-45, 1.4E-45}, Bytes.wrap(new byte[]{1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toDoubleArray(), 0.01); + assertArrayEquals(new double[]{3.6E-43f, 1.4E-45}, Bytes.wrap(new byte[]{1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toDoubleArray(), 0.01); + assertArrayEquals(new double[]{5.43230922614E-312, 1.39067116189206E-309, 1.4E-45}, Bytes.wrap(new byte[]{ + 1, 1, 0, 0, 0, 1, 0, 0, + 1, 1, 1, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 0, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN).toDoubleArray(), 0.01); + } + + @Test + public void testToShortArrayEmpty() { + assertArrayEquals(new short[0], Bytes.empty().toShortArray()); + } + + + @Test(expected = IllegalArgumentException.class) + public void testToShortArrayNotMod2Was5Byte() { + Bytes.wrap(new byte[]{0, 0, 0, 0, 1}).toShortArray(); + } + + @Test + public void testToShortArray() { + assertArrayEquals(new short[]{1}, Bytes.wrap(new byte[]{0, 1}).toShortArray()); + assertArrayEquals(new short[]{257}, Bytes.wrap(new byte[]{1, 1}).toShortArray()); + assertArrayEquals(new short[]{32_767}, Bytes.wrap(new byte[]{127, -1}).toShortArray()); + + assertArrayEquals(new short[]{1, 1}, Bytes.wrap(new byte[]{0, 1, 0, 1}).toShortArray()); + assertArrayEquals(new short[]{257, 1}, Bytes.wrap(new byte[]{1, 1, 0, 1}).toShortArray()); + assertArrayEquals(new short[]{257, 32_767, 1}, Bytes.wrap(new byte[]{1, 1, 127, -1, 0, 1}).toShortArray()); + } + + @Test + public void testToShortArrayLittleEndian() { + assertArrayEquals(new short[]{1}, Bytes.wrap(new byte[]{1, 0}, ByteOrder.LITTLE_ENDIAN).toShortArray()); + assertArrayEquals(new short[]{257}, Bytes.wrap(new byte[]{1, 1}, ByteOrder.LITTLE_ENDIAN).toShortArray()); + assertArrayEquals(new short[]{32_767}, Bytes.wrap(new byte[]{-1, 127}, ByteOrder.LITTLE_ENDIAN).toShortArray()); + + assertArrayEquals(new short[]{1, 1}, Bytes.wrap(new byte[]{1, 0, 1, 0}, ByteOrder.LITTLE_ENDIAN).toShortArray()); + assertArrayEquals(new short[]{257, 1}, Bytes.wrap(new byte[]{1, 1, 1, 0}, ByteOrder.LITTLE_ENDIAN).toShortArray()); + assertArrayEquals(new short[]{257, 32_767, 1}, Bytes.wrap(new byte[]{1, 1, -1, 127, 1, 0}, ByteOrder.LITTLE_ENDIAN).toShortArray()); + } + +} diff --git a/src/test/java/at/favre/lib/bytes/BytesTransformTest.java b/src/test/java/at/favre/lib/bytes/BytesTransformTest.java index 703441d..8041380 100644 --- a/src/test/java/at/favre/lib/bytes/BytesTransformTest.java +++ b/src/test/java/at/favre/lib/bytes/BytesTransformTest.java @@ -25,6 +25,7 @@ import java.math.BigInteger; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.Comparator; @@ -41,18 +42,28 @@ public class BytesTransformTest extends ABytesTest { public void append() { assertArrayEquals(new byte[0], Bytes.from(new byte[0]).append(new byte[0]).array()); assertArrayEquals(new byte[1], Bytes.from(new byte[0]).append(new byte[1]).array()); - assertArrayEquals(Util.concat(example_bytes_seven, example_bytes_one), Bytes.from(example_bytes_seven).append(example_bytes_one).array()); - assertArrayEquals(Util.concat(example_bytes_seven, example_bytes_two), Bytes.from(example_bytes_seven).append(example_bytes_two).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_seven, example_bytes_one), Bytes.from(example_bytes_seven).append(example_bytes_one).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_seven, example_bytes_two), Bytes.from(example_bytes_seven).append(example_bytes_two).array()); - assertArrayEquals(Util.concat(example_bytes_eight, example_bytes_sixteen), Bytes.from(example_bytes_eight).append(Bytes.from(example_bytes_sixteen)).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_eight, example_bytes_sixteen), Bytes.from(example_bytes_eight).append(Bytes.from(example_bytes_sixteen)).array()); + } + + @Test + public void appendMultipleByteArrays() { + assertArrayEquals(new byte[0], Bytes.from(new byte[0]).append(new byte[0], new byte[0]).array()); + assertArrayEquals(new byte[]{0x0, 0x01, 0x02, 0x03}, Bytes.from(new byte[]{0x0}).append(new byte[]{0x1}, new byte[]{0x2}, new byte[]{0x3}).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_seven, example_bytes_one, example_bytes_sixteen), Bytes.from(example_bytes_seven).append(example_bytes_one, example_bytes_sixteen).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_sixteen, example_bytes_sixteen, example_bytes_sixteen), Bytes.from(example_bytes_sixteen).append(example_bytes_sixteen, example_bytes_sixteen).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_two, example_bytes_seven, example_bytes_twentyfour, example_bytes_eight), Bytes.from(example_bytes_two).append(example_bytes_seven, example_bytes_twentyfour, example_bytes_eight).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_two, example_bytes_seven, example_bytes_twentyfour, example_bytes_one, example_bytes_sixteen), Bytes.from(example_bytes_two).append(example_bytes_seven, example_bytes_twentyfour).append(example_bytes_one, example_bytes_sixteen).array()); } @Test public void appendNullSafe() { assertArrayEquals(new byte[0], Bytes.from(new byte[0]).appendNullSafe(new byte[0]).array()); assertArrayEquals(new byte[1], Bytes.from(new byte[0]).appendNullSafe(new byte[1]).array()); - assertArrayEquals(Util.concat(example_bytes_seven, example_bytes_one), Bytes.from(example_bytes_seven).appendNullSafe(example_bytes_one).array()); - assertArrayEquals(Util.concat(example_bytes_seven, example_bytes_two), Bytes.from(example_bytes_seven).appendNullSafe(example_bytes_two).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_seven, example_bytes_one), Bytes.from(example_bytes_seven).appendNullSafe(example_bytes_one).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_seven, example_bytes_two), Bytes.from(example_bytes_seven).appendNullSafe(example_bytes_two).array()); assertArrayEquals(new byte[0], Bytes.from(new byte[0]).appendNullSafe(null).array()); assertArrayEquals(new byte[1], Bytes.from(new byte[1]).appendNullSafe(null).array()); assertArrayEquals(example_bytes_seven, Bytes.from(example_bytes_seven).appendNullSafe(null).array()); @@ -60,11 +71,11 @@ public void appendNullSafe() { @Test public void appendPrimitives() { - assertArrayEquals(Util.concat(example_bytes_eight, new byte[]{1}), Bytes.from(example_bytes_eight).append((byte) 1).array()); - assertArrayEquals(Util.concat(example_bytes_eight, ByteBuffer.allocate(2).putChar((char) 1423).array()), Bytes.from(example_bytes_eight).append((char) 1423).array()); - assertArrayEquals(Util.concat(example_bytes_eight, ByteBuffer.allocate(2).putShort((short) 4129).array()), Bytes.from(example_bytes_eight).append((short) 4129).array()); - assertArrayEquals(Util.concat(example_bytes_eight, ByteBuffer.allocate(4).putInt(362173671).array()), Bytes.from(example_bytes_eight).append(362173671).array()); - assertArrayEquals(Util.concat(example_bytes_eight, ByteBuffer.allocate(8).putLong(0x6762173671L).array()), Bytes.from(example_bytes_eight).append(0x6762173671L).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_eight, new byte[]{1}), Bytes.from(example_bytes_eight).append((byte) 1).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_eight, ByteBuffer.allocate(2).putChar((char) 1423).array()), Bytes.from(example_bytes_eight).append((char) 1423).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_eight, ByteBuffer.allocate(2).putShort((short) 4129).array()), Bytes.from(example_bytes_eight).append((short) 4129).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_eight, ByteBuffer.allocate(4).putInt(362173671).array()), Bytes.from(example_bytes_eight).append(362173671).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_eight, ByteBuffer.allocate(8).putLong(0x6762173671L).array()), Bytes.from(example_bytes_eight).append(0x6762173671L).array()); } @Test @@ -89,7 +100,7 @@ public void appendMulti() { byte[] oldByteArray = b.copy().array(); Bytes newByte = Bytes.random(i % 16 + 1); b = b.append(newByte); - assertArrayEquals(Util.concat(oldByteArray, newByte.copy().array()), b.array()); + assertArrayEquals(Util.Byte.concat(oldByteArray, newByte.copy().array()), b.array()); } } @@ -98,9 +109,9 @@ public void resizeGrowLsb() { assertArrayEquals(new byte[8], Bytes.from(new byte[0]).resize(8).array()); assertArrayEquals(example_bytes_one, Bytes.from(example_bytes_one).resize(1).array()); assertArrayEquals(example_bytes_one, Bytes.from(example_bytes_one).resize(1).array()); - assertArrayEquals(Util.concat(new byte[7], example_bytes_one), Bytes.from(example_bytes_one).resize(8).array()); - assertArrayEquals(Util.concat(new byte[1], example_bytes_seven), Bytes.from(example_bytes_seven).resize(8).array()); - assertArrayEquals(Util.concat(new byte[1], example_bytes_sixteen), Bytes.from(example_bytes_sixteen).resize(17).array()); + assertArrayEquals(Util.Byte.concat(new byte[7], example_bytes_one), Bytes.from(example_bytes_one).resize(8).array()); + assertArrayEquals(Util.Byte.concat(new byte[1], example_bytes_seven), Bytes.from(example_bytes_seven).resize(8).array()); + assertArrayEquals(Util.Byte.concat(new byte[1], example_bytes_sixteen), Bytes.from(example_bytes_sixteen).resize(17).array()); } @Test @@ -108,9 +119,9 @@ public void resizeGrowMsb() { assertArrayEquals(new byte[8], Bytes.from(new byte[0]).resize(8, BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_ZERO_INDEX).array()); assertArrayEquals(example_bytes_one, Bytes.from(example_bytes_one).resize(1, BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_ZERO_INDEX).array()); assertArrayEquals(example_bytes_one, Bytes.from(example_bytes_one).resize(1, BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_ZERO_INDEX).array()); - assertArrayEquals(Util.concat(example_bytes_one, new byte[7]), Bytes.from(example_bytes_one).resize(8, BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_ZERO_INDEX).array()); - assertArrayEquals(Util.concat(example_bytes_seven, new byte[1]), Bytes.from(example_bytes_seven).resize(8, BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_ZERO_INDEX).array()); - assertArrayEquals(Util.concat(example_bytes_sixteen, new byte[1]), Bytes.from(example_bytes_sixteen).resize(17, BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_ZERO_INDEX).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_one, new byte[7]), Bytes.from(example_bytes_one).resize(8, BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_ZERO_INDEX).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_seven, new byte[1]), Bytes.from(example_bytes_seven).resize(8, BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_ZERO_INDEX).array()); + assertArrayEquals(Util.Byte.concat(example_bytes_sixteen, new byte[1]), Bytes.from(example_bytes_sixteen).resize(17, BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_ZERO_INDEX).array()); } @Test @@ -172,15 +183,20 @@ public void xor() { try { Bytes.from(example_bytes_seven).xor(example_bytes_eight); fail(); - } catch (Exception e) { + } catch (Exception ignored) { } } @Test public void or() { assertArrayEquals(new byte[0], Bytes.from(new byte[0]).or(new byte[0]).array()); + assertArrayEquals(new byte[]{1}, Bytes.from(new byte[]{1}).or(new byte[]{0}).array()); assertArrayEquals(new byte[1], Bytes.from(new byte[1]).or(new byte[1]).array()); + assertArrayEquals(new byte[0], Bytes.from(new byte[0]).or(Bytes.wrap(new byte[0])).array()); + assertArrayEquals(new byte[]{1}, Bytes.from(new byte[]{1}).or(Bytes.wrap(new byte[]{0})).array()); + assertArrayEquals(new byte[1], Bytes.from(new byte[1]).or(Bytes.wrap(new byte[1])).array()); + assertArrayEquals(example_bytes_one, Bytes.from(example_bytes_one).or(new byte[1]).array()); assertArrayEquals(example_bytes_two, Bytes.from(example_bytes_two).or(new byte[2]).array()); assertArrayEquals(example_bytes_sixteen, Bytes.from(example_bytes_sixteen).or(new byte[16]).array()); @@ -265,16 +281,26 @@ public void shuffleTest() { } @Test - public void sortTest() { - byte[] sorted = new byte[]{0, 1, 2, 3, 4, 5, 6}; + public void sortSignedTest() { + byte[] sorted = new byte[]{-2, -1, 0, 1, 2, 3, 4, 5, 6}; assertArrayEquals(sorted, Bytes.from(sorted).transform(shuffle()).transform(sort()).array()); - assertArrayEquals(sorted, Bytes.from(new byte[]{6, 0, 3, 4, 1, 5, 2}).transform(sort()).array()); - assertArrayEquals(Bytes.from(sorted).reverse().array(), Bytes.from(new byte[]{6, 0, 3, 4, 1, 5, 2}).transform(sort(new Comparator() { + assertArrayEquals(sorted, Bytes.from(new byte[]{6, 0, 3, -2, -1, 4, 1, 5, 2}).transform(sort()).array()); + assertArrayEquals(Bytes.from(sorted).reverse().array(), Bytes.from(new byte[]{6, -2, -1, 0, 3, 4, 1, 5, 2}).transform(sort(new Comparator() { @Override public int compare(Byte o1, Byte o2) { return o2.compareTo(o1); } })).array()); + + byte[] checkSignedSorted = new byte[]{(byte) 0x80, (byte) 0xFE, (byte) 0xFF, 0x00, 0x01}; + assertArrayEquals(checkSignedSorted, Bytes.from(checkSignedSorted).transform(shuffle()).transform(sort()).array()); + } + + @Test + public void sortUnsignedTest() { + byte[] sorted = new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, (byte) 0x80, (byte) 0xAE, (byte) 0xFF}; + assertArrayEquals(sorted, Bytes.from(sorted).transform(shuffle()).transform(sortUnsigned()).array()); + assertArrayEquals(sorted, Bytes.from(new byte[]{(byte) 0x80, (byte) 0xAE, (byte) 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}).transform(sortUnsigned()).array()); } @Test @@ -321,13 +347,34 @@ public void bitSwitchOutOfBounds() { } @Test - public void hash() { + public void hashSha1() { + assertEquals(Bytes.parseHex("da39a3ee5e6b4b0d3255bfef95601890afd80709"), Bytes.from("").hashSha1()); + assertEquals(Bytes.parseHex("2628013771c4ffda4336231805f9d6c42e40ef86"), Bytes.from("ö9h%6Ghh1\"").hashSha1()); + assertEquals(Bytes.parseHex("15d418a940d699df0cde6304829b2cce5ed4a9ad"), Bytes.from("897SHALkjdn ,n-- kasdjöa").hashSha1()); + } + + @Test + public void hashMd5() { + assertEquals(Bytes.parseHex("d41d8cd98f00b204e9800998ecf8427e"), Bytes.from("").hashMd5()); + assertEquals(Bytes.parseHex("ff38205f1cb22f588d8bc9ae21f22092"), Bytes.from("ö9h%6Ghh1\"").hashMd5()); + assertEquals(Bytes.parseHex("9DFF192C3CE8554DBB1ADCC7721B4B78"), Bytes.from("897SHALkjdn ,n-- kasdjöa").hashMd5()); + } + + @Test + public void hash256() { assertEquals(Bytes.parseHex("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"), Bytes.from("").hashSha256()); assertEquals(Bytes.parseHex("e362eea626386c93a54c9b82e6b896c0350fbff0ee12f284660253aac0908cfb"), Bytes.from("ö9h%6Ghh1\"").hashSha256()); + assertEquals(Bytes.parseHex("48D6BE81CB2EF8488BA2E3BF4050EE21BF9D33D85DB0E556E4AE5992243B8F35"), Bytes.from("897SHALkjdn ,n-- kasdjöa").hashSha256()); + } + + @Test + public void hashCustom() { assertEquals(Bytes.parseHex("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"), Bytes.from("").hash("SHA-512")); assertEquals(Bytes.parseHex("106747C3DDC117091BEF8D21AEBAA8D314656D3AE1135AB36F4C0B07A264127CF625FE616751BEC66B43032B904E2D3B6C21BF14E078F6BB775A72503F48111D"), Bytes.from("ö9h%6Ghh1\"").hash("SHA-512")); assertEquals(Bytes.parseHex("d41d8cd98f00b204e9800998ecf8427e"), Bytes.from("").hash("MD5")); assertEquals(Bytes.parseHex("ff38205f1cb22f588d8bc9ae21f22092"), Bytes.from("ö9h%6Ghh1\"").hash("MD5")); + assertEquals(Bytes.parseHex("38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b"), Bytes.from("").hash("SHA-384")); + assertEquals(Bytes.parseHex("ec89d0d6b067f7f2e240ea7587933d92347fce4bdab68784bd2373dc1cccaa0238c0556b045acb1632080fac788d429d"), Bytes.from("ö9h%6Ghh1\"").hash("SHA-384")); } @Test @@ -400,7 +447,7 @@ public void transformerInPlaceTest() { assertTrue(new BytesTransformer.BitSwitchTransformer(0, true).supportInPlaceTransformation()); assertTrue(new BytesTransformer.BitWiseOperatorTransformer(new byte[]{}, BytesTransformer.BitWiseOperatorTransformer.Mode.XOR).supportInPlaceTransformation()); assertTrue(new BytesTransformer.NegateTransformer().supportInPlaceTransformation()); - assertTrue(new BytesTransformer.ShiftTransformer(0, BytesTransformer.ShiftTransformer.Type.LEFT_SHIFT).supportInPlaceTransformation()); + assertTrue(new BytesTransformer.ShiftTransformer(0, BytesTransformer.ShiftTransformer.Type.LEFT_SHIFT, ByteOrder.BIG_ENDIAN).supportInPlaceTransformation()); assertTrue(new BytesTransformer.ReverseTransformer().supportInPlaceTransformation()); assertFalse(new BytesTransformer.MessageDigestTransformer("SHA1").supportInPlaceTransformation()); @@ -419,4 +466,21 @@ public int compare(Byte o1, Byte o2) { }).supportInPlaceTransformation()); assertTrue(new BytesTransformers.ShuffleTransformer(new SecureRandom()).supportInPlaceTransformation()); } -} \ No newline at end of file + + @Test + public void transformHmac() { + System.out.println(Bytes.parseHex("d8b6239569b184eb7991").transform(new HmacTransformer(Bytes.parseHex("671536819982").array(), "HmacSHA256")).encodeHex()); + + assertEquals(Bytes.parseHex("d8f0eda7a00192091ad8fefa501753ae"), Bytes.allocate(16).transform(new HmacTransformer(new byte[16], "HmacMd5"))); + assertEquals(Bytes.parseHex("c69c13e005ae8ec628ec1869f334ca056bb38958"), Bytes.allocate(16).transform(new HmacTransformer(new byte[20], "HmacSHA1"))); + assertEquals(Bytes.parseHex("c69c13e005ae8ec628ec1869f334ca056bb38958"), Bytes.allocate(16).transform(BytesTransformers.hmacSha1(new byte[20]))); + assertEquals(Bytes.parseHex("853c7403937d8b6239569b184eb7993fc5f751aefcea28f2c863858e2d29c50b"), Bytes.allocate(16).transform(new HmacTransformer(new byte[32], "HmacSHA256"))); + assertEquals(Bytes.parseHex("9aff87db4fd8df58c9081d8386ccc71c9a0f5fe9491235b7bb17e1be20bbe82b"), Bytes.parseHex("d8b6239569b184eb7991").transform(new HmacTransformer(Bytes.parseHex("671536819982").array(), "HmacSHA256"))); + assertEquals(Bytes.parseHex("9aff87db4fd8df58c9081d8386ccc71c9a0f5fe9491235b7bb17e1be20bbe82b"), Bytes.parseHex("d8b6239569b184eb7991").transform(BytesTransformers.hmacSha256(Bytes.parseHex("671536819982").array()))); + assertEquals(Bytes.parseHex("9aff87db4fd8df58c9081d8386ccc71c9a0f5fe9491235b7bb17e1be20bbe82b"), Bytes.parseHex("d8b6239569b184eb7991").transform(BytesTransformers.hmac(Bytes.parseHex("671536819982").array(), "HmacSHA256"))); + + //reference test vectors - see https://tools.ietf.org/html/rfc2104 + assertEquals(Bytes.parseHex("9294727a3638bb1c13f48ef8158bfc9d"), Bytes.from("Hi There").transform(new HmacTransformer(Bytes.parseHex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").array(), "HmacMd5"))); + assertEquals(Bytes.parseHex("56be34521d144c88dbb8c733f0e8b3f6"), Bytes.parseHex("DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD").transform(new HmacTransformer(Bytes.parseHex("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").array(), "HmacMd5"))); + } +} diff --git a/src/test/java/at/favre/lib/bytes/BytesValidatorTest.java b/src/test/java/at/favre/lib/bytes/BytesValidatorTest.java index d7174fa..3baf603 100644 --- a/src/test/java/at/favre/lib/bytes/BytesValidatorTest.java +++ b/src/test/java/at/favre/lib/bytes/BytesValidatorTest.java @@ -32,7 +32,7 @@ public class BytesValidatorTest extends ABytesTest { @Test - public void testOnlyOfValidator() throws Exception { + public void testOnlyOfValidator() { assertFalse(Bytes.allocate(0).validateNotOnlyZeros()); assertFalse(Bytes.allocate(2).validateNotOnlyZeros()); assertTrue(Bytes.wrap(example_bytes_seven).validateNotOnlyZeros()); @@ -50,7 +50,7 @@ public void testOnlyOfValidator() throws Exception { } @Test - public void testLengthValidators() throws Exception { + public void testLengthValidators() { assertFalse(Bytes.allocate(0).validate(atLeast(1))); assertTrue(Bytes.allocate(1).validate(atLeast(1))); assertTrue(Bytes.allocate(2).validate(atLeast(1))); @@ -65,7 +65,7 @@ public void testLengthValidators() throws Exception { } @Test - public void testOrValidation() throws Exception { + public void testOrValidation() { assertTrue(Bytes.allocate(0).validate(or(exactLength(1), exactLength(0)))); assertTrue(Bytes.allocate(2).validate(or(atLeast(3), onlyOf((byte) 0)))); assertTrue(Bytes.allocate(3).validate(or(onlyOf((byte) 1), onlyOf((byte) 0)))); @@ -74,7 +74,7 @@ public void testOrValidation() throws Exception { } @Test - public void testAndValidation() throws Exception { + public void testAndValidation() { assertFalse(Bytes.allocate(5).validate(and(atLeast(3), notOnlyOf((byte) 0)))); assertFalse(Bytes.wrap(new byte[]{1, 0}).validate(and(atLeast(3), notOnlyOf((byte) 0)))); assertTrue(Bytes.wrap(new byte[]{1, 0, 0}).validate(and(atLeast(3), notOnlyOf((byte) 0)))); @@ -82,26 +82,26 @@ public void testAndValidation() throws Exception { } @Test - public void testNotValidation() throws Exception { + public void testNotValidation() { assertEquals(Bytes.allocate(2).validate(not(onlyOf((byte) 0))), Bytes.allocate(2).validate(notOnlyOf((byte) 0))); assertTrue(Bytes.allocate(2).validate(not(atLeast(16)))); assertFalse(Bytes.allocate(2).validate(not(atMost(16)))); } @Test(expected = IllegalArgumentException.class) - public void testLogicalNoElements() throws Exception { + public void testLogicalNoElements() { Bytes.allocate(2).validate(new BytesValidator.Logical(Collections.emptyList(), BytesValidator.Logical.Operator.AND)); } @Test(expected = IllegalArgumentException.class) - public void testLogicalTooManyElements() throws Exception { + public void testLogicalTooManyElements() { Bytes.allocate(2).validate(new BytesValidator.Logical( Arrays.asList(new BytesValidator.Length(2, BytesValidator.Length.Mode.GREATER_OR_EQ_THAN), new BytesValidator.Length(2, BytesValidator.Length.Mode.EXACT)) , BytesValidator.Logical.Operator.NOT)); } @Test - public void testNestedValidation() throws Exception { + public void testNestedValidation() { assertTrue(Bytes.allocate(16).validate( or(and(atLeast(8), not(onlyOf(((byte) 0)))), or(exactLength(16), exactLength(12))))); @@ -113,7 +113,7 @@ public void testNestedValidation() throws Exception { } @Test - public void testStartWithValidate() throws Exception { + public void testStartWithValidate() { assertTrue(Bytes.wrap(new byte[]{0, 3, 0}).validate(startsWith((byte) 0, (byte) 3))); assertFalse(Bytes.wrap(new byte[]{0, 2, 0}).validate(startsWith((byte) 0, (byte) 3))); assertTrue(Bytes.wrap(new byte[]{0, 2, 0}).validate(startsWith((byte) 0))); @@ -123,7 +123,7 @@ public void testStartWithValidate() throws Exception { } @Test - public void testEndsWithValidate() throws Exception { + public void testEndsWithValidate() { assertTrue(Bytes.wrap(new byte[]{1, 2, 3}).validate(endsWith((byte) 2, (byte) 3))); assertFalse(Bytes.wrap(new byte[]{0, 2, 0}).validate(endsWith((byte) 3, (byte) 0))); assertTrue(Bytes.wrap(new byte[]{0, 2, 0}).validate(endsWith((byte) 0))); @@ -131,4 +131,4 @@ public void testEndsWithValidate() throws Exception { assertTrue(Bytes.allocate(16).validate(endsWith((byte) 0))); assertFalse(Bytes.allocate(16).validate(endsWith(Bytes.allocate(17).array()))); } -} \ No newline at end of file +} diff --git a/src/test/java/at/favre/lib/bytes/EncodingBase64JmhBenchmark.java b/src/test/java/at/favre/lib/bytes/EncodingBase64JmhBenchmark.java new file mode 100644 index 0000000..c169185 --- /dev/null +++ b/src/test/java/at/favre/lib/bytes/EncodingBase64JmhBenchmark.java @@ -0,0 +1,99 @@ +/* + * Copyright 2018 Patrick Favre-Bulle + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package at.favre.lib.bytes; + +import org.openjdk.jmh.annotations.*; + +import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/* +# JMH version: 1.21 +# VM version: JDK 1.8.0_172, Java HotSpot(TM) 64-Bit Server VM, 25.172-b11 +# i7 7700K / 24G + +Benchmark (byteLength) Mode Cnt Score Error Units +Benchmark (byteLength) Mode Cnt Score Error Units +EncodingJmhBenchmark.encodeBase64Apache 1 thrpt 4 1260664,187 ± 134162,595 ops/s +EncodingJmhBenchmark.encodeBase64Apache 16 thrpt 4 1018969,264 ± 4008,839 ops/s +EncodingJmhBenchmark.encodeBase64Apache 128 thrpt 4 470368,001 ± 6377,776 ops/s +EncodingJmhBenchmark.encodeBase64Apache 512 thrpt 4 170623,614 ± 5243,433 ops/s +EncodingJmhBenchmark.encodeBase64Apache 1000000 thrpt 4 102,602 ± 2,441 ops/s +EncodingJmhBenchmark.encodeBase64Guava 1 thrpt 4 10961113,761 ± 2448198,032 ops/s +EncodingJmhBenchmark.encodeBase64Guava 16 thrpt 4 6223639,376 ± 189028,495 ops/s +EncodingJmhBenchmark.encodeBase64Guava 128 thrpt 4 1390184,429 ± 35982,746 ops/s +EncodingJmhBenchmark.encodeBase64Guava 512 thrpt 4 343957,426 ± 10939,429 ops/s +EncodingJmhBenchmark.encodeBase64Guava 1000000 thrpt 4 229,641 ± 5,811 ops/s +EncodingJmhBenchmark.encodeBase64Okio 1 thrpt 4 12567622,970 ± 460318,474 ops/s +EncodingJmhBenchmark.encodeBase64Okio 16 thrpt 4 6552615,163 ± 1382416,859 ops/s +EncodingJmhBenchmark.encodeBase64Okio 128 thrpt 4 1640526,665 ± 375607,815 ops/s +EncodingJmhBenchmark.encodeBase64Okio 512 thrpt 4 235282,079 ± 6631,560 ops/s +EncodingJmhBenchmark.encodeBase64Okio 1000000 thrpt 4 103,728 ± 0,820 ops/s + */ + +@State(Scope.Thread) +@Fork(1) +@Warmup(iterations = 2, time = 2) +@Measurement(iterations = 4, time = 5) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +public class EncodingBase64JmhBenchmark { + + @Param({"1", "16", "128", "512", "1000000"}) + private int byteLength; + private Map rndMap; + + private BinaryToTextEncoding.EncoderDecoder base64Okio; + private Random random; + + @Setup(Level.Trial) + public void setup() { + random = new Random(); + base64Okio = new BinaryToTextEncoding.Base64Encoding(); + + rndMap = new HashMap<>(); + int[] lengths = new int[]{1, 16, 128, 512, 1000000}; + for (int length : lengths) { + int count = 10; + rndMap.put(length, new Bytes[count]); + for (int i = 0; i < count; i++) { + rndMap.get(length)[i] = Bytes.random(length); + } + } + } + + @Benchmark + public byte[] encodeBase64Okio() { + return encodeDecode(base64Okio); + } + + private byte[] encodeDecode(BinaryToTextEncoding.EncoderDecoder encoder) { + Bytes[] bytes = rndMap.get(byteLength); + int rndNum = random.nextInt(bytes.length); + + String encoded = encoder.encode(bytes[rndNum].array(), ByteOrder.BIG_ENDIAN); + return encoder.decode(encoded); + } +} diff --git a/src/test/java/at/favre/lib/bytes/EncodingHexJmhBenchmark.java b/src/test/java/at/favre/lib/bytes/EncodingHexJmhBenchmark.java new file mode 100644 index 0000000..80e297b --- /dev/null +++ b/src/test/java/at/favre/lib/bytes/EncodingHexJmhBenchmark.java @@ -0,0 +1,434 @@ +/* + * Copyright 2018 Patrick Favre-Bulle + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package at.favre.lib.bytes; + +import org.openjdk.jmh.annotations.*; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/** + * Simple benchmark checking the performance of hex encoding + *

    + * # Run complete. Total time: 00:22:00 + *

    + * REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on + * why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial + * experiments, perform baseline and negative tests that provide experimental control, make sure + * the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. + * Do not assume the numbers tell you what you want them to tell. + *

    + * Benchmark (byteLength) Mode Cnt Score Error Units + * EncodingHexJmhBenchmark.encodeBigInteger 4 thrpt 3 9427359,108 ± 194693,624 ops/s + * EncodingHexJmhBenchmark.encodeBigInteger 8 thrpt 3 2638943,318 ± 15520,030 ops/s + * EncodingHexJmhBenchmark.encodeBigInteger 16 thrpt 3 2088514,337 ± 50090,292 ops/s + * EncodingHexJmhBenchmark.encodeBigInteger 32 thrpt 3 1002560,730 ± 11096,621 ops/s + * EncodingHexJmhBenchmark.encodeBigInteger 128 thrpt 3 133665,420 ± 5750,982 ops/s + * EncodingHexJmhBenchmark.encodeBigInteger 512 thrpt 3 25807,764 ± 407,273 ops/s + * EncodingHexJmhBenchmark.encodeBigInteger 1000000 thrpt 3 4,178 ± 0,021 ops/s + * EncodingHexJmhBenchmark.encodeBouncyCastleHex 4 thrpt 3 14260598,238 ± 4113483,586 ops/s + * EncodingHexJmhBenchmark.encodeBouncyCastleHex 8 thrpt 3 10910660,517 ± 51077,989 ops/s + * EncodingHexJmhBenchmark.encodeBouncyCastleHex 16 thrpt 3 7501666,934 ± 938571,073 ops/s + * EncodingHexJmhBenchmark.encodeBouncyCastleHex 32 thrpt 3 3657718,637 ± 214574,329 ops/s + * EncodingHexJmhBenchmark.encodeBouncyCastleHex 128 thrpt 3 1077236,523 ± 78734,473 ops/s + * EncodingHexJmhBenchmark.encodeBouncyCastleHex 512 thrpt 3 266238,359 ± 2544,657 ops/s + * EncodingHexJmhBenchmark.encodeBouncyCastleHex 1000000 thrpt 3 152,235 ± 8,156 ops/s + * EncodingHexJmhBenchmark.encodeBytesLib 4 thrpt 3 24944896,332 ± 2367456,841 ops/s + * EncodingHexJmhBenchmark.encodeBytesLib 8 thrpt 3 23222999,376 ± 396218,748 ops/s + * EncodingHexJmhBenchmark.encodeBytesLib 16 thrpt 3 20423170,033 ± 194747,311 ops/s + * EncodingHexJmhBenchmark.encodeBytesLib 32 thrpt 3 15723370,706 ± 148740,280 ops/s + * EncodingHexJmhBenchmark.encodeBytesLib 128 thrpt 3 6685522,295 ± 920540,781 ops/s + * EncodingHexJmhBenchmark.encodeBytesLib 512 thrpt 3 1612325,148 ± 18002,919 ops/s + * EncodingHexJmhBenchmark.encodeBytesLib 1000000 thrpt 3 825,527 ± 124,972 ops/s + * EncodingHexJmhBenchmark.encodeOldBytesLib 4 thrpt 3 17973467,504 ± 429817,214 ops/s + * EncodingHexJmhBenchmark.encodeOldBytesLib 8 thrpt 3 14836039,023 ± 16437596,584 ops/s + * EncodingHexJmhBenchmark.encodeOldBytesLib 16 thrpt 3 11727630,076 ± 1849815,074 ops/s + * EncodingHexJmhBenchmark.encodeOldBytesLib 32 thrpt 3 7440067,345 ± 10968063,752 ops/s + * EncodingHexJmhBenchmark.encodeOldBytesLib 128 thrpt 3 2656731,985 ± 53887,109 ops/s + * EncodingHexJmhBenchmark.encodeOldBytesLib 512 thrpt 3 288949,898 ± 7650,867 ops/s + * EncodingHexJmhBenchmark.encodeOldBytesLib 1000000 thrpt 3 127,737 ± 3,982 ops/s + * EncodingHexJmhBenchmark.encodeStackOverflowCode1 4 thrpt 3 24557504,065 ± 797466,455 ops/s + * EncodingHexJmhBenchmark.encodeStackOverflowCode1 8 thrpt 3 23515327,490 ± 979922,009 ops/s + * EncodingHexJmhBenchmark.encodeStackOverflowCode1 16 thrpt 3 20377272,084 ± 480955,369 ops/s + * EncodingHexJmhBenchmark.encodeStackOverflowCode1 32 thrpt 3 16273674,820 ± 745874,444 ops/s + * EncodingHexJmhBenchmark.encodeStackOverflowCode1 128 thrpt 3 6767893,154 ± 511154,141 ops/s + * EncodingHexJmhBenchmark.encodeStackOverflowCode1 512 thrpt 3 1573543,465 ± 1126382,628 ops/s + * EncodingHexJmhBenchmark.encodeStackOverflowCode1 1000000 thrpt 3 833,885 ± 54,099 ops/s + * EncodingHexJmhBenchmark.encodeStackOverflowCode2 4 thrpt 3 25220997,342 ± 852230,094 ops/s + * EncodingHexJmhBenchmark.encodeStackOverflowCode2 8 thrpt 3 24152600,074 ± 480651,708 ops/s + * EncodingHexJmhBenchmark.encodeStackOverflowCode2 16 thrpt 3 21494551,701 ± 1722250,353 ops/s + * EncodingHexJmhBenchmark.encodeStackOverflowCode2 32 thrpt 3 17283936,474 ± 134694,570 ops/s + * EncodingHexJmhBenchmark.encodeStackOverflowCode2 128 thrpt 3 7554272,476 ± 1329386,082 ops/s + * EncodingHexJmhBenchmark.encodeStackOverflowCode2 512 thrpt 3 1781989,859 ± 125696,811 ops/s + * EncodingHexJmhBenchmark.encodeStackOverflowCode2 1000000 thrpt 3 935,685 ± 103,571 ops/s + * EncodingHexJmhBenchmark.encodeGuavaBase16 4 thrpt 3 17606767,347 ± 3615884,433 ops/s + * EncodingHexJmhBenchmark.encodeGuavaBase16 8 thrpt 3 14077449,253 ± 3570354,199 ops/s + * EncodingHexJmhBenchmark.encodeGuavaBase16 16 thrpt 3 10177925,910 ± 5584265,227 ops/s + * EncodingHexJmhBenchmark.encodeGuavaBase16 32 thrpt 3 6270140,774 ± 96756,395 ops/s + * EncodingHexJmhBenchmark.encodeGuavaBase16 128 thrpt 3 2094658,415 ± 2321008,783 ops/s + * EncodingHexJmhBenchmark.encodeGuavaBase16 512 thrpt 3 514528,610 ± 20419,732 ops/s + * EncodingHexJmhBenchmark.encodeGuavaBase16 1000000 thrpt 3 257,440 ± 23,100 ops/s + * EncodingHexJmhBenchmark.encodeSpringSecurity 4 thrpt 3 25269684,873 ± 1820484,918 ops/s + * EncodingHexJmhBenchmark.encodeSpringSecurity 8 thrpt 3 22631565,512 ± 291386,502 ops/s + * EncodingHexJmhBenchmark.encodeSpringSecurity 16 thrpt 3 18704986,566 ± 604936,041 ops/s + * EncodingHexJmhBenchmark.encodeSpringSecurity 32 thrpt 3 13345680,362 ± 59610,208 ops/s + * EncodingHexJmhBenchmark.encodeSpringSecurity 128 thrpt 3 4904805,240 ± 18454,315 ops/s + * EncodingHexJmhBenchmark.encodeSpringSecurity 512 thrpt 3 1165535,921 ± 13487,051 ops/s + * EncodingHexJmhBenchmark.encodeSpringSecurity 1000000 thrpt 3 601,152 ± 248,099 ops/s + * EncodingHexJmhBenchmark.encodeApacheCommons 4 thrpt 3 24942725,562 ± 4311405,167 ops/s + * EncodingHexJmhBenchmark.encodeApacheCommons 8 thrpt 3 21908225,962 ± 488932,267 ops/s + * EncodingHexJmhBenchmark.encodeApacheCommons 16 thrpt 3 17503857,494 ± 37335,212 ops/s + * EncodingHexJmhBenchmark.encodeApacheCommons 32 thrpt 3 12378489,920 ± 541212,742 ops/s + * EncodingHexJmhBenchmark.encodeApacheCommons 128 thrpt 3 4319898,906 ± 15591,318 ops/s + * EncodingHexJmhBenchmark.encodeApacheCommons 512 thrpt 3 1034983,537 ± 8517,121 ops/s + * EncodingHexJmhBenchmark.encodeApacheCommons 1000000 thrpt 3 529,885 ± 223,283 ops/s + * EncodingHexJmhBenchmark.encodeJaxBDataTypeConverter 16 thrpt 3 13497736,529 ± 871841,900 ops/s + * EncodingHexJmhBenchmark.encodeJaxBDataTypeConverter 32 thrpt 3 8312834,453 ± 196657,729 ops/s + * EncodingHexJmhBenchmark.encodeJaxBDataTypeConverter 128 thrpt 3 2590940,123 ± 50999,492 ops/s + * EncodingHexJmhBenchmark.encodeJaxBDataTypeConverter 1000000 thrpt 3 346,074 ± 29,339 ops/s + */ + +@State(Scope.Thread) +@Fork(1) +@Warmup(iterations = 2, time = 3) +@Measurement(iterations = 3, time = 10) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +public class EncodingHexJmhBenchmark { + + @Param({"4", "8", "16", "32", "128", "512", "1000000"}) + private int byteLength; + private Map rndMap; + + private BinaryToTextEncoding.EncoderDecoder option1; + private BinaryToTextEncoding.EncoderDecoder option2; + private BinaryToTextEncoding.EncoderDecoder option3; + private BinaryToTextEncoding.EncoderDecoder option4; + private BinaryToTextEncoding.EncoderDecoder option5; + private BinaryToTextEncoding.EncoderDecoder option6; + private Random random; + + @Setup(Level.Trial) + public void setup() { + random = new Random(); + + option1 = new StackOverflowAnswer1Encoder(); + option2 = new BinaryToTextEncoding.Hex(true); + option3 = new BigIntegerHexEncoder(); + option4 = new OldBytesImplementation(); + option5 = new StackOverflowAnswer2Encoder(); + option6 = new Jdk17HexFormat(); + + rndMap = new HashMap<>(); + int[] lengths = new int[]{4, 8, 16, 32, 128, 512, 1000000}; + for (int length : lengths) { + int count = 10; + rndMap.put(length, new Bytes[count]); + for (int i = 0; i < count; i++) { + rndMap.get(length)[i] = Bytes.random(length); + } + } + } + + @Benchmark + public String encodeStackOverflowCode1() { + return encodeDecode(option1); + } + + @Benchmark + public String encodeBytesLib() { + return encodeDecode(option2); + } + + @Benchmark + public String encodeBigInteger() { + return encodeDecode(option3); + } + + @Benchmark + public String encodeOldBytesLib() { + return encodeDecode(option4); + } + + @Benchmark + public String encodeStackOverflowCode2() { + return encodeDecode(option5); + } + + @Benchmark + public String encodeHexFormatJdk17() { + return encodeDecode(option6); + } + + private String encodeDecode(BinaryToTextEncoding.EncoderDecoder encoder) { + Bytes[] bytes = rndMap.get(byteLength); + int rndNum = random.nextInt(bytes.length); + return encoder.encode(bytes[rndNum].array(), ByteOrder.BIG_ENDIAN); + } + + /** + * See: https://stackoverflow.com/a/9855338/774398 + */ + static final class StackOverflowAnswer1Encoder implements BinaryToTextEncoding.EncoderDecoder { + private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + + @Override + public String encode(byte[] bytes, ByteOrder byteOrder) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars); + } + + @Override + public byte[] decode(CharSequence encoded) { + throw new UnsupportedOperationException(); + } + } + + /** + * With full lookup table of all 256 values + * See: https://stackoverflow.com/a/21429909/774398 + */ + static final class StackOverflowAnswer2Encoder implements BinaryToTextEncoding.EncoderDecoder { + + private static final char[] BYTE2HEX = ( + "000102030405060708090A0B0C0D0E0F" + + "101112131415161718191A1B1C1D1E1F" + + "202122232425262728292A2B2C2D2E2F" + + "303132333435363738393A3B3C3D3E3F" + + "404142434445464748494A4B4C4D4E4F" + + "505152535455565758595A5B5C5D5E5F" + + "606162636465666768696A6B6C6D6E6F" + + "707172737475767778797A7B7C7D7E7F" + + "808182838485868788898A8B8C8D8E8F" + + "909192939495969798999A9B9C9D9E9F" + + "A0A1A2A3A4A5A6A7A8A9AAABACADAEAF" + + "B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF" + + "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF" + + "D0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF" + + "E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF" + + "F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF").toCharArray(); + + @Override + public String encode(byte[] bytes, ByteOrder byteOrder) { + final int len = bytes.length; + final char[] chars = new char[len << 1]; + int hexIndex; + int idx = 0; + int ofs = 0; + while (ofs < len) { + hexIndex = (bytes[ofs++] & 0xFF) << 1; + chars[idx++] = BYTE2HEX[hexIndex++]; + chars[idx++] = BYTE2HEX[hexIndex]; + } + return new String(chars); + } + + @Override + public byte[] decode(CharSequence encoded) { + throw new UnsupportedOperationException(); + } + } + + static final class BigIntegerHexEncoder implements BinaryToTextEncoding.EncoderDecoder { + @Override + public String encode(byte[] bytes, ByteOrder byteOrder) { + return new BigInteger(1, bytes).toString(16); + } + + @Override + public byte[] decode(CharSequence encoded) { + throw new UnsupportedOperationException(); + } + } + + static final class OldBytesImplementation implements BinaryToTextEncoding.EncoderDecoder { + + @Override + public String encode(byte[] byteArray, ByteOrder byteOrder) { + StringBuilder sb = new StringBuilder(byteArray.length * 2); + + int index; + char first4Bit; + char last4Bit; + for (int i = 0; i < byteArray.length; i++) { + index = (byteOrder == ByteOrder.BIG_ENDIAN) ? i : byteArray.length - i - 1; + first4Bit = Character.forDigit((byteArray[index] >> 4) & 0xF, 16); + last4Bit = Character.forDigit((byteArray[index] & 0xF), 16); + sb.append(first4Bit).append(last4Bit); + } + return sb.toString(); + } + + @Override + public byte[] decode(CharSequence encoded) { + throw new UnsupportedOperationException(); + } + } + + /** + * Copy of the JDK 17 implementation of HexFormat, only difference is that we need to create new strings, while + * the JDK can create strings without byte copy, I cant here. + */ + static final class Jdk17HexFormat implements BinaryToTextEncoding.EncoderDecoder { + private static final byte[] LOWERCASE_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', + }; + private final String delimiter = ""; + private final String prefix = ""; + private final String suffix = ""; + private final byte[] digits = LOWERCASE_DIGITS; + + @Override + public String encode(byte[] byteArray, ByteOrder byteOrder) { + return formatHex(byteArray, 0, byteArray.length); + } + + private String formatHex(byte[] bytes, int fromIndex, int toIndex) { + Objects.requireNonNull(bytes, "bytes"); + //Objects.checkFromToIndex(fromIndex, toIndex, bytes.length); + if (toIndex - fromIndex == 0) { + return ""; + } + // Format efficiently if possible + String s = formatOptDelimiter(bytes, fromIndex, toIndex); + if (s == null) { + long stride = prefix.length() + 2L + suffix.length() + delimiter.length(); + int capacity = checkMaxArraySize((toIndex - fromIndex) * stride - delimiter.length()); + StringBuilder sb = new StringBuilder(capacity); + formatHex(sb, bytes, fromIndex, toIndex); + s = sb.toString(); + } + return s; + } + + private A formatHex(A out, byte[] bytes, int fromIndex, int toIndex) { + Objects.requireNonNull(out, "out"); + Objects.requireNonNull(bytes, "bytes"); + //Objects.checkFromToIndex(fromIndex, toIndex, bytes.length); + + int length = toIndex - fromIndex; + if (length > 0) { + try { + String between = suffix + delimiter + prefix; + out.append(prefix); + toHexDigits(out, bytes[fromIndex]); + if (between.isEmpty()) { + for (int i = 1; i < length; i++) { + toHexDigits(out, bytes[fromIndex + i]); + } + } else { + for (int i = 1; i < length; i++) { + out.append(between); + toHexDigits(out, bytes[fromIndex + i]); + } + } + out.append(suffix); + } catch (IOException ioe) { + throw new RuntimeException(ioe.getMessage(), ioe); + } + } + return out; + } + + private A toHexDigits(A out, byte value) { + Objects.requireNonNull(out, "out"); + try { + out.append(toHighHexDigit(value)); + out.append(toLowHexDigit(value)); + return out; + } catch (IOException ioe) { + throw new RuntimeException(ioe.getMessage(), ioe); + } + } + + private String formatOptDelimiter(byte[] bytes, int fromIndex, int toIndex) { + byte[] rep; + if (!prefix.isEmpty() || !suffix.isEmpty()) { + return null; + } + int length = toIndex - fromIndex; + if (delimiter.isEmpty()) { + // Allocate the byte array and fill in the hex pairs for each byte + rep = new byte[checkMaxArraySize(length * 2L)]; + for (int i = 0; i < length; i++) { + rep[i * 2] = (byte) toHighHexDigit(bytes[fromIndex + i]); + rep[i * 2 + 1] = (byte) toLowHexDigit(bytes[fromIndex + i]); + } + } else if (delimiter.length() == 1 && delimiter.charAt(0) < 256) { + // Allocate the byte array and fill in the characters for the first byte + // Then insert the delimiter and hexadecimal characters for each of the remaining bytes + char sep = delimiter.charAt(0); + rep = new byte[checkMaxArraySize(length * 3L - 1L)]; + rep[0] = (byte) toHighHexDigit(bytes[fromIndex]); + rep[1] = (byte) toLowHexDigit(bytes[fromIndex]); + for (int i = 1; i < length; i++) { + rep[i * 3 - 1] = (byte) sep; + rep[i * 3] = (byte) toHighHexDigit(bytes[fromIndex + i]); + rep[i * 3 + 1] = (byte) toLowHexDigit(bytes[fromIndex + i]); + } + } else { + // Delimiter formatting not to a single byte + return null; + } + try { + // Return a new string using the bytes without making a copy -> we cant use this here as we dont have access to JavaLangAccess + //return jla.newStringNoRepl(rep, StandardCharsets.ISO_8859_1); + return new String(rep, StandardCharsets.ISO_8859_1); + } catch (Exception cce) { + throw new AssertionError(cce); + } + } + + private char toHighHexDigit(int value) { + return (char) digits[(value >> 4) & 0xf]; + } + + private char toLowHexDigit(int value) { + return (char) digits[value & 0xf]; + } + + private static int checkMaxArraySize(long length) { + if (length > Integer.MAX_VALUE) + throw new OutOfMemoryError("String size " + length + + " exceeds maximum " + Integer.MAX_VALUE); + return (int) length; + } + + @Override + public byte[] decode(CharSequence encoded) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/src/test/java/at/favre/lib/bytes/MutableBytesABytesTest.java b/src/test/java/at/favre/lib/bytes/MutableBytesTest.java similarity index 53% rename from src/test/java/at/favre/lib/bytes/MutableBytesABytesTest.java rename to src/test/java/at/favre/lib/bytes/MutableBytesTest.java index 0966b76..3ded72c 100644 --- a/src/test/java/at/favre/lib/bytes/MutableBytesABytesTest.java +++ b/src/test/java/at/favre/lib/bytes/MutableBytesTest.java @@ -23,35 +23,58 @@ import org.junit.Test; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; import java.security.SecureRandom; import java.util.Arrays; import static org.junit.Assert.*; -public class MutableBytesABytesTest extends ABytesTest { +public class MutableBytesTest extends ABytesTest { @Test - public void overwriteWithEmptyArray() throws Exception { + public void allocate() { + MutableBytes b = MutableBytes.allocate(8); + assertEquals(8, b.length()); + assertFalse(b.validateNotOnlyZeros()); + } + + @Test + public void allocateFill() { + MutableBytes b = MutableBytes.allocate(1, (byte) 0xFF); + assertEquals(1, b.length()); + assertEquals((byte) 0xFF, b.byteAt(0)); + } + + @Test + public void overwriteWithEmptyArray() { MutableBytes b = fromAndTest(example_bytes_seven).mutable(); assertSame(b, b.overwrite(new byte[example_bytes_seven.length])); assertArrayEquals(new byte[example_bytes_seven.length], b.array()); } @Test - public void overwriteOtherArray() throws Exception { + public void overwriteOtherArray() { MutableBytes b = fromAndTest(example_bytes_seven).mutable(); assertSame(b, b.overwrite(Arrays.copyOf(example2_bytes_seven, example2_bytes_seven.length))); assertArrayEquals(example2_bytes_seven, b.array()); } @Test - public void overwritePartialArray() throws Exception { + public void overwriteOtherBytesInstance() { + MutableBytes b = fromAndTest(example_bytes_seven).mutable(); + assertSame(b, b.overwrite(Bytes.from(example2_bytes_seven))); + assertArrayEquals(example2_bytes_seven, b.array()); + } + + @Test + public void overwritePartialArray() { MutableBytes b = fromAndTest(example_bytes_seven).mutable(); assertSame(b, b.overwrite(new byte[]{(byte) 0xAA}, 0)); assertArrayEquals(Bytes.from((byte) 0xAA).append(Bytes.wrap(example_bytes_seven).copy(1, example_bytes_seven.length - 1)).array(), b.array()); } @Test - public void overwritePartialArray2() throws Exception { + public void overwritePartialArray2() { MutableBytes b = fromAndTest(example_bytes_seven).mutable(); assertSame(b, b.overwrite(new byte[]{(byte) 0xAA}, 1)); assertArrayEquals( @@ -62,19 +85,97 @@ public void overwritePartialArray2() throws Exception { } @Test - public void fill() throws Exception { + public void overwriteBytes() { + MutableBytes a = fromAndTest(example_bytes_seven).mutable(); + MutableBytes b = Bytes.from((byte) 0).mutable(); + MutableBytes c = a.overwrite(b, 0).mutable(); + MutableBytes d = Bytes.wrap(a).copy(1, a.array().length - 1).mutable(); + + assertArrayEquals(c.array(), Bytes.from(b).append(d).array()); + } + + @Test + public void overwriteTooBigArrayShouldThrowException() { + MutableBytes b = fromAndTest(example_bytes_seven).mutable(); + try { + b.overwrite(new byte[]{(byte) 0xAA, 0x30}, b.length()); + fail(); + } catch (IndexOutOfBoundsException ignored) { + } + + } + + @Test + public void overwriteTooBigBytesShouldThrowException() { + MutableBytes b = fromAndTest(example_bytes_seven).mutable(); + try { + b.overwrite(Bytes.from((byte) 0xAA, 0x30), b.length()); + fail(); + } catch (IndexOutOfBoundsException ignored) { + } + + } + + @Test + public void overwriteNullArrayShouldThrowException() { + MutableBytes nonsense = null; + MutableBytes b = fromAndTest(example_bytes_seven).mutable(); + + try { + b.overwrite(nonsense); + fail(); + } catch (NullPointerException ignored) { + } + + + } + + + @Test + public void fill() { MutableBytes b = fromAndTest(example_bytes_seven).mutable(); assertSame(b, b.fill((byte) 0)); assertArrayEquals(new byte[example_bytes_seven.length], b.array()); } @Test - public void setByteAtTest() throws Exception { + public void testConvertImmutable() { + Bytes b = Bytes.from(example_bytes_seven); + MutableBytes m = b.copy().mutable(); + assertNotEquals(b, m); + assertTrue(b.equalsContent(m)); + assertEquals(b.byteOrder(), m.byteOrder()); + + Bytes m2b = m.immutable(); + assertNotEquals(m2b, m); + assertEquals(m2b, b); + assertNotSame(m2b, b); + assertTrue(m2b.equalsContent(m)); + assertEquals(m2b.byteOrder(), m.byteOrder()); + + assertEquals(m.length(), m2b.length()); + assertEquals(m.length(), b.length()); + + assertNotEquals(example_bytes_seven[0], 0); + assertEquals(example_bytes_seven[0], b.byteAt(0)); + assertEquals(example_bytes_seven[0], m.byteAt(0)); + assertEquals(example_bytes_seven[0], m2b.byteAt(0)); + + m.fill((byte) 0); + + assertEquals(example_bytes_seven[0], b.byteAt(0)); + assertEquals(0, m.byteAt(0)); + assertEquals(0, m2b.byteAt(0)); + } + + @Test + public void setByteAtTest() { MutableBytes b = fromAndTest(example_bytes_sixteen).mutable(); for (int i = 0; i < b.length(); i++) { byte old = b.byteAt(i); - b.setByteAt(i, (byte) 0); + MutableBytes bcopy = b.setByteAt(i, (byte) 0); + assertSame(b, bcopy); if (old != 0) { assertNotEquals(old, b.byteAt(i)); } @@ -82,14 +183,14 @@ public void setByteAtTest() throws Exception { } @Test - public void wipe() throws Exception { + public void wipe() { MutableBytes b = fromAndTest(example_bytes_seven).mutable(); assertSame(b, b.wipe()); assertArrayEquals(new byte[example_bytes_seven.length], b.array()); } @Test - public void secureWipe() throws Exception { + public void secureWipe() { MutableBytes b = fromAndTest(example_bytes_seven).mutable(); int hashcode = b.hashCode(); assertSame(b, b.secureWipe()); @@ -99,7 +200,7 @@ public void secureWipe() throws Exception { } @Test - public void secureWipeWithSecureRandom() throws Exception { + public void secureWipeWithSecureRandom() { MutableBytes b = fromAndTest(example_bytes_seven).mutable(); int hashcode = b.hashCode(); assertSame(b, b.secureWipe(new SecureRandom())); @@ -109,18 +210,18 @@ public void secureWipeWithSecureRandom() throws Exception { } @Test(expected = NullPointerException.class) - public void secureWipeShouldThrowException() throws Exception { + public void secureWipeShouldThrowException() { Bytes.wrap(new byte[0]).mutable().secureWipe(null); } @Test - public void testIfGetSameInstance() throws Exception { + public void testIfGetSameInstance() { MutableBytes b = fromAndTest(example_bytes_seven).mutable(); assertSame(b, b.mutable()); } @Test - public void testTransformerShouldBeMutable() throws Exception { + public void testTransformerShouldBeMutable() { MutableBytes b = fromAndTest(example_bytes_seven).mutable(); assertTrue(b.isMutable()); assertTrue(b.copy().isMutable()); @@ -137,4 +238,19 @@ public void testTransformerShouldBeMutable() throws Exception { assertTrue(b.append(3).isMutable()); assertTrue(b.hashSha256().isMutable()); } -} \ No newline at end of file + + @Test + public void testAutoCloseable() { + MutableBytes leak; + + try (MutableBytes b = Bytes.wrap(new byte[16]).mutable()) { + assertArrayEquals(new byte[16], b.array()); + SecretKey s = new SecretKeySpec(b.array(), "AES"); + leak = b; + } + + assertArrayNotEquals(new byte[16], leak.array()); + + } + +} diff --git a/src/test/java/at/favre/lib/bytes/UtilByteTest.java b/src/test/java/at/favre/lib/bytes/UtilByteTest.java new file mode 100644 index 0000000..95b55fb --- /dev/null +++ b/src/test/java/at/favre/lib/bytes/UtilByteTest.java @@ -0,0 +1,267 @@ +/* + * Copyright 2017 Patrick Favre-Bulle + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package at.favre.lib.bytes; + +import org.junit.Ignore; +import org.junit.Test; + +import java.math.BigInteger; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Random; + +import static org.junit.Assert.*; + +public class UtilByteTest extends AUtilTest { + + @Test + public void testIndexOf() { + assertEquals(-1, indexOf(EMPTY, (byte) 1)); + assertEquals(-1, indexOf(ARRAY1, (byte) 2)); + assertEquals(-1, indexOf(ARRAY234, (byte) 1)); + assertEquals(0, indexOf( + new byte[]{(byte) -1}, (byte) -1)); + assertEquals(0, indexOf(ARRAY234, (byte) 2)); + assertEquals(1, indexOf(ARRAY234, (byte) 3)); + assertEquals(2, indexOf(ARRAY234, (byte) 4)); + assertEquals(1, indexOf( + new byte[]{(byte) 2, (byte) 3, (byte) 2, (byte) 3}, + (byte) 3)); + } + + private int indexOf(byte[] empty, byte b) { + return Util.Byte.indexOf(empty, new byte[]{b}, 0, empty.length); + } + + @Test + public void testIndexOf_arrayTarget() { + assertEquals(-1, Util.Byte.indexOf(EMPTY, EMPTY, 0, EMPTY.length)); + assertEquals(-1, Util.Byte.indexOf(ARRAY234, EMPTY, 0, ARRAY234.length)); + assertEquals(-1, Util.Byte.indexOf(EMPTY, ARRAY234, 0, EMPTY.length)); + assertEquals(-1, Util.Byte.indexOf(ARRAY234, ARRAY1, 0, ARRAY234.length)); + assertEquals(-1, Util.Byte.indexOf(ARRAY1, ARRAY234, 0, ARRAY1.length)); + assertEquals(0, Util.Byte.indexOf(ARRAY1, ARRAY1, 0, ARRAY1.length)); + assertEquals(0, Util.Byte.indexOf(ARRAY234, ARRAY234, 0, ARRAY234.length)); + assertEquals(0, Util.Byte.indexOf( + ARRAY234, new byte[]{(byte) 2, (byte) 3}, 0, 2)); + assertEquals(1, Util.Byte.indexOf( + ARRAY234, new byte[]{(byte) 3, (byte) 4}, 0, 2)); + assertEquals(1, Util.Byte.indexOf(ARRAY234, new byte[]{(byte) 3}, 0, ARRAY234.length)); + assertEquals(2, Util.Byte.indexOf(ARRAY234, new byte[]{(byte) 4}, 0, ARRAY234.length)); + assertEquals(1, Util.Byte.indexOf(new byte[]{(byte) 2, (byte) 3, (byte) 3, (byte) 3, (byte) 3}, new byte[]{(byte) 3}, 0, 5)); + assertEquals(2, Util.Byte.indexOf( + new byte[]{(byte) 2, (byte) 3, (byte) 2, + (byte) 3, (byte) 4, (byte) 2, (byte) 3}, + new byte[]{(byte) 2, (byte) 3, (byte) 4} + , 0, 7)); + assertEquals(1, Util.Byte.indexOf( + new byte[]{(byte) 2, (byte) 2, (byte) 3, + (byte) 4, (byte) 2, (byte) 3, (byte) 4}, + new byte[]{(byte) 2, (byte) 3, (byte) 4} + , 0, 7)); + assertEquals(-1, Util.Byte.indexOf( + new byte[]{(byte) 4, (byte) 3, (byte) 2}, + new byte[]{(byte) 2, (byte) 3, (byte) 4} + , 0, 2)); + } + + @Test + public void testLastIndexOf() { + assertEquals(-1, lastIndexOf(EMPTY, (byte) 1)); + assertEquals(-1, lastIndexOf(ARRAY1, (byte) 2)); + assertEquals(-1, lastIndexOf(ARRAY234, (byte) 1)); + assertEquals(0, lastIndexOf( + new byte[]{(byte) -1}, (byte) -1)); + assertEquals(0, lastIndexOf(ARRAY234, (byte) 2)); + assertEquals(1, lastIndexOf(ARRAY234, (byte) 3)); + assertEquals(2, lastIndexOf(ARRAY234, (byte) 4)); + assertEquals(3, lastIndexOf( + new byte[]{(byte) 2, (byte) 3, (byte) 2, (byte) 3}, + (byte) 3)); + } + + private int lastIndexOf(byte[] empty, byte b) { + return Util.Byte.lastIndexOf(empty, b, 0, empty.length); + } + + @Test + public void testConcat() { + assertArrayEquals(EMPTY, Util.Byte.concat()); + assertArrayEquals(EMPTY, Util.Byte.concat(EMPTY)); + assertArrayEquals(EMPTY, Util.Byte.concat(EMPTY, EMPTY, EMPTY)); + assertArrayEquals(ARRAY1, Util.Byte.concat(ARRAY1)); + assertNotSame(ARRAY1, Util.Byte.concat(ARRAY1)); + assertArrayEquals(ARRAY1, Util.Byte.concat(EMPTY, ARRAY1, EMPTY)); + assertArrayEquals( + new byte[]{(byte) 1, (byte) 1, (byte) 1}, + Util.Byte.concat(ARRAY1, ARRAY1, ARRAY1)); + assertArrayEquals( + new byte[]{(byte) 1, (byte) 2, (byte) 3, (byte) 4}, + Util.Byte.concat(ARRAY1, ARRAY234)); + } + + @Test(expected = IllegalStateException.class) + public void readFromStream() { + Util.File.readFromStream(null, -1); + } + + @Test + public void concatVararg() { + assertArrayEquals(new byte[]{1}, Util.Byte.concatVararg((byte) 1, null)); + } + + @Test + public void testReverse() { + testReverse(new byte[]{}, new byte[]{}); + testReverse(new byte[]{1}, new byte[]{1}); + testReverse(new byte[]{1, 2}, new byte[]{2, 1}); + testReverse(new byte[]{3, 1, 1}, new byte[]{1, 1, 3}); + testReverse(new byte[]{-1, 1, -2, 2}, new byte[]{2, -2, 1, -1}); + } + + @Test + public void testReverseIndexed() { + testReverse(new byte[]{}, 0, 0, new byte[]{}); + testReverse(new byte[]{1}, 0, 1, new byte[]{1}); + testReverse(new byte[]{1, 2}, 0, 2, new byte[]{2, 1}); + testReverse(new byte[]{3, 1, 1}, 0, 2, new byte[]{1, 3, 1}); + testReverse(new byte[]{3, 1, 1}, 0, 1, new byte[]{3, 1, 1}); + testReverse(new byte[]{-1, 1, -2, 2}, 1, 3, new byte[]{-1, -2, 1, 2}); + } + + private static void testReverse(byte[] input, byte[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Util.Byte.reverse(input, 0, input.length); + assertArrayEquals(expectedOutput, input); + } + + private static void testReverse(byte[] input, int fromIndex, int toIndex, byte[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Util.Byte.reverse(input, fromIndex, toIndex); + assertArrayEquals(expectedOutput, input); + } + + @Test + public void testLeftShift() { + byte[] test = new byte[]{0, 0, 1, 0}; + assertArrayEquals(new byte[]{0, 1, 0, 0}, Util.Byte.shiftLeft(new byte[]{0, 0, -128, 0}, 1, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{0, 1, 0, 0}, Util.Byte.shiftLeft(new byte[]{0, 0, 64, 0}, 2, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{1, 1, 1, 0}, Util.Byte.shiftLeft(new byte[]{-128, -128, -128, -128}, 1, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{0, 0, 2, 0}, Util.Byte.shiftLeft(Bytes.from(test).array(), 1, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{0, 0, 4, 0}, Util.Byte.shiftLeft(Bytes.from(test).array(), 2, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{0, 0, 8, 0}, Util.Byte.shiftLeft(Bytes.from(test).array(), 3, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{0, 1, 0, 0}, Util.Byte.shiftLeft(Bytes.from(test).array(), 8, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{0, 2, 0, 0}, Util.Byte.shiftLeft(Bytes.from(test).array(), 9, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{1, 0, 0, 0}, Util.Byte.shiftLeft(Bytes.from(test).array(), 16, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{2, 0, 0, 0}, Util.Byte.shiftLeft(Bytes.from(test).array(), 17, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{-128, 0, 0, 0}, Util.Byte.shiftLeft(Bytes.from(test).array(), 23, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{0, 0, 0, 0}, Util.Byte.shiftLeft(Bytes.from(test).array(), 24, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{0, 0, 0, 0}, Util.Byte.shiftLeft(Bytes.from(test).array(), 24, ByteOrder.BIG_ENDIAN)); + + assertSame(test, Util.Byte.shiftLeft(test, 1, ByteOrder.BIG_ENDIAN)); + + assertArrayEquals(new byte[]{0, 0, 1, 0}, Util.Byte.shiftLeft(new byte[]{0, 1, 0, 0}, 8, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new byte[]{(byte) 0, 0, 1, 0}, Util.Byte.shiftLeft(new byte[]{(byte) 0, 1, 0, 1}, 8, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new byte[]{(byte) 0x54, 0x1}, Util.Byte.shiftLeft(new byte[]{(byte) 0xAA, 0}, 1, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new byte[]{(byte) 0xA8, 0x2}, Util.Byte.shiftLeft(new byte[]{(byte) 0xAA, 0}, 2, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new byte[]{(byte) 0x50, 0x5}, Util.Byte.shiftLeft(new byte[]{(byte) 0xAA, 0}, 3, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new byte[]{(byte) 0xA0, 0xA}, Util.Byte.shiftLeft(new byte[]{(byte) 0xAA, 0}, 4, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new byte[]{(byte) 0x40, 0x15}, Util.Byte.shiftLeft(new byte[]{(byte) 0xAA, 0}, 5, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new byte[]{(byte) 0x80, 0x2A}, Util.Byte.shiftLeft(new byte[]{(byte) 0xAA, 0}, 6, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new byte[]{(byte) 0x00, 0x55}, Util.Byte.shiftLeft(new byte[]{(byte) 0xAA, 0}, 7, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new byte[]{(byte) 0, (byte) 0xAA}, Util.Byte.shiftLeft(new byte[]{(byte) 0xAA, 0}, 8, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new byte[]{0, 0, 0, 0x40}, Util.Byte.shiftLeft(new byte[]{1, 0, 0, 0}, 30, ByteOrder.LITTLE_ENDIAN)); + + } + + @Test + @Ignore + public void testLeftShiftAgainstRefImpl() { + for (int i = 0; i < 1000; i++) { + int shift = 1; + Bytes rnd = Bytes.random(4 + new Random().nextInt(14)); + + byte[] expected = Bytes.from(new BigInteger(rnd.array()).shiftLeft(shift).toByteArray()).resize(rnd.length(), BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_MAX_LENGTH).array(); + byte[] actual = Bytes.from(Util.Byte.shiftLeft(rnd.copy().array(), shift, ByteOrder.BIG_ENDIAN)).resize(rnd.length(), BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_MAX_LENGTH).array(); + + System.out.println("Original \t" + rnd.encodeBinary() + " << " + shift); + System.out.println("Expected \t" + Bytes.wrap(expected).encodeBinary()); + System.out.println("Actual \t" + Bytes.wrap(actual).encodeBinary() + "\n\n"); + + assertArrayEquals(expected, actual); + assertEquals(Bytes.wrap(expected).encodeHex(), Bytes.wrap(actual).encodeHex()); + } + } + + @Test + public void testRightShift() { + byte[] test = new byte[]{0, 0, 16, 0}; + assertEquals(0b01111110, 0b11111101 >>> 1); + assertArrayEquals(new byte[]{0b00101101, (byte) 0b01111110}, Util.Byte.shiftRight(new byte[]{0b01011010, (byte) 0b11111101}, 1, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{0, -128, -128, -128}, Util.Byte.shiftRight(new byte[]{1, 1, 1, 1}, 1, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{0, -128, 66, 0}, Util.Byte.shiftRight(new byte[]{2, 1, 8, 2}, 2, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{0, -128, 66, 0}, new BigInteger(new byte[]{2, 1, 8, 2}).shiftRight(2).toByteArray()); + assertArrayEquals(new byte[]{0, 0, 0, -128}, Util.Byte.shiftRight(Bytes.from(test).array(), 5, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{0, 0, 0, -128}, Util.Byte.shiftRight(new byte[]{0, 0, 1, 0}, 1, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{0, 0, 8, 0}, Util.Byte.shiftRight(Bytes.from(test).array(), 1, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{0, 0, 4, 0}, Util.Byte.shiftRight(Bytes.from(test).array(), 2, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{0, 0, 2, 0}, Util.Byte.shiftRight(Bytes.from(test).array(), 3, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new byte[]{0, 0, 1, 0}, Util.Byte.shiftRight(Bytes.from(test).array(), 4, ByteOrder.BIG_ENDIAN)); + + assertSame(test, Util.Byte.shiftRight(test, 1, ByteOrder.BIG_ENDIAN)); + + assertArrayEquals(new byte[]{0, 0}, Util.Byte.shiftRight(new byte[]{1, 0}, 1, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new byte[]{(byte) 0x80, 0}, Util.Byte.shiftRight(new byte[]{0, 0x02}, 2, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new byte[]{(byte) 0x80, 0, 0, 0}, Util.Byte.shiftRight(new byte[]{0, 0, 0, 1}, 17, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new byte[]{(byte) 1, 0, 0, 0}, Util.Byte.shiftRight(new byte[]{0, 0, 0, (byte) 0x80}, 31, ByteOrder.LITTLE_ENDIAN)); + } + + @Test + public void testRightShiftAgainstRefImpl() { + for (int i = 0; i < 1000; i++) { + int shift = new Random().nextInt(64); + Bytes rnd = Bytes.random(4 + new Random().nextInt(12)); + if (!rnd.bitAt(rnd.lengthBit() - 1)) { //only unsigned + byte[] expected = Bytes.from(new BigInteger(rnd.array()).shiftRight(shift).toByteArray()).resize(rnd.length(), BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_MAX_LENGTH).array(); + byte[] actual = Bytes.from(Util.Byte.shiftRight(rnd.copy().array(), shift, ByteOrder.BIG_ENDIAN)).resize(rnd.length(), BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_MAX_LENGTH).array(); + +// System.out.println("Original \t" + rnd.encodeBinary() + " >> " + shift); +// System.out.println("Expected \t" + Bytes.wrap(expected).encodeBinary()); +// System.out.println("Actual \t" + Bytes.wrap(actual).encodeBinary() + "\n\n"); + + assertArrayEquals(expected, actual); + assertEquals(Bytes.wrap(expected).encodeHex(), Bytes.wrap(actual).encodeHex()); + } + } + } + + @Test + public void entropy() { + assertEquals(0, Util.Byte.entropy(new byte[0]), 0.1d); + assertEquals(0, Util.Byte.entropy(new byte[1]), 0.1d); + assertEquals(0, Util.Byte.entropy(new byte[256]), 0.1d); + assertEquals(0, Util.Byte.entropy(new byte[]{1}), 0.1d); + assertTrue(Util.Byte.entropy(new byte[]{(byte) 0x8E, (byte) 0xD1, (byte) 0xFD, (byte) 0xAA, 0x12, (byte) 0xAF, (byte) 0x78, 0x09, 0x1E, (byte) 0xD1, (byte) 0xFD, (byte) 0xAA, 0x12, (byte) 0xAF, (byte) 0x00, 0x0A, (byte) 0xEE, (byte) 0xD1, (byte) 0xFD, (byte) 0xAA, 0x12, (byte) 0xAF, (byte) 0x78, 0x11}) > 3.5); + assertTrue(Util.Byte.entropy(new byte[]{0x4A, (byte) 0x94, (byte) 0xFD, (byte) 0xFF, 0x1E, (byte) 0xAF, (byte) 0xED}) > 2.5); + assertTrue(Util.Byte.entropy(new byte[]{0x1A, 0x6F}) > 0.5); + } +} diff --git a/src/test/java/at/favre/lib/bytes/UtilConverterTest.java b/src/test/java/at/favre/lib/bytes/UtilConverterTest.java new file mode 100644 index 0000000..3ac6f7b --- /dev/null +++ b/src/test/java/at/favre/lib/bytes/UtilConverterTest.java @@ -0,0 +1,212 @@ +package at.favre.lib.bytes; + +import org.junit.Test; + +import java.nio.ByteOrder; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.*; + +public class UtilConverterTest extends AUtilTest { + + @Test + public void testToArray() { + List none = Collections.emptyList(); + assertArrayEquals(EMPTY, Util.Converter.toArray(none)); + + List one = Collections.singletonList((byte) 1); + assertArrayEquals(ARRAY1, Util.Converter.toArray(one)); + + byte[] array = {(byte) 0, (byte) 1, (byte) 0x55}; + + List three = Arrays.asList((byte) 0, (byte) 1, (byte) 0x55); + assertArrayEquals(array, Util.Converter.toArray(three)); + + assertArrayEquals(array, Util.Converter.toArray(Util.Converter.toList(array))); + } + + @Test + public void testToArray_withNull() { + List list = Arrays.asList((byte) 0, (byte) 1, null); + try { + Util.Converter.toArray(list); + fail(); + } catch (NullPointerException expected) { + } + } + + @Test + public void testToArray_withConversion() { + byte[] array = {(byte) 0, (byte) 1, (byte) 2}; + + List bytes = Arrays.asList((byte) 0, (byte) 1, (byte) 2); + assertArrayEquals(array, Util.Converter.toArray(bytes)); + } + + @Test + public void testAsList_toArray_roundTrip() { + byte[] array = {(byte) 0, (byte) 1, (byte) 2}; + List list = Util.Converter.toList(array); + byte[] newArray = Util.Converter.toArray(list); + + // Make sure it returned a copy + list.set(0, (byte) 4); + assertArrayEquals( + new byte[]{(byte) 0, (byte) 1, (byte) 2}, newArray); + newArray[1] = (byte) 5; + assertEquals((byte) 1, (byte) list.get(1)); + } + + @Test + // This test stems from a real bug found by andrewk + public void testAsList_subList_toArray_roundTrip() { + byte[] array = {(byte) 0, (byte) 1, (byte) 2, (byte) 3}; + List list = Util.Converter.toList(array); + assertArrayEquals(new byte[]{(byte) 1, (byte) 2}, + Util.Converter.toArray(list.subList(1, 3))); + assertArrayEquals(new byte[]{}, + Util.Converter.toArray(list.subList(2, 2))); + } + + @Test + public void testCharToByteArray() { + Charset[] charsets = new Charset[]{StandardCharsets.UTF_8, StandardCharsets.US_ASCII, StandardCharsets.UTF_16}; + for (Charset charset : charsets) { + checkCharArrayToByteArray("".toCharArray(), charset); + checkCharArrayToByteArray("A".toCharArray(), charset); + checkCharArrayToByteArray("12".toCharArray(), charset); + checkCharArrayToByteArray("XYZ".toCharArray(), charset); + checkCharArrayToByteArray("abcdefg".toCharArray(), charset); + checkCharArrayToByteArray("71oh872gdl2dhp81g".toCharArray(), charset); + + } + + checkCharArrayToByteArray("யe2ாமறிந்தиют убSîne klâwenasd1".toCharArray(), StandardCharsets.UTF_8); + } + + private void checkCharArrayToByteArray(char[] subject, Charset charset) { + for (int lenI = 1; lenI < subject.length + 1; lenI++) { + for (int offsetI = 0; offsetI < subject.length; offsetI++) { + if (offsetI + lenI > subject.length) break; + byte[] bytes = Util.Converter.charToByteArray(subject, charset, offsetI, lenI); + assertEquals(Bytes.wrap(bytes), Bytes.wrap(new String(subject).substring(offsetI, offsetI + lenI).getBytes(charset))); + } + } + compareArrayToByteArrayWithoutOffset(subject, charset); + } + + private void compareArrayToByteArrayWithoutOffset(char[] subject, Charset charset) { + assertArrayEquals(Util.Converter.charToByteArray(subject, charset, 0, subject.length), new String(subject).getBytes(charset)); + } + + @Test(expected = IllegalArgumentException.class) + public void testCharToByteArrayIllegalOffset() { + Util.Converter.charToByteArray("abcdef".toCharArray(), StandardCharsets.UTF_8, -1, 1); + } + + @Test(expected = IllegalArgumentException.class) + public void testCharToByteArrayIllegalLength() { + Util.Converter.charToByteArray("abcdef".toCharArray(), StandardCharsets.UTF_8, 0, -1); + } + + @Test(expected = IllegalArgumentException.class) + public void testCharToByteArrayIllegalOffsetPlusLength() { + Util.Converter.charToByteArray("abcdef".toCharArray(), StandardCharsets.UTF_8, 4, 3); + } + + @Test + public void testToIntArray() { + assertArrayEquals(new int[]{1}, Util.Converter.toIntArray(new byte[]{0, 0, 0, 1}, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new int[]{257}, Util.Converter.toIntArray(new byte[]{0, 0, 1, 1}, ByteOrder.BIG_ENDIAN)); + + assertArrayEquals(new int[]{1}, Util.Converter.toIntArray(new byte[]{1, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new int[]{257}, Util.Converter.toIntArray(new byte[]{1, 1, 0, 0}, ByteOrder.LITTLE_ENDIAN)); + + assertArrayEquals(new int[]{16_843_009}, Util.Converter.toIntArray(new byte[]{1, 1, 1, 1}, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new int[]{571_211_845}, Util.Converter.toIntArray(new byte[]{34, 12, 0, 69}, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new int[]{1_290_429_439}, Util.Converter.toIntArray(new byte[]{76, (byte) 234, 99, (byte) 255}, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new int[]{257, 65_793, 1}, Util.Converter.toIntArray(new byte[]{0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1}, ByteOrder.BIG_ENDIAN)); + + assertArrayEquals(new int[]{1_290_429_439}, Util.Converter.toIntArray(new byte[]{(byte) 255, 99, (byte) 234, 76}, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new int[]{257, 65_793, 1}, Util.Converter.toIntArray(new byte[]{1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN)); + + assertArrayEquals(new int[0], Util.Converter.toIntArray(new byte[0], ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new int[0], Util.Converter.toIntArray(new byte[0], ByteOrder.BIG_ENDIAN)); + } + + @Test + public void testToLongArray() { + assertArrayEquals(new long[]{1}, Util.Converter.toLongArray(new byte[]{0, 0, 0, 0, 0, 0, 0, 1}, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new long[]{257}, Util.Converter.toLongArray(new byte[]{0, 0, 0, 0, 0, 0, 1, 1}, ByteOrder.BIG_ENDIAN)); + + assertArrayEquals(new long[]{1}, Util.Converter.toLongArray(new byte[]{1, 0, 0, 0, 0, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new long[]{257}, Util.Converter.toLongArray(new byte[]{1, 1, 0, 0, 0, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN)); + + assertArrayEquals(new long[]{-7124130559744646145L}, Util.Converter.toLongArray(new byte[]{(byte) 157, 34, 1, 0, 76, (byte) 234, 99, (byte) 255}, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new long[]{-7124130559744646145L}, Util.Converter.toLongArray(new byte[]{(byte) 255, 99, (byte) 234, 76, 0, 1, 34, (byte) 157}, ByteOrder.LITTLE_ENDIAN)); + + assertArrayEquals(new long[]{257, 1}, Util.Converter.toLongArray(new byte[]{0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1}, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new long[]{257, 1}, Util.Converter.toLongArray(new byte[]{1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN)); + + assertArrayEquals(new long[0], Util.Converter.toLongArray(new byte[0], ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new long[0], Util.Converter.toLongArray(new byte[0], ByteOrder.BIG_ENDIAN)); + } + + @Test + public void testToFloatArray() { + assertArrayEquals(new float[]{1.4E-45f}, Util.Converter.toFloatArray(new byte[]{0, 0, 0, 1}, ByteOrder.BIG_ENDIAN), 0.01f); + assertArrayEquals(new float[]{3.6E-43f}, Util.Converter.toFloatArray(new byte[]{0, 0, 1, 1}, ByteOrder.BIG_ENDIAN), 0.01f); + + assertArrayEquals(new float[]{1.4E-45f}, Util.Converter.toFloatArray(new byte[]{1, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN), 0.01f); + assertArrayEquals(new float[]{3.6E-43f}, Util.Converter.toFloatArray(new byte[]{1, 1, 0, 0}, ByteOrder.LITTLE_ENDIAN), 0.01f); + + assertArrayEquals(new float[]{2.3694278E-38f}, Util.Converter.toFloatArray(new byte[]{1, 1, 1, 1}, ByteOrder.BIG_ENDIAN), 0.01f); + assertArrayEquals(new float[]{1.897368E-18f}, Util.Converter.toFloatArray(new byte[]{34, 12, 0, 69}, ByteOrder.BIG_ENDIAN), 0.01f); + assertArrayEquals(new float[]{1.22888184E8f}, Util.Converter.toFloatArray(new byte[]{76, (byte) 234, 99, (byte) 255}, ByteOrder.BIG_ENDIAN), 0.01f); + assertArrayEquals(new float[]{3.6E-43f, 9.2196E-41f, 1.4E-45f}, Util.Converter.toFloatArray(new byte[]{0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1}, ByteOrder.BIG_ENDIAN), 0.01f); + + assertArrayEquals(new float[]{1.22888184E8f}, Util.Converter.toFloatArray(new byte[]{(byte) 255, 99, (byte) 234, 76}, ByteOrder.LITTLE_ENDIAN), 0.01f); + assertArrayEquals(new float[]{3.6E-43f, 9.2196E-41f, 1.4E-45f}, Util.Converter.toFloatArray(new byte[]{1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN), 0.01f); + + assertArrayEquals(new float[0], Util.Converter.toFloatArray(new byte[0], ByteOrder.LITTLE_ENDIAN), 0.01f); + assertArrayEquals(new float[0], Util.Converter.toFloatArray(new byte[0], ByteOrder.BIG_ENDIAN), 0.01f); + } + + @Test + public void testToDoubleArray() { + assertArrayEquals(new double[]{1.4E-45}, Util.Converter.toDoubleArray(new byte[]{0, 0, 0, 0, 0, 0, 0, 1}, ByteOrder.BIG_ENDIAN), 0.01); + assertArrayEquals(new double[]{3.6E-43}, Util.Converter.toDoubleArray(new byte[]{0, 0, 0, 0, 0, 0, 1, 1}, ByteOrder.BIG_ENDIAN), 0.01); + + assertArrayEquals(new double[]{1.4E-45}, Util.Converter.toDoubleArray(new byte[]{1, 0, 0, 0, 0, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN), 0.01); + assertArrayEquals(new double[]{3.6E-43}, Util.Converter.toDoubleArray(new byte[]{1, 1, 0, 0, 0, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN), 0.01); + + assertArrayEquals(new double[]{-2.385279556059394E-168}, Util.Converter.toDoubleArray(new byte[]{(byte) 157, 34, 1, 0, 76, (byte) 234, 99, (byte) 255}, ByteOrder.BIG_ENDIAN), 0.01); + assertArrayEquals(new double[]{-2.385279556059394E-168}, Util.Converter.toDoubleArray(new byte[]{(byte) 255, 99, (byte) 234, 76, 0, 1, 34, (byte) 157}, ByteOrder.LITTLE_ENDIAN), 0.01); + + assertArrayEquals(new double[]{1.27E-321, 4.9E-324}, Util.Converter.toDoubleArray(new byte[]{0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1}, ByteOrder.BIG_ENDIAN), 0.01); + assertArrayEquals(new double[]{1.27E-321, 4.9E-324}, Util.Converter.toDoubleArray(new byte[]{1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}, ByteOrder.LITTLE_ENDIAN), 0.01); + + assertArrayEquals(new double[0], Util.Converter.toDoubleArray(new byte[0], ByteOrder.LITTLE_ENDIAN), 0.01); + assertArrayEquals(new double[0], Util.Converter.toDoubleArray(new byte[0], ByteOrder.BIG_ENDIAN), 0.01); + } + + @Test + public void testToShortArray() { + assertArrayEquals(new short[]{1}, Util.Converter.toShortArray(new byte[]{0, 1}, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new short[]{257}, Util.Converter.toShortArray(new byte[]{1, 1}, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new short[]{32_767}, Util.Converter.toShortArray(new byte[]{127, -1}, ByteOrder.BIG_ENDIAN)); + assertArrayEquals(new short[]{257, 32_767, 1}, Util.Converter.toShortArray(new byte[]{1, 1, 127, -1, 0, 1}, ByteOrder.BIG_ENDIAN)); + + assertArrayEquals(new short[]{1}, Util.Converter.toShortArray(new byte[]{1, 0}, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new short[]{257}, Util.Converter.toShortArray(new byte[]{1, 1}, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new short[]{32_767}, Util.Converter.toShortArray(new byte[]{-1, 127}, ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new short[]{257, 32_767, 1}, Util.Converter.toShortArray(new byte[]{1, 1, -1, 127, 1, 0}, ByteOrder.LITTLE_ENDIAN)); + + assertArrayEquals(new short[0], Util.Converter.toShortArray(new byte[0], ByteOrder.LITTLE_ENDIAN)); + assertArrayEquals(new short[0], Util.Converter.toShortArray(new byte[0], ByteOrder.BIG_ENDIAN)); + } +} diff --git a/src/test/java/at/favre/lib/bytes/UtilTest.java b/src/test/java/at/favre/lib/bytes/UtilTest.java deleted file mode 100644 index 03ff618..0000000 --- a/src/test/java/at/favre/lib/bytes/UtilTest.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Copyright 2017 Patrick Favre-Bulle - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package at.favre.lib.bytes; - -import org.junit.Ignore; -import org.junit.Test; - -import java.math.BigInteger; -import java.util.Arrays; -import java.util.List; -import java.util.Random; - -import static junit.framework.TestCase.assertTrue; -import static org.junit.Assert.*; - -public class UtilTest { - private static final byte[] EMPTY = {}; - private static final byte[] ARRAY1 = {(byte) 1}; - private static final byte[] ARRAY234 = {(byte) 2, (byte) 3, (byte) 4}; - - @Test - public void testIndexOf() { - assertEquals(-1, indexOf(EMPTY, (byte) 1)); - assertEquals(-1, indexOf(ARRAY1, (byte) 2)); - assertEquals(-1, indexOf(ARRAY234, (byte) 1)); - assertEquals(0, indexOf( - new byte[]{(byte) -1}, (byte) -1)); - assertEquals(0, indexOf(ARRAY234, (byte) 2)); - assertEquals(1, indexOf(ARRAY234, (byte) 3)); - assertEquals(2, indexOf(ARRAY234, (byte) 4)); - assertEquals(1, indexOf( - new byte[]{(byte) 2, (byte) 3, (byte) 2, (byte) 3}, - (byte) 3)); - } - - private int indexOf(byte[] empty, byte b) { - return Util.indexOf(empty, b, 0, empty.length); - } - - @Test - public void testIndexOf_arrayTarget() { - assertEquals(0, Util.indexOf(EMPTY, EMPTY)); - assertEquals(0, Util.indexOf(ARRAY234, EMPTY)); - assertEquals(-1, Util.indexOf(EMPTY, ARRAY234)); - assertEquals(-1, Util.indexOf(ARRAY234, ARRAY1)); - assertEquals(-1, Util.indexOf(ARRAY1, ARRAY234)); - assertEquals(0, Util.indexOf(ARRAY1, ARRAY1)); - assertEquals(0, Util.indexOf(ARRAY234, ARRAY234)); - assertEquals(0, Util.indexOf( - ARRAY234, new byte[]{(byte) 2, (byte) 3})); - assertEquals(1, Util.indexOf( - ARRAY234, new byte[]{(byte) 3, (byte) 4})); - assertEquals(1, Util.indexOf(ARRAY234, new byte[]{(byte) 3})); - assertEquals(2, Util.indexOf(ARRAY234, new byte[]{(byte) 4})); - assertEquals(1, Util.indexOf(new byte[]{(byte) 2, (byte) 3, (byte) 3, (byte) 3, (byte) 3}, new byte[]{(byte) 3})); - assertEquals(2, Util.indexOf( - new byte[]{(byte) 2, (byte) 3, (byte) 2, - (byte) 3, (byte) 4, (byte) 2, (byte) 3}, - new byte[]{(byte) 2, (byte) 3, (byte) 4} - )); - assertEquals(1, Util.indexOf( - new byte[]{(byte) 2, (byte) 2, (byte) 3, - (byte) 4, (byte) 2, (byte) 3, (byte) 4}, - new byte[]{(byte) 2, (byte) 3, (byte) 4} - )); - assertEquals(-1, Util.indexOf( - new byte[]{(byte) 4, (byte) 3, (byte) 2}, - new byte[]{(byte) 2, (byte) 3, (byte) 4} - )); - } - - @Test - public void testLastIndexOf() { - assertEquals(-1, lastIndexOf(EMPTY, (byte) 1)); - assertEquals(-1, lastIndexOf(ARRAY1, (byte) 2)); - assertEquals(-1, lastIndexOf(ARRAY234, (byte) 1)); - assertEquals(0, lastIndexOf( - new byte[]{(byte) -1}, (byte) -1)); - assertEquals(0, lastIndexOf(ARRAY234, (byte) 2)); - assertEquals(1, lastIndexOf(ARRAY234, (byte) 3)); - assertEquals(2, lastIndexOf(ARRAY234, (byte) 4)); - assertEquals(3, lastIndexOf( - new byte[]{(byte) 2, (byte) 3, (byte) 2, (byte) 3}, - (byte) 3)); - } - - private int lastIndexOf(byte[] empty, byte b) { - return Util.lastIndexOf(empty, b, 0, empty.length); - } - - @Test - public void testConcat() { - assertTrue(Arrays.equals(EMPTY, Util.concat())); - assertTrue(Arrays.equals(EMPTY, Util.concat(EMPTY))); - assertTrue(Arrays.equals(EMPTY, Util.concat(EMPTY, EMPTY, EMPTY))); - assertTrue(Arrays.equals(ARRAY1, Util.concat(ARRAY1))); - assertNotSame(ARRAY1, Util.concat(ARRAY1)); - assertTrue(Arrays.equals(ARRAY1, Util.concat(EMPTY, ARRAY1, EMPTY))); - assertTrue(Arrays.equals( - new byte[]{(byte) 1, (byte) 1, (byte) 1}, - Util.concat(ARRAY1, ARRAY1, ARRAY1))); - assertTrue(Arrays.equals( - new byte[]{(byte) 1, (byte) 2, (byte) 3, (byte) 4}, - Util.concat(ARRAY1, ARRAY234))); - } - - @Test - public void testToArray() { - // need explicit type parameter to avoid javac warning!? - List none = Arrays.asList(); - assertTrue(Arrays.equals(EMPTY, Util.toArray(none))); - - List one = Arrays.asList((byte) 1); - assertTrue(Arrays.equals(ARRAY1, Util.toArray(one))); - - byte[] array = {(byte) 0, (byte) 1, (byte) 0x55}; - - List three = Arrays.asList((byte) 0, (byte) 1, (byte) 0x55); - assertTrue(Arrays.equals(array, Util.toArray(three))); - - assertTrue(Arrays.equals(array, Util.toArray(Util.toList(array)))); - } - - @Test - public void testToArray_withNull() { - List list = Arrays.asList((byte) 0, (byte) 1, null); - try { - Util.toArray(list); - fail(); - } catch (NullPointerException expected) { - } - } - - @Test - public void testToArray_withConversion() { - byte[] array = {(byte) 0, (byte) 1, (byte) 2}; - - List bytes = Arrays.asList((byte) 0, (byte) 1, (byte) 2); - assertTrue(Arrays.equals(array, Util.toArray(bytes))); - } - - @Test - public void testAsList_toArray_roundTrip() { - byte[] array = {(byte) 0, (byte) 1, (byte) 2}; - List list = Util.toList(array); - byte[] newArray = Util.toArray(list); - - // Make sure it returned a copy - list.set(0, (byte) 4); - assertTrue(Arrays.equals( - new byte[]{(byte) 0, (byte) 1, (byte) 2}, newArray)); - newArray[1] = (byte) 5; - assertEquals((byte) 1, (byte) list.get(1)); - } - - @Test - // This test stems from a real bug found by andrewk - public void testAsList_subList_toArray_roundTrip() { - byte[] array = {(byte) 0, (byte) 1, (byte) 2, (byte) 3}; - List list = Util.toList(array); - assertTrue(Arrays.equals(new byte[]{(byte) 1, (byte) 2}, - Util.toArray(list.subList(1, 3)))); - assertTrue(Arrays.equals(new byte[]{}, - Util.toArray(list.subList(2, 2)))); - } - - @Test(expected = IllegalStateException.class) - public void readFromStream() { - Util.readFromStream(null); - } - - @Test - public void concatVararg() { - assertArrayEquals(new byte[]{1}, Util.concatVararg((byte) 1, null)); - } - - @Test - public void testReverse() { - testReverse(new byte[]{}, new byte[]{}); - testReverse(new byte[]{1}, new byte[]{1}); - testReverse(new byte[]{1, 2}, new byte[]{2, 1}); - testReverse(new byte[]{3, 1, 1}, new byte[]{1, 1, 3}); - testReverse(new byte[]{-1, 1, -2, 2}, new byte[]{2, -2, 1, -1}); - } - - @Test - public void testReverseIndexed() { - testReverse(new byte[]{}, 0, 0, new byte[]{}); - testReverse(new byte[]{1}, 0, 1, new byte[]{1}); - testReverse(new byte[]{1, 2}, 0, 2, new byte[]{2, 1}); - testReverse(new byte[]{3, 1, 1}, 0, 2, new byte[]{1, 3, 1}); - testReverse(new byte[]{3, 1, 1}, 0, 1, new byte[]{3, 1, 1}); - testReverse(new byte[]{-1, 1, -2, 2}, 1, 3, new byte[]{-1, -2, 1, 2}); - } - - private static void testReverse(byte[] input, byte[] expectedOutput) { - input = Arrays.copyOf(input, input.length); - Util.reverse(input, 0, input.length); - assertTrue(Arrays.equals(expectedOutput, input)); - } - - private static void testReverse(byte[] input, int fromIndex, int toIndex, byte[] expectedOutput) { - input = Arrays.copyOf(input, input.length); - Util.reverse(input, fromIndex, toIndex); - assertTrue(Arrays.equals(expectedOutput, input)); - } - - @Test - public void testLeftShift() { - byte[] test = new byte[]{0, 0, 1, 0}; - assertArrayEquals(new byte[]{0, 1, 0, 0}, Util.shiftLeft(new byte[]{0, 0, -128, 0}, 1)); - assertArrayEquals(new byte[]{0, 1, 0, 0}, Util.shiftLeft(new byte[]{0, 0, 64, 0}, 2)); - assertArrayEquals(new byte[]{1, 1, 1, 0}, Util.shiftLeft(new byte[]{-128, -128, -128, -128}, 1)); - assertArrayEquals(new byte[]{0, 0, 2, 0}, Util.shiftLeft(Bytes.from(test).array(), 1)); - assertArrayEquals(new byte[]{0, 0, 4, 0}, Util.shiftLeft(Bytes.from(test).array(), 2)); - assertArrayEquals(new byte[]{0, 0, 8, 0}, Util.shiftLeft(Bytes.from(test).array(), 3)); - assertArrayEquals(new byte[]{0, 1, 0, 0}, Util.shiftLeft(Bytes.from(test).array(), 8)); - assertArrayEquals(new byte[]{0, 2, 0, 0}, Util.shiftLeft(Bytes.from(test).array(), 9)); - assertArrayEquals(new byte[]{1, 0, 0, 0}, Util.shiftLeft(Bytes.from(test).array(), 16)); - assertArrayEquals(new byte[]{2, 0, 0, 0}, Util.shiftLeft(Bytes.from(test).array(), 17)); - assertArrayEquals(new byte[]{-128, 0, 0, 0}, Util.shiftLeft(Bytes.from(test).array(), 23)); - assertArrayEquals(new byte[]{0, 0, 0, 0}, Util.shiftLeft(Bytes.from(test).array(), 24)); - assertArrayEquals(new byte[]{0, 0, 0, 0}, Util.shiftLeft(Bytes.from(test).array(), 24)); - - assertSame(test, Util.shiftLeft(test, 1)); - } - - @Test - @Ignore - public void testLeftShiftAgainstRefImpl() { - for (int i = 0; i < 1000; i++) { - int shift = 1; - Bytes rnd = Bytes.random(4 + new Random().nextInt(14)); - - byte[] expected = Bytes.from(new BigInteger(rnd.array()).shiftLeft(shift).toByteArray()).resize(rnd.length(), BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_MAX_LENGTH).array(); - byte[] actual = Bytes.from(Util.shiftLeft(rnd.copy().array(), shift)).resize(rnd.length(), BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_MAX_LENGTH).array(); - - System.out.println("Original \t" + rnd.encodeBinary() + " << " + shift); - System.out.println("Expected \t" + Bytes.wrap(expected).encodeBinary()); - System.out.println("Actual \t" + Bytes.wrap(actual).encodeBinary() + "\n\n"); - - assertArrayEquals(expected, actual); - assertEquals(Bytes.wrap(expected).encodeHex(), Bytes.wrap(actual).encodeHex()); - } - } - - @Test - public void testRightShift() { - byte[] test = new byte[]{0, 0, 16, 0}; - assertEquals(0b01111110, 0b11111101 >>> 1); - assertArrayEquals(new byte[]{0b00101101, (byte) 0b01111110}, Util.shiftRight(new byte[]{0b01011010, (byte) 0b11111101}, 1)); - assertArrayEquals(new byte[]{0, -128, -128, -128}, Util.shiftRight(new byte[]{1, 1, 1, 1}, 1)); - assertArrayEquals(new byte[]{0, -128, 66, 0}, Util.shiftRight(new byte[]{2, 1, 8, 2}, 2)); - assertArrayEquals(new byte[]{0, -128, 66, 0}, new BigInteger(new byte[]{2, 1, 8, 2}).shiftRight(2).toByteArray()); - assertArrayEquals(new byte[]{0, 0, 0, -128}, Util.shiftRight(Bytes.from(test).array(), 5)); - assertArrayEquals(new byte[]{0, 0, 0, -128}, Util.shiftRight(new byte[]{0, 0, 1, 0}, 1)); - assertArrayEquals(new byte[]{0, 0, 8, 0}, Util.shiftRight(Bytes.from(test).array(), 1)); - assertArrayEquals(new byte[]{0, 0, 4, 0}, Util.shiftRight(Bytes.from(test).array(), 2)); - assertArrayEquals(new byte[]{0, 0, 2, 0}, Util.shiftRight(Bytes.from(test).array(), 3)); - assertArrayEquals(new byte[]{0, 0, 1, 0}, Util.shiftRight(Bytes.from(test).array(), 4)); - - assertSame(test, Util.shiftRight(test, 1)); - } - - @Test - public void testRightShiftAgainstRefImpl() { - for (int i = 0; i < 1000; i++) { - int shift = new Random().nextInt(64); - Bytes rnd = Bytes.random(4 + new Random().nextInt(12)); - if (!rnd.bitAt(rnd.lengthBit() - 1)) { //only unsigned - byte[] expected = Bytes.from(new BigInteger(rnd.array()).shiftRight(shift).toByteArray()).resize(rnd.length(), BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_MAX_LENGTH).array(); - byte[] actual = Bytes.from(Util.shiftRight(rnd.copy().array(), shift)).resize(rnd.length(), BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_MAX_LENGTH).array(); - -// System.out.println("Original \t" + rnd.encodeBinary() + " >> " + shift); -// System.out.println("Expected \t" + Bytes.wrap(expected).encodeBinary()); -// System.out.println("Actual \t" + Bytes.wrap(actual).encodeBinary() + "\n\n"); - - assertArrayEquals(expected, actual); - assertEquals(Bytes.wrap(expected).encodeHex(), Bytes.wrap(actual).encodeHex()); - } - } - } -} diff --git a/src/test/java/at/favre/lib/bytes/otherPackage/BytesTransformTest.java b/src/test/java/at/favre/lib/bytes/otherPackage/BytesTransformTest.java index 619c0a4..8756289 100644 --- a/src/test/java/at/favre/lib/bytes/otherPackage/BytesTransformTest.java +++ b/src/test/java/at/favre/lib/bytes/otherPackage/BytesTransformTest.java @@ -31,7 +31,7 @@ public class BytesTransformTest { @Test - public void testApiVisible() throws Exception { + public void testApiVisible() { assertFalse(BytesTransformers.checksum(new CRC32(), ChecksumTransformer.Mode.TRANSFORM, 4).supportInPlaceTransformation()); } -} \ No newline at end of file +}