diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5ace460..b6006ef 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,6 +1,12 @@ version: 2 updates: - - package-ecosystem: "github-actions" + + - package-ecosystem: "maven" directory: "/" schedule: interval: "weekly" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..de2aad9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: Java CI + +on: + schedule: + - cron: '42 0 * * 4' + push: + branches: + - master + pull_request: + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + - name: Set up JDK 21 + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 21 + cache: 'maven' + - name: Build & run tests with Maven + run: ./mvnw -V verify diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml deleted file mode 100644 index db8679a..0000000 --- a/.github/workflows/maven.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Java CI - -on: [push] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - - name: Set up JDK 11 - uses: actions/setup-java@v5 - with: - distribution: temurin - java-version: 11 - - name: Build with Maven - run: mvn -B test-compile - - name: Run tests - timeout-minutes: 10 - run: mvn -B test diff --git a/.mvn/maven.config b/.mvn/maven.config new file mode 100644 index 0000000..18c894e --- /dev/null +++ b/.mvn/maven.config @@ -0,0 +1,7 @@ +--strict-checksums +-Daether.checksums.algorithms=SHA-512,SHA-1,MD5 +-Daether.trustedChecksumsSource.summaryFile=true +-Daether.trustedChecksumsSource.summaryFile.basedir=${session.rootDirectory}/.mvn/checksums/ +-Daether.artifactResolver.postProcessor.trustedChecksums=true +-Daether.artifactResolver.postProcessor.trustedChecksums.checksumAlgorithms=SHA-512 +-Daether.artifactResolver.postProcessor.trustedChecksums.failIfMissing=false diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..533e775 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,4 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip +distributionSha256Sum=305773a68d6ddfd413df58c82b3f8050e89778e777f3a745c8e5b8cbea4018ef diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..bd8896b --- /dev/null +++ b/mvnw @@ -0,0 +1,295 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.4 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..5761d94 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml index 187915d..acf9db2 100644 --- a/pom.xml +++ b/pom.xml @@ -1,27 +1,15 @@ - + + 4.0.0 com.github.multiformats java-multibase - v1.3.0-SNAPSHOT + 1.3.0-SNAPSHOT jar multibase https://github.com/multiformats/java-multibase - - https://github.com/multiformats/java-multibase/issues - GitHub Issues - - - - https://github.com/multiformats/java-multibase - scm:git:git://github.com/multiformats/java-multibase.git - scm:git:git@github.com:multiformats/java-multibase.git - - MIT License @@ -30,24 +18,37 @@ + + scm:git:git://github.com/multiformats/java-multibase.git + scm:git:git@github.com:multiformats/java-multibase.git + HEAD + https://github.com/multiformats/java-multibase + + + + GitHub Issues + https://github.com/multiformats/java-multibase/issues + + + 2026-01-20T00:00:00Z UTF-8 UTF-8 - 5.11.0 - 3.0 + 11 + 6.0.2 org.junit.jupiter - junit-jupiter + junit-jupiter-api ${version.junit} test - org.hamcrest - hamcrest - ${version.hamcrest} + org.junit.jupiter + junit-jupiter-params + ${version.junit} test @@ -56,22 +57,77 @@ org.apache.maven.plugins - maven-compiler-plugin - 3.1 + maven-enforcer-plugin + 3.6.2 - 11 - 11 + + + 3.9.6 + + + 21 + + + + + enforce + + enforce + + validate + + + + + com.diffplug.spotless + spotless-maven-plugin + 3.1.0 + + + + 1.33.0 + + + google-java-format + + + + + + false + + true + true + + + + true + + + + + + ${spotless.action} + + process-sources + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.1 org.apache.maven.plugins maven-surefire-plugin - 3.3.1 + 3.5.4 org.apache.maven.plugins maven-jar-plugin - 3.0.2 + 3.5.0 @@ -82,4 +138,28 @@ + + + format-check + + + env.CI + + + + check + + + + format + + + !env.CI + + + + apply + + + diff --git a/src/main/java/io/ipfs/multibase/Base16.java b/src/main/java/io/ipfs/multibase/Base16.java index db385b8..d26d12f 100644 --- a/src/main/java/io/ipfs/multibase/Base16.java +++ b/src/main/java/io/ipfs/multibase/Base16.java @@ -1,35 +1,35 @@ package io.ipfs.multibase; public class Base16 { - public static byte[] decode(String hex) { - if (hex.length() % 2 == 1) - throw new IllegalArgumentException("Must have an even number of hex digits to convert to bytes!"); - byte[] res = new byte[hex.length()/2]; - for (int i=0; i < res.length; i++) - res[i] = (byte) Integer.parseInt(hex.substring(2*i, 2*i+2), 16); - return res; - } + public static byte[] decode(String hex) { + if (hex.length() % 2 == 1) + throw new IllegalArgumentException( + "Must have an even number of hex digits to convert to bytes!"); + byte[] res = new byte[hex.length() / 2]; + for (int i = 0; i < res.length; i++) + res[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16); + return res; + } - public static String encode(byte[] data) { - return bytesToHex(data); - } + public static String encode(byte[] data) { + return bytesToHex(data); + } - private static String[] HEX_DIGITS = new String[]{ - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; - private static String[] HEX = new String[256]; - static { - for (int i=0; i < 256; i++) - HEX[i] = HEX_DIGITS[(i >> 4) & 0xF] + HEX_DIGITS[i & 0xF]; - } + private static String[] HEX_DIGITS = + new String[] {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; + private static String[] HEX = new String[256]; - public static String byteToHex(byte b) { - return HEX[b & 0xFF]; - } + static { + for (int i = 0; i < 256; i++) HEX[i] = HEX_DIGITS[(i >> 4) & 0xF] + HEX_DIGITS[i & 0xF]; + } - public static String bytesToHex(byte[] data) { - StringBuilder s = new StringBuilder(); - for (byte b : data) - s.append(byteToHex(b)); - return s.toString(); - } + public static String byteToHex(byte b) { + return HEX[b & 0xFF]; + } + + public static String bytesToHex(byte[] data) { + StringBuilder s = new StringBuilder(); + for (byte b : data) s.append(byteToHex(b)); + return s.toString(); + } } diff --git a/src/main/java/io/ipfs/multibase/Base256Emoji.java b/src/main/java/io/ipfs/multibase/Base256Emoji.java index 8cc11e9..e6368f3 100644 --- a/src/main/java/io/ipfs/multibase/Base256Emoji.java +++ b/src/main/java/io/ipfs/multibase/Base256Emoji.java @@ -21,83 +21,85 @@ */ /** - * Base256Emoji - * is an encoding mapping each 0-255 byte value to (or from) a specific single Unicode Emoji character. + * Base256Emoji + * is an encoding mapping each 0-255 byte value to (or from) a specific single Unicode Emoji + * character. * * @author Michael Vorburger.ch */ public class Base256Emoji { - // from https://github.com/multiformats/multibase/blob/master/rfcs/Base256Emoji.md - private static final String[] EMOJIS = { - "๐Ÿš€", "๐Ÿช", "โ˜„", "๐Ÿ›ฐ", "๐ŸŒŒ", "๐ŸŒ‘", "๐ŸŒ’", "๐ŸŒ“", "๐ŸŒ”", "๐ŸŒ•", - "๐ŸŒ–", "๐ŸŒ—", "๐ŸŒ˜", "๐ŸŒ", "๐ŸŒ", "๐ŸŒŽ", "๐Ÿ‰", "โ˜€", "๐Ÿ’ป", "๐Ÿ–ฅ", - "๐Ÿ’พ", "๐Ÿ’ฟ", "๐Ÿ˜‚", "โค", "๐Ÿ˜", "๐Ÿคฃ", "๐Ÿ˜Š", "๐Ÿ™", "๐Ÿ’•", "๐Ÿ˜ญ", - "๐Ÿ˜˜", "๐Ÿ‘", "๐Ÿ˜…", "๐Ÿ‘", "๐Ÿ˜", "๐Ÿ”ฅ", "๐Ÿฅฐ", "๐Ÿ’”", "๐Ÿ’–", "๐Ÿ’™", - "๐Ÿ˜ข", "๐Ÿค”", "๐Ÿ˜†", "๐Ÿ™„", "๐Ÿ’ช", "๐Ÿ˜‰", "โ˜บ", "๐Ÿ‘Œ", "๐Ÿค—", "๐Ÿ’œ", - "๐Ÿ˜”", "๐Ÿ˜Ž", "๐Ÿ˜‡", "๐ŸŒน", "๐Ÿคฆ", "๐ŸŽ‰", "๐Ÿ’ž", "โœŒ", "โœจ", "๐Ÿคท", - "๐Ÿ˜ฑ", "๐Ÿ˜Œ", "๐ŸŒธ", "๐Ÿ™Œ", "๐Ÿ˜‹", "๐Ÿ’—", "๐Ÿ’š", "๐Ÿ˜", "๐Ÿ’›", "๐Ÿ™‚", - "๐Ÿ’“", "๐Ÿคฉ", "๐Ÿ˜„", "๐Ÿ˜€", "๐Ÿ–ค", "๐Ÿ˜ƒ", "๐Ÿ’ฏ", "๐Ÿ™ˆ", "๐Ÿ‘‡", "๐ŸŽถ", - "๐Ÿ˜’", "๐Ÿคญ", "โฃ", "๐Ÿ˜œ", "๐Ÿ’‹", "๐Ÿ‘€", "๐Ÿ˜ช", "๐Ÿ˜‘", "๐Ÿ’ฅ", "๐Ÿ™‹", - "๐Ÿ˜ž", "๐Ÿ˜ฉ", "๐Ÿ˜ก", "๐Ÿคช", "๐Ÿ‘Š", "๐Ÿฅณ", "๐Ÿ˜ฅ", "๐Ÿคค", "๐Ÿ‘‰", "๐Ÿ’ƒ", - "๐Ÿ˜ณ", "โœ‹", "๐Ÿ˜š", "๐Ÿ˜", "๐Ÿ˜ด", "๐ŸŒŸ", "๐Ÿ˜ฌ", "๐Ÿ™ƒ", "๐Ÿ€", "๐ŸŒท", - "๐Ÿ˜ป", "๐Ÿ˜“", "โญ", "โœ…", "๐Ÿฅบ", "๐ŸŒˆ", "๐Ÿ˜ˆ", "๐Ÿค˜", "๐Ÿ’ฆ", "โœ”", - "๐Ÿ˜ฃ", "๐Ÿƒ", "๐Ÿ’", "โ˜น", "๐ŸŽŠ", "๐Ÿ’˜", "๐Ÿ˜ ", "โ˜", "๐Ÿ˜•", "๐ŸŒบ", - "๐ŸŽ‚", "๐ŸŒป", "๐Ÿ˜", "๐Ÿ–•", "๐Ÿ’", "๐Ÿ™Š", "๐Ÿ˜น", "๐Ÿ—ฃ", "๐Ÿ’ซ", "๐Ÿ’€", - "๐Ÿ‘‘", "๐ŸŽต", "๐Ÿคž", "๐Ÿ˜›", "๐Ÿ”ด", "๐Ÿ˜ค", "๐ŸŒผ", "๐Ÿ˜ซ", "โšฝ", "๐Ÿค™", - "โ˜•", "๐Ÿ†", "๐Ÿคซ", "๐Ÿ‘ˆ", "๐Ÿ˜ฎ", "๐Ÿ™†", "๐Ÿป", "๐Ÿƒ", "๐Ÿถ", "๐Ÿ’", - "๐Ÿ˜ฒ", "๐ŸŒฟ", "๐Ÿงก", "๐ŸŽ", "โšก", "๐ŸŒž", "๐ŸŽˆ", "โŒ", "โœŠ", "๐Ÿ‘‹", - "๐Ÿ˜ฐ", "๐Ÿคจ", "๐Ÿ˜ถ", "๐Ÿค", "๐Ÿšถ", "๐Ÿ’ฐ", "๐Ÿ“", "๐Ÿ’ข", "๐ŸคŸ", "๐Ÿ™", - "๐Ÿšจ", "๐Ÿ’จ", "๐Ÿคฌ", "โœˆ", "๐ŸŽ€", "๐Ÿบ", "๐Ÿค“", "๐Ÿ˜™", "๐Ÿ’Ÿ", "๐ŸŒฑ", - "๐Ÿ˜–", "๐Ÿ‘ถ", "๐Ÿฅด", "โ–ถ", "โžก", "โ“", "๐Ÿ’Ž", "๐Ÿ’ธ", "โฌ‡", "๐Ÿ˜จ", - "๐ŸŒš", "๐Ÿฆ‹", "๐Ÿ˜ท", "๐Ÿ•บ", "โš ", "๐Ÿ™…", "๐Ÿ˜Ÿ", "๐Ÿ˜ต", "๐Ÿ‘Ž", "๐Ÿคฒ", - "๐Ÿค ", "๐Ÿคง", "๐Ÿ“Œ", "๐Ÿ”ต", "๐Ÿ’…", "๐Ÿง", "๐Ÿพ", "๐Ÿ’", "๐Ÿ˜—", "๐Ÿค‘", - "๐ŸŒŠ", "๐Ÿคฏ", "๐Ÿท", "โ˜Ž", "๐Ÿ’ง", "๐Ÿ˜ฏ", "๐Ÿ’†", "๐Ÿ‘†", "๐ŸŽค", "๐Ÿ™‡", - "๐Ÿ‘", "โ„", "๐ŸŒด", "๐Ÿ’ฃ", "๐Ÿธ", "๐Ÿ’Œ", "๐Ÿ“", "๐Ÿฅ€", "๐Ÿคข", "๐Ÿ‘…", - "๐Ÿ’ก", "๐Ÿ’ฉ", "๐Ÿ‘", "๐Ÿ“ธ", "๐Ÿ‘ป", "๐Ÿค", "๐Ÿคฎ", "๐ŸŽผ", "๐Ÿฅต", "๐Ÿšฉ", - "๐ŸŽ", "๐ŸŠ", "๐Ÿ‘ผ", "๐Ÿ’", "๐Ÿ“ฃ", "๐Ÿฅ‚" }; + // from https://github.com/multiformats/multibase/blob/master/rfcs/Base256Emoji.md + private static final String[] EMOJIS = { + "๐Ÿš€", "๐Ÿช", "โ˜„", "๐Ÿ›ฐ", "๐ŸŒŒ", "๐ŸŒ‘", "๐ŸŒ’", "๐ŸŒ“", "๐ŸŒ”", "๐ŸŒ•", + "๐ŸŒ–", "๐ŸŒ—", "๐ŸŒ˜", "๐ŸŒ", "๐ŸŒ", "๐ŸŒŽ", "๐Ÿ‰", "โ˜€", "๐Ÿ’ป", "๐Ÿ–ฅ", + "๐Ÿ’พ", "๐Ÿ’ฟ", "๐Ÿ˜‚", "โค", "๐Ÿ˜", "๐Ÿคฃ", "๐Ÿ˜Š", "๐Ÿ™", "๐Ÿ’•", "๐Ÿ˜ญ", + "๐Ÿ˜˜", "๐Ÿ‘", "๐Ÿ˜…", "๐Ÿ‘", "๐Ÿ˜", "๐Ÿ”ฅ", "๐Ÿฅฐ", "๐Ÿ’”", "๐Ÿ’–", "๐Ÿ’™", + "๐Ÿ˜ข", "๐Ÿค”", "๐Ÿ˜†", "๐Ÿ™„", "๐Ÿ’ช", "๐Ÿ˜‰", "โ˜บ", "๐Ÿ‘Œ", "๐Ÿค—", "๐Ÿ’œ", + "๐Ÿ˜”", "๐Ÿ˜Ž", "๐Ÿ˜‡", "๐ŸŒน", "๐Ÿคฆ", "๐ŸŽ‰", "๐Ÿ’ž", "โœŒ", "โœจ", "๐Ÿคท", + "๐Ÿ˜ฑ", "๐Ÿ˜Œ", "๐ŸŒธ", "๐Ÿ™Œ", "๐Ÿ˜‹", "๐Ÿ’—", "๐Ÿ’š", "๐Ÿ˜", "๐Ÿ’›", "๐Ÿ™‚", + "๐Ÿ’“", "๐Ÿคฉ", "๐Ÿ˜„", "๐Ÿ˜€", "๐Ÿ–ค", "๐Ÿ˜ƒ", "๐Ÿ’ฏ", "๐Ÿ™ˆ", "๐Ÿ‘‡", "๐ŸŽถ", + "๐Ÿ˜’", "๐Ÿคญ", "โฃ", "๐Ÿ˜œ", "๐Ÿ’‹", "๐Ÿ‘€", "๐Ÿ˜ช", "๐Ÿ˜‘", "๐Ÿ’ฅ", "๐Ÿ™‹", + "๐Ÿ˜ž", "๐Ÿ˜ฉ", "๐Ÿ˜ก", "๐Ÿคช", "๐Ÿ‘Š", "๐Ÿฅณ", "๐Ÿ˜ฅ", "๐Ÿคค", "๐Ÿ‘‰", "๐Ÿ’ƒ", + "๐Ÿ˜ณ", "โœ‹", "๐Ÿ˜š", "๐Ÿ˜", "๐Ÿ˜ด", "๐ŸŒŸ", "๐Ÿ˜ฌ", "๐Ÿ™ƒ", "๐Ÿ€", "๐ŸŒท", + "๐Ÿ˜ป", "๐Ÿ˜“", "โญ", "โœ…", "๐Ÿฅบ", "๐ŸŒˆ", "๐Ÿ˜ˆ", "๐Ÿค˜", "๐Ÿ’ฆ", "โœ”", + "๐Ÿ˜ฃ", "๐Ÿƒ", "๐Ÿ’", "โ˜น", "๐ŸŽŠ", "๐Ÿ’˜", "๐Ÿ˜ ", "โ˜", "๐Ÿ˜•", "๐ŸŒบ", + "๐ŸŽ‚", "๐ŸŒป", "๐Ÿ˜", "๐Ÿ–•", "๐Ÿ’", "๐Ÿ™Š", "๐Ÿ˜น", "๐Ÿ—ฃ", "๐Ÿ’ซ", "๐Ÿ’€", + "๐Ÿ‘‘", "๐ŸŽต", "๐Ÿคž", "๐Ÿ˜›", "๐Ÿ”ด", "๐Ÿ˜ค", "๐ŸŒผ", "๐Ÿ˜ซ", "โšฝ", "๐Ÿค™", + "โ˜•", "๐Ÿ†", "๐Ÿคซ", "๐Ÿ‘ˆ", "๐Ÿ˜ฎ", "๐Ÿ™†", "๐Ÿป", "๐Ÿƒ", "๐Ÿถ", "๐Ÿ’", + "๐Ÿ˜ฒ", "๐ŸŒฟ", "๐Ÿงก", "๐ŸŽ", "โšก", "๐ŸŒž", "๐ŸŽˆ", "โŒ", "โœŠ", "๐Ÿ‘‹", + "๐Ÿ˜ฐ", "๐Ÿคจ", "๐Ÿ˜ถ", "๐Ÿค", "๐Ÿšถ", "๐Ÿ’ฐ", "๐Ÿ“", "๐Ÿ’ข", "๐ŸคŸ", "๐Ÿ™", + "๐Ÿšจ", "๐Ÿ’จ", "๐Ÿคฌ", "โœˆ", "๐ŸŽ€", "๐Ÿบ", "๐Ÿค“", "๐Ÿ˜™", "๐Ÿ’Ÿ", "๐ŸŒฑ", + "๐Ÿ˜–", "๐Ÿ‘ถ", "๐Ÿฅด", "โ–ถ", "โžก", "โ“", "๐Ÿ’Ž", "๐Ÿ’ธ", "โฌ‡", "๐Ÿ˜จ", + "๐ŸŒš", "๐Ÿฆ‹", "๐Ÿ˜ท", "๐Ÿ•บ", "โš ", "๐Ÿ™…", "๐Ÿ˜Ÿ", "๐Ÿ˜ต", "๐Ÿ‘Ž", "๐Ÿคฒ", + "๐Ÿค ", "๐Ÿคง", "๐Ÿ“Œ", "๐Ÿ”ต", "๐Ÿ’…", "๐Ÿง", "๐Ÿพ", "๐Ÿ’", "๐Ÿ˜—", "๐Ÿค‘", + "๐ŸŒŠ", "๐Ÿคฏ", "๐Ÿท", "โ˜Ž", "๐Ÿ’ง", "๐Ÿ˜ฏ", "๐Ÿ’†", "๐Ÿ‘†", "๐ŸŽค", "๐Ÿ™‡", + "๐Ÿ‘", "โ„", "๐ŸŒด", "๐Ÿ’ฃ", "๐Ÿธ", "๐Ÿ’Œ", "๐Ÿ“", "๐Ÿฅ€", "๐Ÿคข", "๐Ÿ‘…", + "๐Ÿ’ก", "๐Ÿ’ฉ", "๐Ÿ‘", "๐Ÿ“ธ", "๐Ÿ‘ป", "๐Ÿค", "๐Ÿคฎ", "๐ŸŽผ", "๐Ÿฅต", "๐Ÿšฉ", + "๐ŸŽ", "๐ŸŠ", "๐Ÿ‘ผ", "๐Ÿ’", "๐Ÿ“ฃ", "๐Ÿฅ‚" + }; - // TODO Propose adding a Guava dependency to use ImmutableMap instead of this + // TODO Propose adding a Guava dependency to use ImmutableMap instead of this - private static final Map EMOJI_TO_INDEX; - private static final int MAP_EXPECTED_SIZE = EMOJIS.length; - private static final float MAP_LOAD_FACTOR = 1.0f; + private static final Map EMOJI_TO_INDEX; + private static final int MAP_EXPECTED_SIZE = EMOJIS.length; + private static final float MAP_LOAD_FACTOR = 1.0f; - static { - if (EMOJIS.length != 256) { - throw new IllegalStateException("EMOJIS.length must be 256, but is " + EMOJIS.length); - } - - Map mutableMap = new HashMap<>(MAP_EXPECTED_SIZE, MAP_LOAD_FACTOR); - for (int i = 0; i < EMOJIS.length; i++) { - mutableMap.put(EMOJIS[i], i); - } - EMOJI_TO_INDEX = Collections.unmodifiableMap(mutableMap); + static { + if (EMOJIS.length != 256) { + throw new IllegalStateException("EMOJIS.length must be 256, but is " + EMOJIS.length); } - public static String encode(byte[] in) { - StringBuilder sb = new StringBuilder(in.length); - for (byte b : in) { - sb.append(EMOJIS[b & 0xFF]); - } - return sb.toString(); + Map mutableMap = new HashMap<>(MAP_EXPECTED_SIZE, MAP_LOAD_FACTOR); + for (int i = 0; i < EMOJIS.length; i++) { + mutableMap.put(EMOJIS[i], i); } + EMOJI_TO_INDEX = Collections.unmodifiableMap(mutableMap); + } - public static byte[] decode(String in) { - int length = in.codePointCount(0, in.length()); - byte[] bytes = new byte[length]; + public static String encode(byte[] in) { + StringBuilder sb = new StringBuilder(in.length); + for (byte b : in) { + sb.append(EMOJIS[b & 0xFF]); + } + return sb.toString(); + } - for (int i = 0; i < in.codePointCount(0, in.length()); i++) { - int cp = in.codePointAt(in.offsetByCodePoints(0, i)); - String emoji = new String(Character.toChars(cp)); - Integer index = EMOJI_TO_INDEX.get(emoji); - if (index == null) { - throw new IllegalArgumentException("Unknown Base256Emoji character: " + emoji); - } - bytes[i] = (byte) (index & 0xFF); - } + public static byte[] decode(String in) { + int length = in.codePointCount(0, in.length()); + byte[] bytes = new byte[length]; - return bytes; + for (int i = 0; i < in.codePointCount(0, in.length()); i++) { + int cp = in.codePointAt(in.offsetByCodePoints(0, i)); + String emoji = new String(Character.toChars(cp)); + Integer index = EMOJI_TO_INDEX.get(emoji); + if (index == null) { + throw new IllegalArgumentException("Unknown Base256Emoji character: " + emoji); + } + bytes[i] = (byte) (index & 0xFF); } + return bytes; + } } diff --git a/src/main/java/io/ipfs/multibase/Base36.java b/src/main/java/io/ipfs/multibase/Base36.java index 698d17c..0eb9cc5 100644 --- a/src/main/java/io/ipfs/multibase/Base36.java +++ b/src/main/java/io/ipfs/multibase/Base36.java @@ -1,42 +1,41 @@ package io.ipfs.multibase; -import java.math.*; +import java.math.BigInteger; public class Base36 { - public static byte[] decode(String in) { - byte[] withoutLeadingZeroes = new BigInteger(in, 36).toByteArray(); - int zeroPrefixLength = zeroPrefixLength(in); - byte[] res = new byte[zeroPrefixLength + withoutLeadingZeroes.length]; - System.arraycopy(withoutLeadingZeroes, 0, res, zeroPrefixLength, withoutLeadingZeroes.length); - return res; - } + public static byte[] decode(String in) { + byte[] withoutLeadingZeroes = new BigInteger(in, 36).toByteArray(); + int zeroPrefixLength = zeroPrefixLength(in); + byte[] res = new byte[zeroPrefixLength + withoutLeadingZeroes.length]; + System.arraycopy(withoutLeadingZeroes, 0, res, zeroPrefixLength, withoutLeadingZeroes.length); + return res; + } - public static String encode(byte[] in) { - String withoutLeadingZeroes = new BigInteger(1, in).toString(36); - int zeroPrefixLength = zeroPrefixLength(in); - StringBuilder b = new StringBuilder(); - for (int i=0; i < zeroPrefixLength; i++) - b.append("0"); - b.append(withoutLeadingZeroes); - return b.toString(); - } + public static String encode(byte[] in) { + String withoutLeadingZeroes = new BigInteger(1, in).toString(36); + int zeroPrefixLength = zeroPrefixLength(in); + StringBuilder b = new StringBuilder(); + for (int i = 0; i < zeroPrefixLength; i++) b.append("0"); + b.append(withoutLeadingZeroes); + return b.toString(); + } - private static int zeroPrefixLength(byte[] bytes) { - for (int i = 0; i < bytes.length; i++) { - if (bytes[i] != 0) { - return i; - } - } - return bytes.length; + private static int zeroPrefixLength(byte[] bytes) { + for (int i = 0; i < bytes.length; i++) { + if (bytes[i] != 0) { + return i; + } } + return bytes.length; + } - private static int zeroPrefixLength(String in) { - for (int i = 0; i < in.length(); i++) { - if (in.charAt(i) != '0') { - return i; - } - } - return in.length(); + private static int zeroPrefixLength(String in) { + for (int i = 0; i < in.length(); i++) { + if (in.charAt(i) != '0') { + return i; + } } + return in.length(); + } } diff --git a/src/main/java/io/ipfs/multibase/Base58.java b/src/main/java/io/ipfs/multibase/Base58.java index ab0bf40..7d8219e 100644 --- a/src/main/java/io/ipfs/multibase/Base58.java +++ b/src/main/java/io/ipfs/multibase/Base58.java @@ -24,139 +24,146 @@ /** * Base58 is a way to encode Bitcoin addresses (or arbitrary data) as alphanumeric strings. - *

- * Note that this is not the same base58 as used by Flickr, which you may find referenced around the Internet. - *

- * Satoshi explains: why base-58 instead of standard base-64 encoding? + * + *

Note that this is not the same base58 as used by Flickr, which you may find referenced around + * the Internet. + * + *

Satoshi explains: why base-58 instead of standard base-64 encoding? + * *

    - *
  • Don't want 0OIl characters that look the same in some fonts and - * could be used to create visually identical looking account numbers.
  • - *
  • A string with non-alphanumeric characters is not as easily accepted as an account number.
  • - *
  • E-mail usually won't line-break if there's no punctuation to break at.
  • - *
  • Doubleclicking selects the whole number as one word if it's all alphanumeric.
  • + *
  • Don't want 0OIl characters that look the same in some fonts and could be used to create + * visually identical looking account numbers. + *
  • A string with non-alphanumeric characters is not as easily accepted as an account number. + *
  • E-mail usually won't line-break if there's no punctuation to break at. + *
  • Doubleclicking selects the whole number as one word if it's all alphanumeric. *
- *

- * However, note that the encoding/decoding runs in O(n²) time, so it is not useful for large data. - *

- * The basic idea of the encoding is to treat the data bytes as a large number represented using + * + *

However, note that the encoding/decoding runs in O(n²) time, so it is not useful for + * large data. + * + *

The basic idea of the encoding is to treat the data bytes as a large number represented using * base-256 digits, convert the number to be represented using base-58 digits, preserve the exact * number of leading zeros (which are otherwise lost during the mathematical operations on the * numbers), and finally represent the resulting base-58 digits as alphanumeric ASCII characters. */ public class Base58 { - public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); - private static final char ENCODED_ZERO = ALPHABET[0]; - private static final int[] INDEXES = new int[128]; - static { - Arrays.fill(INDEXES, -1); - for (int i = 0; i < ALPHABET.length; i++) { - INDEXES[ALPHABET[i]] = i; - } - } + public static final char[] ALPHABET = + "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + private static final char ENCODED_ZERO = ALPHABET[0]; + private static final int[] INDEXES = new int[128]; - /** - * Encodes the given bytes as a base58 string (no checksum is appended). - * - * @param input the bytes to encode - * @return the base58-encoded string - */ - public static String encode(byte[] input) { - if (input.length == 0) { - return ""; - } - // Count leading zeros. - int zeros = 0; - while (zeros < input.length && input[zeros] == 0) { - ++zeros; - } - // Convert base-256 digits to base-58 digits (plus conversion to ASCII characters) - input = Arrays.copyOf(input, input.length); // since we modify it in-place - char[] encoded = new char[input.length * 2]; // upper bound - int outputStart = encoded.length; - for (int inputStart = zeros; inputStart < input.length; ) { - encoded[--outputStart] = ALPHABET[divmod(input, inputStart, 256, 58)]; - if (input[inputStart] == 0) { - ++inputStart; // optimization - skip leading zeros - } - } - // Preserve exactly as many leading encoded zeros in output as there were leading zeros in input. - while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) { - ++outputStart; - } - while (--zeros >= 0) { - encoded[--outputStart] = ENCODED_ZERO; - } - // Return encoded string (including encoded leading zeros). - return new String(encoded, outputStart, encoded.length - outputStart); + static { + Arrays.fill(INDEXES, -1); + for (int i = 0; i < ALPHABET.length; i++) { + INDEXES[ALPHABET[i]] = i; } + } - /** - * Decodes the given base58 string into the original data bytes. - * - * @param input the base58-encoded string to decode - * @return the decoded data bytes - */ - public static byte[] decode(String input) { - if (input.length() == 0) { - return new byte[0]; - } - // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits). - byte[] input58 = new byte[input.length()]; - for (int i = 0; i < input.length(); ++i) { - char c = input.charAt(i); - int digit = c < 128 ? INDEXES[c] : -1; - if (digit < 0) { - throw new IllegalArgumentException(String.format("Invalid character in Base58: 0x%04x", (int) c)); - } - input58[i] = (byte) digit; - } - // Count leading zeros. - int zeros = 0; - while (zeros < input58.length && input58[zeros] == 0) { - ++zeros; - } - // Convert base-58 digits to base-256 digits. - byte[] decoded = new byte[input.length()]; - int outputStart = decoded.length; - for (int inputStart = zeros; inputStart < input58.length; ) { - decoded[--outputStart] = divmod(input58, inputStart, 58, 256); - if (input58[inputStart] == 0) { - ++inputStart; // optimization - skip leading zeros - } - } - // Ignore extra leading zeroes that were added during the calculation. - while (outputStart < decoded.length && decoded[outputStart] == 0) { - ++outputStart; - } - // Return decoded data (including original number of leading zeros). - return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length); + /** + * Encodes the given bytes as a base58 string (no checksum is appended). + * + * @param input the bytes to encode + * @return the base58-encoded string + */ + public static String encode(byte[] input) { + if (input.length == 0) { + return ""; + } + // Count leading zeros. + int zeros = 0; + while (zeros < input.length && input[zeros] == 0) { + ++zeros; + } + // Convert base-256 digits to base-58 digits (plus conversion to ASCII characters) + input = Arrays.copyOf(input, input.length); // since we modify it in-place + char[] encoded = new char[input.length * 2]; // upper bound + int outputStart = encoded.length; + for (int inputStart = zeros; inputStart < input.length; ) { + encoded[--outputStart] = ALPHABET[divmod(input, inputStart, 256, 58)]; + if (input[inputStart] == 0) { + ++inputStart; // optimization - skip leading zeros + } } + // Preserve exactly as many leading encoded zeros in output as there were leading zeros in + // input. + while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) { + ++outputStart; + } + while (--zeros >= 0) { + encoded[--outputStart] = ENCODED_ZERO; + } + // Return encoded string (including encoded leading zeros). + return new String(encoded, outputStart, encoded.length - outputStart); + } - public static BigInteger decodeToBigInteger(String input) { - return new BigInteger(1, decode(input)); + /** + * Decodes the given base58 string into the original data bytes. + * + * @param input the base58-encoded string to decode + * @return the decoded data bytes + */ + public static byte[] decode(String input) { + if (input.length() == 0) { + return new byte[0]; + } + // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits). + byte[] input58 = new byte[input.length()]; + for (int i = 0; i < input.length(); ++i) { + char c = input.charAt(i); + int digit = c < 128 ? INDEXES[c] : -1; + if (digit < 0) { + throw new IllegalArgumentException( + String.format("Invalid character in Base58: 0x%04x", (int) c)); + } + input58[i] = (byte) digit; + } + // Count leading zeros. + int zeros = 0; + while (zeros < input58.length && input58[zeros] == 0) { + ++zeros; } + // Convert base-58 digits to base-256 digits. + byte[] decoded = new byte[input.length()]; + int outputStart = decoded.length; + for (int inputStart = zeros; inputStart < input58.length; ) { + decoded[--outputStart] = divmod(input58, inputStart, 58, 256); + if (input58[inputStart] == 0) { + ++inputStart; // optimization - skip leading zeros + } + } + // Ignore extra leading zeroes that were added during the calculation. + while (outputStart < decoded.length && decoded[outputStart] == 0) { + ++outputStart; + } + // Return decoded data (including original number of leading zeros). + return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length); + } + + public static BigInteger decodeToBigInteger(String input) { + return new BigInteger(1, decode(input)); + } - /** - * Divides a number, represented as an array of bytes each containing a single digit - * in the specified base, by the given divisor. The given number is modified in-place - * to contain the quotient, and the return value is the remainder. - * - * @param number the number to divide - * @param firstDigit the index within the array of the first non-zero digit - * (this is used for optimization by skipping the leading zeros) - * @param base the base in which the number's digits are represented (up to 256) - * @param divisor the number to divide by (up to 256) - * @return the remainder of the division operation - */ - private static byte divmod(byte[] number, int firstDigit, int base, int divisor) { - // this is just long division which accounts for the base of the input digits - int remainder = 0; - for (int i = firstDigit; i < number.length; i++) { - int digit = (int) number[i] & 0xFF; - int temp = remainder * base + digit; - number[i] = (byte) (temp / divisor); - remainder = temp % divisor; - } - return (byte) remainder; + /** + * Divides a number, represented as an array of bytes each containing a single digit in the + * specified base, by the given divisor. The given number is modified in-place to contain the + * quotient, and the return value is the remainder. + * + * @param number the number to divide + * @param firstDigit the index within the array of the first non-zero digit (this is used for + * optimization by skipping the leading zeros) + * @param base the base in which the number's digits are represented (up to 256) + * @param divisor the number to divide by (up to 256) + * @return the remainder of the division operation + */ + private static byte divmod(byte[] number, int firstDigit, int base, int divisor) { + // this is just long division which accounts for the base of the input digits + int remainder = 0; + for (int i = firstDigit; i < number.length; i++) { + int digit = (int) number[i] & 0xFF; + int temp = remainder * base + digit; + number[i] = (byte) (temp / divisor); + remainder = temp % divisor; } -} \ No newline at end of file + return (byte) remainder; + } +} diff --git a/src/main/java/io/ipfs/multibase/BinaryDecoder.java b/src/main/java/io/ipfs/multibase/BinaryDecoder.java index 36d384a..14be1d3 100644 --- a/src/main/java/io/ipfs/multibase/BinaryDecoder.java +++ b/src/main/java/io/ipfs/multibase/BinaryDecoder.java @@ -24,15 +24,13 @@ */ public interface BinaryDecoder extends Decoder { - /** - * Decodes a byte array and returns the results as a byte array. - * - * @param source - * A byte array which has been encoded with the appropriate encoder - * @return a byte array that contains decoded content - * @throws DecoderException - * A decoder exception is thrown if a Decoder encounters a failure condition during the decode process. - */ - byte[] decode(byte[] source) throws DecoderException; + /** + * Decodes a byte array and returns the results as a byte array. + * + * @param source A byte array which has been encoded with the appropriate encoder + * @return a byte array that contains decoded content + * @throws DecoderException A decoder exception is thrown if a Decoder encounters a failure + * condition during the decode process. + */ + byte[] decode(byte[] source) throws DecoderException; } - diff --git a/src/main/java/io/ipfs/multibase/BinaryEncoder.java b/src/main/java/io/ipfs/multibase/BinaryEncoder.java index 02e2a74..fe33ebf 100644 --- a/src/main/java/io/ipfs/multibase/BinaryEncoder.java +++ b/src/main/java/io/ipfs/multibase/BinaryEncoder.java @@ -24,15 +24,13 @@ */ public interface BinaryEncoder extends Encoder { - /** - * Encodes a byte array and return the encoded data as a byte array. - * - * @param source - * Data to be encoded - * @return A byte array containing the encoded data - * @throws EncoderException - * thrown if the Encoder encounters a failure condition during the encoding process. - */ - byte[] encode(byte[] source) throws EncoderException; + /** + * Encodes a byte array and return the encoded data as a byte array. + * + * @param source Data to be encoded + * @return A byte array containing the encoded data + * @throws EncoderException thrown if the Encoder encounters a failure condition during the + * encoding process. + */ + byte[] encode(byte[] source) throws EncoderException; } - diff --git a/src/main/java/io/ipfs/multibase/CharEncoding.java b/src/main/java/io/ipfs/multibase/CharEncoding.java index 04f8bc6..1eb8168 100644 --- a/src/main/java/io/ipfs/multibase/CharEncoding.java +++ b/src/main/java/io/ipfs/multibase/CharEncoding.java @@ -20,94 +20,109 @@ /** * Character encoding names required of every implementation of the Java platform. * - * From the Java documentation Standard charsets: - *

- * Every implementation of the Java platform is required to support the following character encodings. Consult the - * release documentation for your implementation to see if any other encodings are supported. Consult the release - * documentation for your implementation to see if any other encodings are supported. - *

+ *

From the Java documentation Standard + * charsets: + * + *

Every implementation of the Java platform is required to support the following character + * encodings. Consult the release documentation for your implementation to see if any other + * encodings are supported. Consult the release documentation for your implementation to see if any + * other encodings are supported. * *

    - *
  • US-ASCII
    - * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set.
  • - *
  • ISO-8859-1
    - * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
  • - *
  • UTF-8
    - * Eight-bit Unicode Transformation Format.
  • - *
  • UTF-16BE
    - * Sixteen-bit Unicode Transformation Format, big-endian byte order.
  • - *
  • UTF-16LE
    - * Sixteen-bit Unicode Transformation Format, little-endian byte order.
  • - *
  • UTF-16
    - * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark (either order - * accepted on input, big-endian used on output.)
  • + *
  • US-ASCII
    + * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character + * set. + *
  • ISO-8859-1
    + * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1. + *
  • UTF-8
    + * Eight-bit Unicode Transformation Format. + *
  • UTF-16BE
    + * Sixteen-bit Unicode Transformation Format, big-endian byte order. + *
  • UTF-16LE
    + * Sixteen-bit Unicode Transformation Format, little-endian byte order. + *
  • UTF-16
    + * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial + * byte-order mark (either order accepted on input, big-endian used on output.) *
* - * This perhaps would best belong in the [lang] project. Even if a similar interface is defined in [lang], it is not - * foreseen that [codec] would be made to depend on [lang]. + * This perhaps would best belong in the [lang] project. Even if a similar interface is defined in + * [lang], it is not foreseen that [codec] would be made to depend on [lang]. * - *

- * This class is immutable and thread-safe. - *

+ *

This class is immutable and thread-safe. * - * @see Standard charsets + * @see Standard + * charsets * @since 1.4 * @version $Id$ */ public class CharEncoding { - /** - * CharEncodingISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1. - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - */ - public static final String ISO_8859_1 = "ISO-8859-1"; + /** + * CharEncodingISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1. + * + *

Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard + * charsets + */ + public static final String ISO_8859_1 = "ISO-8859-1"; - /** - * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of the Unicode character set. - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - */ - public static final String US_ASCII = "US-ASCII"; + /** + * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of the Unicode + * character set. + * + *

Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard + * charsets + */ + public static final String US_ASCII = "US-ASCII"; - /** - * Sixteen-bit Unicode Transformation Format, The byte order specified by a mandatory initial byte-order mark - * (either order accepted on input, big-endian used on output) - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - */ - public static final String UTF_16 = "UTF-16"; + /** + * Sixteen-bit Unicode Transformation Format, The byte order specified by a mandatory initial + * byte-order mark (either order accepted on input, big-endian used on output) + * + *

Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard + * charsets + */ + public static final String UTF_16 = "UTF-16"; - /** - * Sixteen-bit Unicode Transformation Format, big-endian byte order. - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - */ - public static final String UTF_16BE = "UTF-16BE"; + /** + * Sixteen-bit Unicode Transformation Format, big-endian byte order. + * + *

Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard + * charsets + */ + public static final String UTF_16BE = "UTF-16BE"; - /** - * Sixteen-bit Unicode Transformation Format, little-endian byte order. - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - */ - public static final String UTF_16LE = "UTF-16LE"; + /** + * Sixteen-bit Unicode Transformation Format, little-endian byte order. + * + *

Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard + * charsets + */ + public static final String UTF_16LE = "UTF-16LE"; - /** - * Eight-bit Unicode Transformation Format. - *

- * Every implementation of the Java platform is required to support this character encoding. - * - * @see Standard charsets - */ - public static final String UTF_8 = "UTF-8"; + /** + * Eight-bit Unicode Transformation Format. + * + *

Every implementation of the Java platform is required to support this character encoding. + * + * @see Standard + * charsets + */ + public static final String UTF_8 = "UTF-8"; } diff --git a/src/main/java/io/ipfs/multibase/Charsets.java b/src/main/java/io/ipfs/multibase/Charsets.java index c15f58b..3f0afb4 100644 --- a/src/main/java/io/ipfs/multibase/Charsets.java +++ b/src/main/java/io/ipfs/multibase/Charsets.java @@ -21,71 +21,72 @@ /** * Charsets required of every implementation of the Java platform. * - * From the Java documentation Standard + *

From the Java documentation Standard * charsets: - *

- * Every implementation of the Java platform is required to support the following character encodings. Consult the - * release documentation for your implementation to see if any other encodings are supported. Consult the release - * documentation for your implementation to see if any other encodings are supported. - *

+ * + *

Every implementation of the Java platform is required to support the following character + * encodings. Consult the release documentation for your implementation to see if any other + * encodings are supported. Consult the release documentation for your implementation to see if any + * other encodings are supported. * *

    - *
  • US-ASCII
    - * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set.
  • - *
  • ISO-8859-1
    - * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
  • - *
  • UTF-8
    - * Eight-bit Unicode Transformation Format.
  • - *
  • UTF-16BE
    - * Sixteen-bit Unicode Transformation Format, big-endian byte order.
  • - *
  • UTF-16LE
    - * Sixteen-bit Unicode Transformation Format, little-endian byte order.
  • - *
  • UTF-16
    - * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark (either order - * accepted on input, big-endian used on output.)
  • + *
  • US-ASCII
    + * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character + * set. + *
  • ISO-8859-1
    + * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1. + *
  • UTF-8
    + * Eight-bit Unicode Transformation Format. + *
  • UTF-16BE
    + * Sixteen-bit Unicode Transformation Format, big-endian byte order. + *
  • UTF-16LE
    + * Sixteen-bit Unicode Transformation Format, little-endian byte order. + *
  • UTF-16
    + * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial + * byte-order mark (either order accepted on input, big-endian used on output.) *
* - * This perhaps would best belong in the Commons Lang project. Even if a similar class is defined in Commons Lang, it is - * not foreseen that Commons Codec would be made to depend on Commons Lang. + * This perhaps would best belong in the Commons Lang project. Even if a similar class is defined in + * Commons Lang, it is not foreseen that Commons Codec would be made to depend on Commons Lang. * - *

- * This class is immutable and thread-safe. - *

+ *

This class is immutable and thread-safe. * - * @see Standard charsets + * @see Standard + * charsets * @since 1.7 * @version $Id: CharEncoding.java 1173287 2011-09-20 18:16:19Z ggregory $ */ public class Charsets { - // - // This class should only contain Charset instances for required encodings. This guarantees that it will load - // correctly and without delay on all Java platforms. - // + // + // This class should only contain Charset instances for required encodings. This guarantees that + // it will load + // correctly and without delay on all Java platforms. + // - /** - * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of the Unicode character set. - *

- * Every implementation of the Java platform is required to support this character encoding. - *

- *

- * On Java 7 or later, use {@link java.nio.charset.StandardCharsets#ISO_8859_1} instead. - *

- * - * @see Standard charsets - */ - public static final Charset US_ASCII = Charset.forName(CharEncoding.US_ASCII); + /** + * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of the Unicode + * character set. + * + *

Every implementation of the Java platform is required to support this character encoding. + * + *

On Java 7 or later, use {@link java.nio.charset.StandardCharsets#ISO_8859_1} instead. + * + * @see Standard + * charsets + */ + public static final Charset US_ASCII = Charset.forName(CharEncoding.US_ASCII); - /** - * Eight-bit Unicode Transformation Format. - *

- * Every implementation of the Java platform is required to support this character encoding. - *

- *

- * On Java 7 or later, use {@link java.nio.charset.StandardCharsets#ISO_8859_1} instead. - *

- * - * @see Standard charsets - */ - public static final Charset UTF_8 = Charset.forName(CharEncoding.UTF_8); + /** + * Eight-bit Unicode Transformation Format. + * + *

Every implementation of the Java platform is required to support this character encoding. + * + *

On Java 7 or later, use {@link java.nio.charset.StandardCharsets#ISO_8859_1} instead. + * + * @see Standard + * charsets + */ + public static final Charset UTF_8 = Charset.forName(CharEncoding.UTF_8); } diff --git a/src/main/java/io/ipfs/multibase/Decoder.java b/src/main/java/io/ipfs/multibase/Decoder.java index 39f3981..6ae0d78 100644 --- a/src/main/java/io/ipfs/multibase/Decoder.java +++ b/src/main/java/io/ipfs/multibase/Decoder.java @@ -19,29 +19,28 @@ /** * Provides the highest level of abstraction for Decoders. - *

- * This is the sister interface of {@link Encoder}. All Decoders implement this common generic interface. - * Allows a user to pass a generic Object to any Decoder implementation in the codec package. - *

- * One of the two interfaces at the center of the codec package. + * + *

This is the sister interface of {@link Encoder}. All Decoders implement this common generic + * interface. Allows a user to pass a generic Object to any Decoder implementation in the codec + * package. + * + *

One of the two interfaces at the center of the codec package. * * @version $Id$ */ public interface Decoder { - /** - * Decodes an "encoded" Object and returns a "decoded" Object. Note that the implementation of this interface will - * try to cast the Object parameter to the specific type expected by a particular Decoder implementation. If a - * {@link ClassCastException} occurs this decode method will throw a DecoderException. - * - * @param source - * the object to decode - * @return a 'decoded" object - * @throws DecoderException - * a decoder exception can be thrown for any number of reasons. Some good candidates are that the - * parameter passed to this method is null, a param cannot be cast to the appropriate type for a - * specific encoder. - */ - Object decode(Object source) throws DecoderException; + /** + * Decodes an "encoded" Object and returns a "decoded" Object. Note that the implementation of + * this interface will try to cast the Object parameter to the specific type expected by a + * particular Decoder implementation. If a {@link ClassCastException} occurs this decode method + * will throw a DecoderException. + * + * @param source the object to decode + * @return a 'decoded" object + * @throws DecoderException a decoder exception can be thrown for any number of reasons. Some good + * candidates are that the parameter passed to this method is null, a param cannot be cast to + * the appropriate type for a specific encoder. + */ + Object decode(Object source) throws DecoderException; } - diff --git a/src/main/java/io/ipfs/multibase/DecoderException.java b/src/main/java/io/ipfs/multibase/DecoderException.java index 78295d4..ddfc94c 100644 --- a/src/main/java/io/ipfs/multibase/DecoderException.java +++ b/src/main/java/io/ipfs/multibase/DecoderException.java @@ -18,69 +18,73 @@ package io.ipfs.multibase; /** - * Thrown when there is a failure condition during the decoding process. This exception is thrown when a {@link Decoder} - * encounters a decoding specific exception such as invalid data, or characters outside of the expected range. + * Thrown when there is a failure condition during the decoding process. This exception is thrown + * when a {@link Decoder} encounters a decoding specific exception such as invalid data, or + * characters outside of the expected range. * * @version $Id$ */ public class DecoderException extends Exception { - /** - * Declares the Serial Version Uid. - * - * @see Always Declare Serial Version Uid - */ - private static final long serialVersionUID = 1L; + /** + * Declares the Serial Version Uid. + * + * @see Always Declare Serial + * Version Uid + */ + private static final long serialVersionUID = 1L; - /** - * Constructs a new exception with null as its detail message. The cause is not initialized, and may - * subsequently be initialized by a call to {@link #initCause}. - * - * @since 1.4 - */ - public DecoderException() { - super(); - } + /** + * Constructs a new exception with null as its detail message. The cause is not + * initialized, and may subsequently be initialized by a call to {@link #initCause}. + * + * @since 1.4 + */ + public DecoderException() { + super(); + } - /** - * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently - * be initialized by a call to {@link #initCause}. - * - * @param message - * The detail message which is saved for later retrieval by the {@link #getMessage()} method. - */ - public DecoderException(final String message) { - super(message); - } + /** + * Constructs a new exception with the specified detail message. The cause is not initialized, and + * may subsequently be initialized by a call to {@link #initCause}. + * + * @param message The detail message which is saved for later retrieval by the {@link + * #getMessage()} method. + */ + public DecoderException(final String message) { + super(message); + } - /** - * Constructs a new exception with the specified detail message and cause. - *

- * Note that the detail message associated with cause is not automatically incorporated into this - * exception's detail message. - * - * @param message - * The detail message which is saved for later retrieval by the {@link #getMessage()} method. - * @param cause - * The cause which is saved for later retrieval by the {@link #getCause()} method. A null - * value is permitted, and indicates that the cause is nonexistent or unknown. - * @since 1.4 - */ - public DecoderException(final String message, final Throwable cause) { - super(message, cause); - } + /** + * Constructs a new exception with the specified detail message and cause. + * + *

Note that the detail message associated with cause is not automatically + * incorporated into this exception's detail message. + * + * @param message The detail message which is saved for later retrieval by the {@link + * #getMessage()} method. + * @param cause The cause which is saved for later retrieval by the {@link #getCause()} method. A + * null value is permitted, and indicates that the cause is nonexistent or + * unknown. + * @since 1.4 + */ + public DecoderException(final String message, final Throwable cause) { + super(message, cause); + } - /** - * Constructs a new exception with the specified cause and a detail message of (cause==null ? - * null : cause.toString()) (which typically contains the class and detail message of cause). - * This constructor is useful for exceptions that are little more than wrappers for other throwables. - * - * @param cause - * The cause which is saved for later retrieval by the {@link #getCause()} method. A null - * value is permitted, and indicates that the cause is nonexistent or unknown. - * @since 1.4 - */ - public DecoderException(final Throwable cause) { - super(cause); - } + /** + * Constructs a new exception with the specified cause and a detail message of + * (cause==null ? + * null : cause.toString()) (which typically contains the class and detail message of + * cause). This constructor is useful for exceptions that are little more than + * wrappers for other throwables. + * + * @param cause The cause which is saved for later retrieval by the {@link #getCause()} method. A + * null value is permitted, and indicates that the cause is nonexistent or + * unknown. + * @since 1.4 + */ + public DecoderException(final Throwable cause) { + super(cause); + } } diff --git a/src/main/java/io/ipfs/multibase/Encoder.java b/src/main/java/io/ipfs/multibase/Encoder.java index c0336fe..facd21a 100644 --- a/src/main/java/io/ipfs/multibase/Encoder.java +++ b/src/main/java/io/ipfs/multibase/Encoder.java @@ -19,26 +19,23 @@ /** * Provides the highest level of abstraction for Encoders. - *

- * This is the sister interface of {@link Decoder}. Every implementation of Encoder provides this - * common generic interface which allows a user to pass a generic Object to any Encoder implementation - * in the codec package. + * + *

This is the sister interface of {@link Decoder}. Every implementation of Encoder provides this + * common generic interface which allows a user to pass a generic Object to any Encoder + * implementation in the codec package. * * @version $Id$ */ public interface Encoder { - /** - * Encodes an "Object" and returns the encoded content as an Object. The Objects here may just be - * byte[] or Strings depending on the implementation used. - * - * @param source - * An object to encode - * @return An "encoded" Object - * @throws EncoderException - * An encoder exception is thrown if the encoder experiences a failure condition during the encoding - * process. - */ - Object encode(Object source) throws EncoderException; + /** + * Encodes an "Object" and returns the encoded content as an Object. The Objects here may just be + * byte[] or Strings depending on the implementation used. + * + * @param source An object to encode + * @return An "encoded" Object + * @throws EncoderException An encoder exception is thrown if the encoder experiences a failure + * condition during the encoding process. + */ + Object encode(Object source) throws EncoderException; } - diff --git a/src/main/java/io/ipfs/multibase/EncoderException.java b/src/main/java/io/ipfs/multibase/EncoderException.java index 5e6b1ea..47d38ee 100644 --- a/src/main/java/io/ipfs/multibase/EncoderException.java +++ b/src/main/java/io/ipfs/multibase/EncoderException.java @@ -18,72 +18,72 @@ package io.ipfs.multibase; /** - * Thrown when there is a failure condition during the encoding process. This exception is thrown when an - * {@link Encoder} encounters a encoding specific exception such as invalid data, inability to calculate a checksum, - * characters outside of the expected range. + * Thrown when there is a failure condition during the encoding process. This exception is thrown + * when an {@link Encoder} encounters a encoding specific exception such as invalid data, inability + * to calculate a checksum, characters outside of the expected range. * * @version $Id$ */ public class EncoderException extends Exception { - /** - * Declares the Serial Version Uid. - * - * @see Always Declare Serial Version Uid - */ - private static final long serialVersionUID = 1L; + /** + * Declares the Serial Version Uid. + * + * @see Always Declare Serial + * Version Uid + */ + private static final long serialVersionUID = 1L; - /** - * Constructs a new exception with null as its detail message. The cause is not initialized, and may - * subsequently be initialized by a call to {@link #initCause}. - * - * @since 1.4 - */ - public EncoderException() { - super(); - } + /** + * Constructs a new exception with null as its detail message. The cause is not + * initialized, and may subsequently be initialized by a call to {@link #initCause}. + * + * @since 1.4 + */ + public EncoderException() { + super(); + } - /** - * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently - * be initialized by a call to {@link #initCause}. - * - * @param message - * a useful message relating to the encoder specific error. - */ - public EncoderException(final String message) { - super(message); - } + /** + * Constructs a new exception with the specified detail message. The cause is not initialized, and + * may subsequently be initialized by a call to {@link #initCause}. + * + * @param message a useful message relating to the encoder specific error. + */ + public EncoderException(final String message) { + super(message); + } - /** - * Constructs a new exception with the specified detail message and cause. - * - *

- * Note that the detail message associated with cause is not automatically incorporated into this - * exception's detail message. - *

- * - * @param message - * The detail message which is saved for later retrieval by the {@link #getMessage()} method. - * @param cause - * The cause which is saved for later retrieval by the {@link #getCause()} method. A null - * value is permitted, and indicates that the cause is nonexistent or unknown. - * @since 1.4 - */ - public EncoderException(final String message, final Throwable cause) { - super(message, cause); - } + /** + * Constructs a new exception with the specified detail message and cause. + * + *

Note that the detail message associated with cause is not automatically + * incorporated into this exception's detail message. + * + * @param message The detail message which is saved for later retrieval by the {@link + * #getMessage()} method. + * @param cause The cause which is saved for later retrieval by the {@link #getCause()} method. A + * null value is permitted, and indicates that the cause is nonexistent or + * unknown. + * @since 1.4 + */ + public EncoderException(final String message, final Throwable cause) { + super(message, cause); + } - /** - * Constructs a new exception with the specified cause and a detail message of (cause==null ? - * null : cause.toString()) (which typically contains the class and detail message of cause). - * This constructor is useful for exceptions that are little more than wrappers for other throwables. - * - * @param cause - * The cause which is saved for later retrieval by the {@link #getCause()} method. A null - * value is permitted, and indicates that the cause is nonexistent or unknown. - * @since 1.4 - */ - public EncoderException(final Throwable cause) { - super(cause); - } + /** + * Constructs a new exception with the specified cause and a detail message of + * (cause==null ? + * null : cause.toString()) (which typically contains the class and detail message of + * cause). This constructor is useful for exceptions that are little more than + * wrappers for other throwables. + * + * @param cause The cause which is saved for later retrieval by the {@link #getCause()} method. A + * null value is permitted, and indicates that the cause is nonexistent or + * unknown. + * @since 1.4 + */ + public EncoderException(final Throwable cause) { + super(cause); + } } diff --git a/src/main/java/io/ipfs/multibase/Multibase.java b/src/main/java/io/ipfs/multibase/Multibase.java index 6f0e823..0d2a7b7 100644 --- a/src/main/java/io/ipfs/multibase/Multibase.java +++ b/src/main/java/io/ipfs/multibase/Multibase.java @@ -1,183 +1,178 @@ package io.ipfs.multibase; +import io.ipfs.multibase.binary.Base32; +import io.ipfs.multibase.binary.Base64; import java.util.Map; import java.util.Optional; import java.util.TreeMap; -import io.ipfs.multibase.binary.Base32; -import io.ipfs.multibase.binary.Base64; - public class Multibase { - public enum Base { - Base1("1"), - Base2("0"), - Base8("7"), - Base10("9"), - Base16("f"), - Base16Upper("F"), - Base32("b"), - Base32Upper("B"), - Base32Pad("c"), - Base32PadUpper("C"), - Base32Hex("v"), - Base32HexUpper("V"), - Base32HexPad("t"), - Base32HexPadUpper("T"), - Base36("k"), - Base36Upper("K"), - Base58BTC("z"), - Base58Flickr("Z"), - Base64("m"), - Base64Url("u"), - Base64Pad("M"), - Base64UrlPad("U"), - Base256Emoji("๐Ÿš€"); + public enum Base { + Base1("1"), + Base2("0"), + Base8("7"), + Base10("9"), + Base16("f"), + Base16Upper("F"), + Base32("b"), + Base32Upper("B"), + Base32Pad("c"), + Base32PadUpper("C"), + Base32Hex("v"), + Base32HexUpper("V"), + Base32HexPad("t"), + Base32HexPadUpper("T"), + Base36("k"), + Base36Upper("K"), + Base58BTC("z"), + Base58Flickr("Z"), + Base64("m"), + Base64Url("u"), + Base64Pad("M"), + Base64UrlPad("U"), + Base256Emoji("๐Ÿš€"); - public String prefix; + public String prefix; - Base(String prefix) { - this.prefix = prefix; - } - - private static Map lookup = new TreeMap<>(); - static { - for (Base b : Base.values()) - lookup.put(b.prefix, b); - } + Base(String prefix) { + this.prefix = prefix; + } - private static Optional lookupOptional(String data) { - if (data == null || data.isEmpty()) - return Optional.empty(); - String p = Character.toString(data.codePointAt(0)); - Base base = lookup.get(p); - if (base != null) - return Optional.of(base); - if (data.startsWith(Base256Emoji.prefix)) - return Optional.of(Base256Emoji); - return Optional.empty(); - } + private static Map lookup = new TreeMap<>(); - public static Base lookup(String data) { - return lookupOptional(data) - .orElseThrow(() -> new IllegalArgumentException("Unknown Multibase type: " + data)); - } + static { + for (Base b : Base.values()) lookup.put(b.prefix, b); } - public static String encode(Base b, byte[] data) { - switch (b) { - case Base58BTC: - return b.prefix + Base58.encode(data); - case Base16: - return b.prefix + Base16.encode(data); - case Base16Upper: - return b.prefix + Base16.encode(data).toUpperCase(); - case Base32: - return b.prefix + new String(new Base32().encode(data)).toLowerCase().replaceAll("=", ""); - case Base32Pad: - return b.prefix + new String(new Base32().encode(data)).toLowerCase(); - case Base32PadUpper: - return b.prefix + new String(new Base32().encode(data)); - case Base32Upper: - return b.prefix + new String(new Base32().encode(data)).replaceAll("=", ""); - case Base32Hex: - return b.prefix + new String(new Base32(true).encode(data)).toLowerCase().replaceAll("=", ""); - case Base32HexPad: - return b.prefix + new String(new Base32(true).encode(data)).toLowerCase(); - case Base32HexPadUpper: - return b.prefix + new String(new Base32(true).encode(data)); - case Base32HexUpper: - return b.prefix + new String(new Base32(true).encode(data)).replaceAll("=", ""); - case Base36: - return b.prefix + Base36.encode(data); - case Base36Upper: - return b.prefix + Base36.encode(data).toUpperCase(); - case Base64: - return b.prefix + Base64.encodeBase64String(data).replaceAll("=", ""); - case Base64Url: - return b.prefix + Base64.encodeBase64URLSafeString(data).replaceAll("=", ""); - case Base64Pad: - return b.prefix + Base64.encodeBase64String(data); - case Base64UrlPad: - return b.prefix + Base64.encodeBase64String(data).replaceAll("\\+", "-").replaceAll("/", "_"); - case Base256Emoji: - return b.prefix + Base256Emoji.encode(data); - default: - throw new UnsupportedOperationException("Unsupported base encoding: " + b.name()); - } + private static Optional lookupOptional(String data) { + if (data == null || data.isEmpty()) return Optional.empty(); + String p = Character.toString(data.codePointAt(0)); + Base base = lookup.get(p); + if (base != null) return Optional.of(base); + if (data.startsWith(Base256Emoji.prefix)) return Optional.of(Base256Emoji); + return Optional.empty(); } - public static Base encoding(String data) { - return Base.lookup(data); + public static Base lookup(String data) { + return lookupOptional(data) + .orElseThrow(() -> new IllegalArgumentException("Unknown Multibase type: " + data)); } + } - public static byte[] decode(String data) { - if (data.isEmpty()) { - throw new IllegalArgumentException("Cannot decode an empty string"); - } - Base b = encoding(data); - String rest = safeSubstringFromIndexOne(data); - switch (b) { - case Base58BTC: - return Base58.decode(rest); - case Base16: - return Base16.decode(rest); - case Base16Upper: - return Base16.decode(rest.toLowerCase()); - case Base32: - case Base32Pad: - return new Base32().decode(rest); - case Base32PadUpper: - case Base32Upper: - return new Base32().decode(rest.toLowerCase()); - case Base32Hex: - case Base32HexPad: - return new Base32(true).decode(rest); - case Base32HexPadUpper: - case Base32HexUpper: - return new Base32(true).decode(rest.toLowerCase()); - case Base36: - return Base36.decode(rest); - case Base36Upper: - return Base36.decode(rest.toLowerCase()); - case Base64: - case Base64Url: - case Base64Pad: - case Base64UrlPad: - return Base64.decodeBase64(rest); - case Base256Emoji: - return Base256Emoji.decode(rest); - default: - throw new UnsupportedOperationException("Unsupported base encoding: " + b.name()); - } + public static String encode(Base b, byte[] data) { + switch (b) { + case Base58BTC: + return b.prefix + Base58.encode(data); + case Base16: + return b.prefix + Base16.encode(data); + case Base16Upper: + return b.prefix + Base16.encode(data).toUpperCase(); + case Base32: + return b.prefix + new String(new Base32().encode(data)).toLowerCase().replaceAll("=", ""); + case Base32Pad: + return b.prefix + new String(new Base32().encode(data)).toLowerCase(); + case Base32PadUpper: + return b.prefix + new String(new Base32().encode(data)); + case Base32Upper: + return b.prefix + new String(new Base32().encode(data)).replaceAll("=", ""); + case Base32Hex: + return b.prefix + + new String(new Base32(true).encode(data)).toLowerCase().replaceAll("=", ""); + case Base32HexPad: + return b.prefix + new String(new Base32(true).encode(data)).toLowerCase(); + case Base32HexPadUpper: + return b.prefix + new String(new Base32(true).encode(data)); + case Base32HexUpper: + return b.prefix + new String(new Base32(true).encode(data)).replaceAll("=", ""); + case Base36: + return b.prefix + Base36.encode(data); + case Base36Upper: + return b.prefix + Base36.encode(data).toUpperCase(); + case Base64: + return b.prefix + Base64.encodeBase64String(data).replaceAll("=", ""); + case Base64Url: + return b.prefix + Base64.encodeBase64URLSafeString(data).replaceAll("=", ""); + case Base64Pad: + return b.prefix + Base64.encodeBase64String(data); + case Base64UrlPad: + return b.prefix + + Base64.encodeBase64String(data).replaceAll("\\+", "-").replaceAll("/", "_"); + case Base256Emoji: + return b.prefix + Base256Emoji.encode(data); + default: + throw new UnsupportedOperationException("Unsupported base encoding: " + b.name()); } + } - private static String safeSubstringFromIndexOne(String data) { - // Check if there's at least 2 code points in the string - if (data.codePointCount(0, data.length()) <= 1) { - return ""; - } + public static Base encoding(String data) { + return Base.lookup(data); + } - // If so, do an Emoji-safe data.substring(1) equivalent: - int charIndex = data.offsetByCodePoints(0, 1); - return data.substring(charIndex); + public static byte[] decode(String data) { + if (data.isEmpty()) { + throw new IllegalArgumentException("Cannot decode an empty string"); + } + Base b = encoding(data); + String rest = safeSubstringFromIndexOne(data); + switch (b) { + case Base58BTC: + return Base58.decode(rest); + case Base16: + return Base16.decode(rest); + case Base16Upper: + return Base16.decode(rest.toLowerCase()); + case Base32: + case Base32Pad: + return new Base32().decode(rest); + case Base32PadUpper: + case Base32Upper: + return new Base32().decode(rest.toLowerCase()); + case Base32Hex: + case Base32HexPad: + return new Base32(true).decode(rest); + case Base32HexPadUpper: + case Base32HexUpper: + return new Base32(true).decode(rest.toLowerCase()); + case Base36: + return Base36.decode(rest); + case Base36Upper: + return Base36.decode(rest.toLowerCase()); + case Base64: + case Base64Url: + case Base64Pad: + case Base64UrlPad: + return Base64.decodeBase64(rest); + case Base256Emoji: + return Base256Emoji.decode(rest); + default: + throw new UnsupportedOperationException("Unsupported base encoding: " + b.name()); } + } - /** - * Check if the given data has a valid multibase prefix. - * - *

- * Please note that "having a valid prefix" is NOT the same as "being an - * entirely valid multibase string"; even if true, it's still entirely - * possible for {@link #decode(String)} to throw an - * IllegalArgumentException, if prefix is valid, but the following - * data is not. - * - * @param data Multibase string to check. - * @return true if the data has a valid multibase prefix, false otherwise; but - * see above. - */ - public static boolean hasValidPrefix(String data) { - return Base.lookupOptional(data).isPresent(); + private static String safeSubstringFromIndexOne(String data) { + // Check if there's at least 2 code points in the string + if (data.codePointCount(0, data.length()) <= 1) { + return ""; } + + // If so, do an Emoji-safe data.substring(1) equivalent: + int charIndex = data.offsetByCodePoints(0, 1); + return data.substring(charIndex); + } + + /** + * Check if the given data has a valid multibase prefix. + * + *

Please note that "having a valid prefix" is NOT the same as "being an entirely valid + * multibase string"; even if true, it's still entirely possible for {@link + * #decode(String)} to throw an IllegalArgumentException, if prefix is valid, but the + * following data is not. + * + * @param data Multibase string to check. + * @return true if the data has a valid multibase prefix, false otherwise; but see above. + */ + public static boolean hasValidPrefix(String data) { + return Base.lookupOptional(data).isPresent(); + } } diff --git a/src/main/java/io/ipfs/multibase/binary/Base32.java b/src/main/java/io/ipfs/multibase/binary/Base32.java index f73fc7e..12cde40 100644 --- a/src/main/java/io/ipfs/multibase/binary/Base32.java +++ b/src/main/java/io/ipfs/multibase/binary/Base32.java @@ -18,529 +18,631 @@ package io.ipfs.multibase.binary; /** - * Provides Base32 encoding and decoding as defined by RFC 4648. + * Provides Base32 encoding and decoding as defined by RFC 4648. * - * From https://commons.apache.org/proper/commons-codec/ + *

From https://commons.apache.org/proper/commons-codec/ + * + *

The class can be parameterized in the following manner with various constructors: * - *

- * The class can be parameterized in the following manner with various constructors: - *

*
    - *
  • Whether to use the "base32hex" variant instead of the default "base32"
  • - *
  • Line length: Default 76. Line length that aren't multiples of 8 will still essentially end up being multiples of - * 8 in the encoded data. - *
  • Line separator: Default is CRLF ("\r\n")
  • + *
  • Whether to use the "base32hex" variant instead of the default "base32" + *
  • Line length: Default 76. Line length that aren't multiples of 8 will still essentially end + * up being multiples of 8 in the encoded data. + *
  • Line separator: Default is CRLF ("\r\n") *
- *

- * This class operates directly on byte streams, and not character streams. - *

- *

- * This class is thread-safe. - *

* - * @see RFC 4648 + *

This class operates directly on byte streams, and not character streams. + * + *

This class is thread-safe. * + * @see RFC 4648 * @since 1.5 * @version $Id$ */ public class Base32 extends BaseNCodec { - /** - * BASE32 characters are 5 bits in length. - * They are formed by taking a block of five octets to form a 40-bit string, - * which is converted into eight BASE32 characters. - */ - private static final int BITS_PER_ENCODED_BYTE = 5; - private static final int BYTES_PER_ENCODED_BLOCK = 8; - private static final int BYTES_PER_UNENCODED_BLOCK = 5; - - /** - * Chunk separator per RFC 2045 section 2.1. - * - * @see RFC 2045 section 2.1 - */ - private static final byte[] CHUNK_SEPARATOR = {'\r', '\n'}; - - /** - * This array is a lookup table that translates Unicode characters drawn from the "Base32 Alphabet" (as specified - * in Table 3 of RFC 4648) into their 5-bit positive integer equivalents. Characters that are not in the Base32 - * alphabet but fall within the bounds of the array are translated to -1. - */ - private static final byte[] DECODE_TABLE = { - // 0 1 2 3 4 5 6 7 8 9 A B C D E F - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20-2f - -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, // 30-3f 2-7 - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40-4f A-O - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 50-5a P-Z - -1, -1, -1, -1, -1, // 5b - 5f - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 60 - 6f a-o - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 70 - 7a p-z/**/ - }; - - /** - * This array is a lookup table that translates 5-bit positive integer index values into their "Base32 Alphabet" - * equivalents as specified in Table 3 of RFC 4648. - */ - private static final byte[] ENCODE_TABLE = { - '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', - '2', '3', '4', '5', '6', '7', - }; - - /** - * This array is a lookup table that translates Unicode characters drawn from the "Base32 Hex Alphabet" (as - * specified in Table 4 of RFC 4648) into their 5-bit positive integer equivalents. Characters that are not in the - * Base32 Hex alphabet but fall within the bounds of the array are translated to -1. - */ - private static final byte[] HEX_DECODE_TABLE = { - // 0 1 2 3 4 5 6 7 8 9 A B C D E F - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20-2f - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 30-3f 2-7 - -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 40-4f A-O - 25, 26, 27, 28, 29, 30, 31, // 50-56 P-V - -1, -1, -1, -1, -1, -1, -1, -1, -1, // 57-5f Z-_ - -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 60-6f `-o - 25, 26, 27, 28, 29, 30, 31 // 70-76 p-v - }; - - /** - * This array is a lookup table that translates 5-bit positive integer index values into their - * "Base32 Hex Alphabet" equivalents as specified in Table 4 of RFC 4648. - */ - private static final byte[] HEX_ENCODE_TABLE = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', - }; - - /** Mask used to extract 5 bits, used when encoding Base32 bytes */ - private static final int MASK_5BITS = 0x1f; - - // The static final fields above are used for the original static byte[] methods on Base32. - // The private member fields below are used with the new streaming approach, which requires - // some state be preserved between calls of encode() and decode(). - - /** - * Place holder for the bytes we're dealing with for our based logic. - * Bitwise operations store and extract the encoding or decoding from this variable. - */ - - /** - * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. - * decodeSize = {@link #BYTES_PER_ENCODED_BLOCK} - 1 + lineSeparator.length; - */ - private final int decodeSize; - - /** - * Decode table to use. - */ - private final byte[] decodeTable; - - /** - * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. - * encodeSize = {@link #BYTES_PER_ENCODED_BLOCK} + lineSeparator.length; - */ - private final int encodeSize; - - /** - * Encode table to use. - */ - private final byte[] encodeTable; - - /** - * Line separator for encoding. Not used when decoding. Only used if lineLength > 0. - */ - private final byte[] lineSeparator; - - /** - * Creates a Base32 codec used for decoding and encoding. - *

- * When encoding the line length is 0 (no chunking). - *

- * - */ - public Base32() { - this(false); - } - - /** - * Creates a Base32 codec used for decoding and encoding. - *

- * When encoding the line length is 0 (no chunking). - *

- * @param pad byte used as padding byte. - */ - public Base32(final byte pad) { - this(false, pad); + /** + * BASE32 characters are 5 bits in length. They are formed by taking a block of five octets to + * form a 40-bit string, which is converted into eight BASE32 characters. + */ + private static final int BITS_PER_ENCODED_BYTE = 5; + + private static final int BYTES_PER_ENCODED_BLOCK = 8; + private static final int BYTES_PER_UNENCODED_BLOCK = 5; + + /** + * Chunk separator per RFC 2045 section 2.1. + * + * @see RFC 2045 section 2.1 + */ + private static final byte[] CHUNK_SEPARATOR = {'\r', '\n'}; + + /** + * This array is a lookup table that translates Unicode characters drawn from the "Base32 + * Alphabet" (as specified in Table 3 of RFC 4648) into their 5-bit positive integer equivalents. + * Characters that are not in the Base32 alphabet but fall within the bounds of the array are + * translated to -1. + */ + private static final byte[] DECODE_TABLE = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20-2f + -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, // 30-3f 2-7 + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40-4f A-O + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 50-5a P-Z + -1, -1, -1, -1, -1, // 5b - 5f + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 60 - 6f a-o + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 70 - 7a p-z/**/ + }; + + /** + * This array is a lookup table that translates 5-bit positive integer index values into their + * "Base32 Alphabet" equivalents as specified in Table 3 of RFC 4648. + */ + private static final byte[] ENCODE_TABLE = { + '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', + '2', '3', '4', '5', '6', '7', + }; + + /** + * This array is a lookup table that translates Unicode characters drawn from the "Base32 Hex + * Alphabet" (as specified in Table 4 of RFC 4648) into their 5-bit positive integer equivalents. + * Characters that are not in the Base32 Hex alphabet but fall within the bounds of the array are + * translated to -1. + */ + private static final byte[] HEX_DECODE_TABLE = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, // 00-0f + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, // 10-1f + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, // 20-2f + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + -1, + -1, + -1, + -1, + -1, + -1, // 30-3f 2-7 + -1, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, // 40-4f A-O + 25, + 26, + 27, + 28, + 29, + 30, + 31, // 50-56 P-V + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, // 57-5f Z-_ + -1, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, // 60-6f `-o + 25, + 26, + 27, + 28, + 29, + 30, + 31 // 70-76 p-v + }; + + /** + * This array is a lookup table that translates 5-bit positive integer index values into their + * "Base32 Hex Alphabet" equivalents as specified in Table 4 of RFC 4648. + */ + private static final byte[] HEX_ENCODE_TABLE = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', + 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + }; + + /** Mask used to extract 5 bits, used when encoding Base32 bytes */ + private static final int MASK_5BITS = 0x1f; + + // The static final fields above are used for the original static byte[] methods on Base32. + // The private member fields below are used with the new streaming approach, which requires + // some state be preserved between calls of encode() and decode(). + + /** + * Place holder for the bytes we're dealing with for our based logic. Bitwise operations store and + * extract the encoding or decoding from this variable. + */ + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs + * resizing. decodeSize = {@link #BYTES_PER_ENCODED_BLOCK} - 1 + lineSeparator.length; + * + */ + private final int decodeSize; + + /** Decode table to use. */ + private final byte[] decodeTable; + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs + * resizing. encodeSize = {@link #BYTES_PER_ENCODED_BLOCK} + lineSeparator.length; + */ + private final int encodeSize; + + /** Encode table to use. */ + private final byte[] encodeTable; + + /** Line separator for encoding. Not used when decoding. Only used if lineLength > 0. */ + private final byte[] lineSeparator; + + /** + * Creates a Base32 codec used for decoding and encoding. + * + *

When encoding the line length is 0 (no chunking). + */ + public Base32() { + this(false); + } + + /** + * Creates a Base32 codec used for decoding and encoding. + * + *

When encoding the line length is 0 (no chunking). + * + * @param pad byte used as padding byte. + */ + public Base32(final byte pad) { + this(false, pad); + } + + /** + * Creates a Base32 codec used for decoding and encoding. + * + *

When encoding the line length is 0 (no chunking). + * + * @param useHex if {@code true} then use Base32 Hex alphabet + */ + public Base32(final boolean useHex) { + this(0, null, useHex, PAD_DEFAULT); + } + + /** + * Creates a Base32 codec used for decoding and encoding. + * + *

When encoding the line length is 0 (no chunking). + * + * @param useHex if {@code true} then use Base32 Hex alphabet + * @param pad byte used as padding byte. + */ + public Base32(final boolean useHex, final byte pad) { + this(0, null, useHex, pad); + } + + /** + * Creates a Base32 codec used for decoding and encoding. + * + *

When encoding the line length is given in the constructor, the line separator is CRLF. + * + * @param lineLength Each line of encoded data will be at most of the given length (rounded down + * to nearest multiple of 8). If lineLength <= 0, then the output will not be divided into + * lines (chunks). Ignored when decoding. + */ + public Base32(final int lineLength) { + this(lineLength, CHUNK_SEPARATOR); + } + + /** + * Creates a Base32 codec used for decoding and encoding. + * + *

When encoding the line length and line separator are given in the constructor. + * + *

Line lengths that aren't multiples of 8 will still essentially end up being multiples of 8 + * in the encoded data. + * + * @param lineLength Each line of encoded data will be at most of the given length (rounded down + * to nearest multiple of 8). If lineLength <= 0, then the output will not be divided into + * lines (chunks). Ignored when decoding. + * @param lineSeparator Each line of encoded data will end with this sequence of bytes. + * @throws IllegalArgumentException The provided lineSeparator included some Base32 characters. + * That's not going to work! + */ + public Base32(final int lineLength, final byte[] lineSeparator) { + this(lineLength, lineSeparator, false, PAD_DEFAULT); + } + + /** + * Creates a Base32 / Base32 Hex codec used for decoding and encoding. + * + *

When encoding the line length and line separator are given in the constructor. + * + *

Line lengths that aren't multiples of 8 will still essentially end up being multiples of 8 + * in the encoded data. + * + * @param lineLength Each line of encoded data will be at most of the given length (rounded down + * to nearest multiple of 8). If lineLength <= 0, then the output will not be divided into + * lines (chunks). Ignored when decoding. + * @param lineSeparator Each line of encoded data will end with this sequence of bytes. + * @param useHex if {@code true}, then use Base32 Hex alphabet, otherwise use Base32 alphabet + * @throws IllegalArgumentException The provided lineSeparator included some Base32 characters. + * That's not going to work! Or the lineLength > 0 and lineSeparator is null. + */ + public Base32(final int lineLength, final byte[] lineSeparator, final boolean useHex) { + this(lineLength, lineSeparator, useHex, PAD_DEFAULT); + } + + /** + * Creates a Base32 / Base32 Hex codec used for decoding and encoding. + * + *

When encoding the line length and line separator are given in the constructor. + * + *

Line lengths that aren't multiples of 8 will still essentially end up being multiples of 8 + * in the encoded data. + * + * @param lineLength Each line of encoded data will be at most of the given length (rounded down + * to nearest multiple of 8). If lineLength <= 0, then the output will not be divided into + * lines (chunks). Ignored when decoding. + * @param lineSeparator Each line of encoded data will end with this sequence of bytes. + * @param useHex if {@code true}, then use Base32 Hex alphabet, otherwise use Base32 alphabet + * @param pad byte used as padding byte. + * @throws IllegalArgumentException The provided lineSeparator included some Base32 characters. + * That's not going to work! Or the lineLength > 0 and lineSeparator is null. + */ + public Base32( + final int lineLength, final byte[] lineSeparator, final boolean useHex, final byte pad) { + super( + BYTES_PER_UNENCODED_BLOCK, + BYTES_PER_ENCODED_BLOCK, + lineLength, + lineSeparator == null ? 0 : lineSeparator.length, + pad); + if (useHex) { + this.encodeTable = HEX_ENCODE_TABLE; + this.decodeTable = HEX_DECODE_TABLE; + } else { + this.encodeTable = ENCODE_TABLE; + this.decodeTable = DECODE_TABLE; } - - /** - * Creates a Base32 codec used for decoding and encoding. - *

- * When encoding the line length is 0 (no chunking). - *

- * @param useHex if {@code true} then use Base32 Hex alphabet - */ - public Base32(final boolean useHex) { - this(0, null, useHex, PAD_DEFAULT); - } - - /** - * Creates a Base32 codec used for decoding and encoding. - *

- * When encoding the line length is 0 (no chunking). - *

- * @param useHex if {@code true} then use Base32 Hex alphabet - * @param pad byte used as padding byte. - */ - public Base32(final boolean useHex, final byte pad) { - this(0, null, useHex, pad); + if (lineLength > 0) { + if (lineSeparator == null) { + throw new IllegalArgumentException( + "lineLength " + lineLength + " > 0, but lineSeparator is null"); + } + // Must be done after initializing the tables + if (containsAlphabetOrPad(lineSeparator)) { + final String sep = StringUtils.newStringUtf8(lineSeparator); + throw new IllegalArgumentException( + "lineSeparator must not contain Base32 characters: [" + sep + "]"); + } + this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length; + this.lineSeparator = new byte[lineSeparator.length]; + System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length); + } else { + this.encodeSize = BYTES_PER_ENCODED_BLOCK; + this.lineSeparator = null; } + this.decodeSize = this.encodeSize - 1; - /** - * Creates a Base32 codec used for decoding and encoding. - *

- * When encoding the line length is given in the constructor, the line separator is CRLF. - *

- * - * @param lineLength - * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of - * 8). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when - * decoding. - */ - public Base32(final int lineLength) { - this(lineLength, CHUNK_SEPARATOR); + if (isInAlphabet(pad) || isWhiteSpace(pad)) { + throw new IllegalArgumentException("pad must not be in alphabet or whitespace"); } - - /** - * Creates a Base32 codec used for decoding and encoding. - *

- * When encoding the line length and line separator are given in the constructor. - *

- *

- * Line lengths that aren't multiples of 8 will still essentially end up being multiples of 8 in the encoded data. - *

- * - * @param lineLength - * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of - * 8). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when - * decoding. - * @param lineSeparator - * Each line of encoded data will end with this sequence of bytes. - * @throws IllegalArgumentException - * The provided lineSeparator included some Base32 characters. That's not going to work! - */ - public Base32(final int lineLength, final byte[] lineSeparator) { - this(lineLength, lineSeparator, false, PAD_DEFAULT); + } + + /** + * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at + * least twice: once with the data to decode, and once with inAvail set to "-1" to alert decoder + * that EOF has been reached. The "-1" call is not necessary when decoding, but it doesn't hurt, + * either. + * + *

Ignores all non-Base32 characters. This is how chunked (e.g. 76 character) data is handled, + * since CR and LF are silently ignored, but has implications for other bytes, too. This method + * subscribes to the garbage-in, garbage-out philosophy: it will not check the provided data for + * validity. + * + * @param in byte[] array of ascii data to Base32 decode. + * @param inPos Position to start reading data from. + * @param inAvail Amount of bytes available from input for encoding. + * @param context the context to be used + *

Output is written to {@link Context#buffer} as 8-bit octets, using {@link Context#pos} + * as the buffer position + */ + @Override + void decode(final byte[] in, int inPos, final int inAvail, final Context context) { + // package protected for access from I/O streams + + if (context.eof) { + return; } - - /** - * Creates a Base32 / Base32 Hex codec used for decoding and encoding. - *

- * When encoding the line length and line separator are given in the constructor. - *

- *

- * Line lengths that aren't multiples of 8 will still essentially end up being multiples of 8 in the encoded data. - *

- * - * @param lineLength - * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of - * 8). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when - * decoding. - * @param lineSeparator - * Each line of encoded data will end with this sequence of bytes. - * @param useHex - * if {@code true}, then use Base32 Hex alphabet, otherwise use Base32 alphabet - * @throws IllegalArgumentException - * The provided lineSeparator included some Base32 characters. That's not going to work! Or the - * lineLength > 0 and lineSeparator is null. - */ - public Base32(final int lineLength, final byte[] lineSeparator, final boolean useHex) { - this(lineLength, lineSeparator, useHex, PAD_DEFAULT); + if (inAvail < 0) { + context.eof = true; } - - /** - * Creates a Base32 / Base32 Hex codec used for decoding and encoding. - *

- * When encoding the line length and line separator are given in the constructor. - *

- *

- * Line lengths that aren't multiples of 8 will still essentially end up being multiples of 8 in the encoded data. - *

- * - * @param lineLength - * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of - * 8). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when - * decoding. - * @param lineSeparator - * Each line of encoded data will end with this sequence of bytes. - * @param useHex - * if {@code true}, then use Base32 Hex alphabet, otherwise use Base32 alphabet - * @param pad byte used as padding byte. - * @throws IllegalArgumentException - * The provided lineSeparator included some Base32 characters. That's not going to work! Or the - * lineLength > 0 and lineSeparator is null. - */ - public Base32(final int lineLength, final byte[] lineSeparator, final boolean useHex, final byte pad) { - super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK, lineLength, - lineSeparator == null ? 0 : lineSeparator.length, pad); - if (useHex) { - this.encodeTable = HEX_ENCODE_TABLE; - this.decodeTable = HEX_DECODE_TABLE; - } else { - this.encodeTable = ENCODE_TABLE; - this.decodeTable = DECODE_TABLE; - } - if (lineLength > 0) { - if (lineSeparator == null) { - throw new IllegalArgumentException("lineLength " + lineLength + " > 0, but lineSeparator is null"); - } - // Must be done after initializing the tables - if (containsAlphabetOrPad(lineSeparator)) { - final String sep = StringUtils.newStringUtf8(lineSeparator); - throw new IllegalArgumentException("lineSeparator must not contain Base32 characters: [" + sep + "]"); - } - this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length; - this.lineSeparator = new byte[lineSeparator.length]; - System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length); - } else { - this.encodeSize = BYTES_PER_ENCODED_BLOCK; - this.lineSeparator = null; - } - this.decodeSize = this.encodeSize - 1; - - if (isInAlphabet(pad) || isWhiteSpace(pad)) { - throw new IllegalArgumentException("pad must not be in alphabet or whitespace"); + for (int i = 0; i < inAvail; i++) { + final byte b = in[inPos++]; + if (b == pad) { + // We're done. + context.eof = true; + break; + } + final byte[] buffer = ensureBufferSize(decodeSize, context); + if (b >= 0 && b < this.decodeTable.length) { + final int result = this.decodeTable[b]; + if (result >= 0) { + context.modulus = (context.modulus + 1) % BYTES_PER_ENCODED_BLOCK; + // collect decoded bytes + context.lbitWorkArea = (context.lbitWorkArea << BITS_PER_ENCODED_BYTE) + result; + if (context.modulus == 0) { // we can output the 5 bytes + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 32) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 24) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); + buffer[context.pos++] = (byte) (context.lbitWorkArea & MASK_8BITS); + } } + } } - /** - *

- * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once - * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1" - * call is not necessary when decoding, but it doesn't hurt, either. - *

- *

- * Ignores all non-Base32 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are - * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in, - * garbage-out philosophy: it will not check the provided data for validity. - *

- * - * @param in - * byte[] array of ascii data to Base32 decode. - * @param inPos - * Position to start reading data from. - * @param inAvail - * Amount of bytes available from input for encoding. - * @param context the context to be used - * - * Output is written to {@link Context#buffer} as 8-bit octets, using {@link Context#pos} as the buffer position - */ - @Override - void decode(final byte[] in, int inPos, final int inAvail, final Context context) { - // package protected for access from I/O streams - - if (context.eof) { - return; - } - if (inAvail < 0) { - context.eof = true; - } - for (int i = 0; i < inAvail; i++) { - final byte b = in[inPos++]; - if (b == pad) { - // We're done. - context.eof = true; - break; - } - final byte[] buffer = ensureBufferSize(decodeSize, context); - if (b >= 0 && b < this.decodeTable.length) { - final int result = this.decodeTable[b]; - if (result >= 0) { - context.modulus = (context.modulus+1) % BYTES_PER_ENCODED_BLOCK; - // collect decoded bytes - context.lbitWorkArea = (context.lbitWorkArea << BITS_PER_ENCODED_BYTE) + result; - if (context.modulus == 0) { // we can output the 5 bytes - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 32) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 24) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); - buffer[context.pos++] = (byte) (context.lbitWorkArea & MASK_8BITS); - } - } - } - } - - // Two forms of EOF as far as Base32 decoder is concerned: actual - // EOF (-1) and first time '=' character is encountered in stream. - // This approach makes the '=' padding characters completely optional. - if (context.eof && context.modulus >= 2) { // if modulus < 2, nothing to do - final byte[] buffer = ensureBufferSize(decodeSize, context); - - // we ignore partial bytes, i.e. only multiples of 8 count - switch (context.modulus) { - case 2 : // 10 bits, drop 2 and output one byte - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 2) & MASK_8BITS); - break; - case 3 : // 15 bits, drop 7 and output 1 byte - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 7) & MASK_8BITS); - break; - case 4 : // 20 bits = 2*8 + 4 - context.lbitWorkArea = context.lbitWorkArea >> 4; // drop 4 bits - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS); - break; - case 5 : // 25bits = 3*8 + 1 - context.lbitWorkArea = context.lbitWorkArea >> 1; - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS); - break; - case 6 : // 30bits = 3*8 + 6 - context.lbitWorkArea = context.lbitWorkArea >> 6; - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS); - break; - case 7 : // 35 = 4*8 +3 - context.lbitWorkArea = context.lbitWorkArea >> 3; - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 24) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS); - break; - default: - // modulus can be 0-7, and we excluded 0,1 already - throw new IllegalStateException("Impossible modulus "+context.modulus); - } - } + // Two forms of EOF as far as Base32 decoder is concerned: actual + // EOF (-1) and first time '=' character is encountered in stream. + // This approach makes the '=' padding characters completely optional. + if (context.eof && context.modulus >= 2) { // if modulus < 2, nothing to do + final byte[] buffer = ensureBufferSize(decodeSize, context); + + // we ignore partial bytes, i.e. only multiples of 8 count + switch (context.modulus) { + case 2: // 10 bits, drop 2 and output one byte + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 2) & MASK_8BITS); + break; + case 3: // 15 bits, drop 7 and output 1 byte + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 7) & MASK_8BITS); + break; + case 4: // 20 bits = 2*8 + 4 + context.lbitWorkArea = context.lbitWorkArea >> 4; // drop 4 bits + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS); + break; + case 5: // 25bits = 3*8 + 1 + context.lbitWorkArea = context.lbitWorkArea >> 1; + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS); + break; + case 6: // 30bits = 3*8 + 6 + context.lbitWorkArea = context.lbitWorkArea >> 6; + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS); + break; + case 7: // 35 = 4*8 +3 + context.lbitWorkArea = context.lbitWorkArea >> 3; + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 24) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 16) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea >> 8) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.lbitWorkArea) & MASK_8BITS); + break; + default: + // modulus can be 0-7, and we excluded 0,1 already + throw new IllegalStateException("Impossible modulus " + context.modulus); + } } - - /** - *

- * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with - * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, so flush last - * remaining bytes (if not multiple of 5). - *

- * - * @param in - * byte[] array of binary data to Base32 encode. - * @param inPos - * Position to start reading data from. - * @param inAvail - * Amount of bytes available from input for encoding. - * @param context the context to be used - */ - @Override - void encode(final byte[] in, int inPos, final int inAvail, final Context context) { - // package protected for access from I/O streams - - if (context.eof) { - return; + } + + /** + * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least + * twice: once with the data to encode, and once with inAvail set to "-1" to alert encoder that + * EOF has been reached, so flush last remaining bytes (if not multiple of 5). + * + * @param in byte[] array of binary data to Base32 encode. + * @param inPos Position to start reading data from. + * @param inAvail Amount of bytes available from input for encoding. + * @param context the context to be used + */ + @Override + void encode(final byte[] in, int inPos, final int inAvail, final Context context) { + // package protected for access from I/O streams + + if (context.eof) { + return; + } + // inAvail < 0 is how we're informed of EOF in the underlying data we're + // encoding. + if (inAvail < 0) { + context.eof = true; + if (0 == context.modulus && lineLength == 0) { + return; // no leftovers to process and not using chunking + } + final byte[] buffer = ensureBufferSize(encodeSize, context); + final int savedPos = context.pos; + switch (context.modulus) { // % 5 + case 0: + break; + case 1: // Only 1 octet; take top 5 bits then remainder + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea >> 3) & MASK_5BITS]; // 8-1*5 = 3 + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea << 2) & MASK_5BITS]; // 5-3=2 + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + break; + case 2: // 2 octets = 16 bits to use + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea >> 11) & MASK_5BITS]; // 16-1*5 = 11 + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea >> 6) & MASK_5BITS]; // 16-2*5 = 6 + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea >> 1) & MASK_5BITS]; // 16-3*5 = 1 + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea << 4) & MASK_5BITS]; // 5-1 = 4 + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + break; + case 3: // 3 octets = 24 bits to use + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea >> 19) & MASK_5BITS]; // 24-1*5 = 19 + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea >> 14) & MASK_5BITS]; // 24-2*5 = 14 + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea >> 9) & MASK_5BITS]; // 24-3*5 = 9 + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea >> 4) & MASK_5BITS]; // 24-4*5 = 4 + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea << 1) & MASK_5BITS]; // 5-4 = 1 + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + break; + case 4: // 4 octets = 32 bits to use + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea >> 27) & MASK_5BITS]; // 32-1*5 = 27 + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea >> 22) & MASK_5BITS]; // 32-2*5 = 22 + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea >> 17) & MASK_5BITS]; // 32-3*5 = 17 + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea >> 12) & MASK_5BITS]; // 32-4*5 = 12 + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea >> 7) & MASK_5BITS]; // 32-5*5 = 7 + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea >> 2) & MASK_5BITS]; // 32-6*5 = 2 + buffer[context.pos++] = + encodeTable[(int) (context.lbitWorkArea << 3) & MASK_5BITS]; // 5-2 = 3 + buffer[context.pos++] = pad; + break; + default: + throw new IllegalStateException("Impossible modulus " + context.modulus); + } + context.currentLinePos += context.pos - savedPos; // keep track of current line position + // if currentPos == 0 we are at the start of a line, so don't add CRLF + if (lineLength > 0 && context.currentLinePos > 0) { // add chunk separator if required + System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); + context.pos += lineSeparator.length; + } + } else { + for (int i = 0; i < inAvail; i++) { + final byte[] buffer = ensureBufferSize(encodeSize, context); + context.modulus = (context.modulus + 1) % BYTES_PER_UNENCODED_BLOCK; + int b = in[inPos++]; + if (b < 0) { + b += 256; } - // inAvail < 0 is how we're informed of EOF in the underlying data we're - // encoding. - if (inAvail < 0) { - context.eof = true; - if (0 == context.modulus && lineLength == 0) { - return; // no leftovers to process and not using chunking - } - final byte[] buffer = ensureBufferSize(encodeSize, context); - final int savedPos = context.pos; - switch (context.modulus) { // % 5 - case 0 : - break; - case 1 : // Only 1 octet; take top 5 bits then remainder - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 3) & MASK_5BITS]; // 8-1*5 = 3 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 2) & MASK_5BITS]; // 5-3=2 - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - break; - case 2 : // 2 octets = 16 bits to use - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 11) & MASK_5BITS]; // 16-1*5 = 11 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 6) & MASK_5BITS]; // 16-2*5 = 6 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 1) & MASK_5BITS]; // 16-3*5 = 1 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 4) & MASK_5BITS]; // 5-1 = 4 - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - break; - case 3 : // 3 octets = 24 bits to use - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 19) & MASK_5BITS]; // 24-1*5 = 19 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 14) & MASK_5BITS]; // 24-2*5 = 14 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 9) & MASK_5BITS]; // 24-3*5 = 9 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 4) & MASK_5BITS]; // 24-4*5 = 4 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 1) & MASK_5BITS]; // 5-4 = 1 - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - break; - case 4 : // 4 octets = 32 bits to use - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 27) & MASK_5BITS]; // 32-1*5 = 27 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 22) & MASK_5BITS]; // 32-2*5 = 22 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 17) & MASK_5BITS]; // 32-3*5 = 17 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 12) & MASK_5BITS]; // 32-4*5 = 12 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 7) & MASK_5BITS]; // 32-5*5 = 7 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 2) & MASK_5BITS]; // 32-6*5 = 2 - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea << 3) & MASK_5BITS]; // 5-2 = 3 - buffer[context.pos++] = pad; - break; - default: - throw new IllegalStateException("Impossible modulus "+context.modulus); - } - context.currentLinePos += context.pos - savedPos; // keep track of current line position - // if currentPos == 0 we are at the start of a line, so don't add CRLF - if (lineLength > 0 && context.currentLinePos > 0){ // add chunk separator if required - System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); - context.pos += lineSeparator.length; - } - } else { - for (int i = 0; i < inAvail; i++) { - final byte[] buffer = ensureBufferSize(encodeSize, context); - context.modulus = (context.modulus+1) % BYTES_PER_UNENCODED_BLOCK; - int b = in[inPos++]; - if (b < 0) { - b += 256; - } - context.lbitWorkArea = (context.lbitWorkArea << 8) + b; // BITS_PER_BYTE - if (0 == context.modulus) { // we have enough bytes to create our output - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 35) & MASK_5BITS]; - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 30) & MASK_5BITS]; - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 25) & MASK_5BITS]; - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 20) & MASK_5BITS]; - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 15) & MASK_5BITS]; - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 10) & MASK_5BITS]; - buffer[context.pos++] = encodeTable[(int)(context.lbitWorkArea >> 5) & MASK_5BITS]; - buffer[context.pos++] = encodeTable[(int)context.lbitWorkArea & MASK_5BITS]; - context.currentLinePos += BYTES_PER_ENCODED_BLOCK; - if (lineLength > 0 && lineLength <= context.currentLinePos) { - System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); - context.pos += lineSeparator.length; - context.currentLinePos = 0; - } - } - } + context.lbitWorkArea = (context.lbitWorkArea << 8) + b; // BITS_PER_BYTE + if (0 == context.modulus) { // we have enough bytes to create our output + buffer[context.pos++] = encodeTable[(int) (context.lbitWorkArea >> 35) & MASK_5BITS]; + buffer[context.pos++] = encodeTable[(int) (context.lbitWorkArea >> 30) & MASK_5BITS]; + buffer[context.pos++] = encodeTable[(int) (context.lbitWorkArea >> 25) & MASK_5BITS]; + buffer[context.pos++] = encodeTable[(int) (context.lbitWorkArea >> 20) & MASK_5BITS]; + buffer[context.pos++] = encodeTable[(int) (context.lbitWorkArea >> 15) & MASK_5BITS]; + buffer[context.pos++] = encodeTable[(int) (context.lbitWorkArea >> 10) & MASK_5BITS]; + buffer[context.pos++] = encodeTable[(int) (context.lbitWorkArea >> 5) & MASK_5BITS]; + buffer[context.pos++] = encodeTable[(int) context.lbitWorkArea & MASK_5BITS]; + context.currentLinePos += BYTES_PER_ENCODED_BLOCK; + if (lineLength > 0 && lineLength <= context.currentLinePos) { + System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); + context.pos += lineSeparator.length; + context.currentLinePos = 0; + } } + } } - - /** - * Returns whether or not the {@code octet} is in the Base32 alphabet. - * - * @param octet - * The value to test - * @return {@code true} if the value is defined in the the Base32 alphabet {@code false} otherwise. - */ - @Override - public boolean isInAlphabet(final byte octet) { - return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1; - } + } + + /** + * Returns whether or not the {@code octet} is in the Base32 alphabet. + * + * @param octet The value to test + * @return {@code true} if the value is defined in the the Base32 alphabet {@code false} + * otherwise. + */ + @Override + public boolean isInAlphabet(final byte octet) { + return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1; + } } diff --git a/src/main/java/io/ipfs/multibase/binary/Base64.java b/src/main/java/io/ipfs/multibase/binary/Base64.java index 9627f5b..be3cff4 100644 --- a/src/main/java/io/ipfs/multibase/binary/Base64.java +++ b/src/main/java/io/ipfs/multibase/binary/Base64.java @@ -20,34 +20,32 @@ import java.math.BigInteger; /** - * Provides Base64 encoding and decoding as defined by RFC 2045. + * Provides Base64 encoding and decoding as defined by RFC 2045. * - * From https://commons.apache.org/proper/commons-codec/ + *

From https://commons.apache.org/proper/commons-codec/ + * + *

This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 + * Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message + * Bodies by Freed and Borenstein. + * + *

The class can be parameterized in the following manner with various constructors: * - *

- * This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose - * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. - *

- *

- * The class can be parameterized in the following manner with various constructors: - *

*
    - *
  • URL-safe mode: Default off.
  • - *
  • Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of - * 4 in the encoded data. - *
  • Line separator: Default is CRLF ("\r\n")
  • + *
  • URL-safe mode: Default off. + *
  • Line length: Default 76. Line length that aren't multiples of 4 will still essentially end + * up being multiples of 4 in the encoded data. + *
  • Line separator: Default is CRLF ("\r\n") *
- *

- * The URL-safe parameter is only applied to encode operations. Decoding seamlessly handles both modes. - *

- *

- * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only - * encode/decode character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, - * UTF-8, etc). - *

- *

- * This class is thread-safe. - *

+ * + *

The URL-safe parameter is only applied to encode operations. Decoding seamlessly handles both + * modes. + * + *

Since this class operates directly on byte streams, and not character streams, it is + * hard-coded to only encode/decode character encodings which are compatible with the lower 127 + * ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). + * + *

This class is thread-safe. * * @see RFC 2045 * @since 1.0 @@ -55,733 +53,693 @@ */ public class Base64 extends BaseNCodec { - /** - * BASE32 characters are 6 bits in length. - * They are formed by taking a block of 3 octets to form a 24-bit string, - * which is converted into 4 BASE64 characters. - */ - private static final int BITS_PER_ENCODED_BYTE = 6; - private static final int BYTES_PER_UNENCODED_BLOCK = 3; - private static final int BYTES_PER_ENCODED_BLOCK = 4; - - /** - * Chunk separator per RFC 2045 section 2.1. - * - *

- * N.B. The next major release may break compatibility and make this field private. - *

- * - * @see RFC 2045 section 2.1 - */ - static final byte[] CHUNK_SEPARATOR = {'\r', '\n'}; - - /** - * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet" - * equivalents as specified in Table 1 of RFC 2045. - * - * Thanks to "commons" project in ws.apache.org for this code. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ - */ - private static final byte[] STANDARD_ENCODE_TABLE = { - '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', '+', '/' - }; - - /** - * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and / - * changed to - and _ to make the encoded Base64 results more URL-SAFE. - * This table is only used when the Base64's mode is set to URL-SAFE. - */ - private static final byte[] URL_SAFE_ENCODE_TABLE = { - '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', '-', '_' - }; - - /** - * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified - * in Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64 - * alphabet but fall within the bounds of the array are translated to -1. - * - * Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both - * URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to know ahead of time what to emit). - * - * Thanks to "commons" project in ws.apache.org for this code. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ - */ - private static final byte[] DECODE_TABLE = { - // 0 1 2 3 4 5 6 7 8 9 A B C D E F - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, // 20-2f + - / - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 30-3f 0-9 - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40-4f A-O - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, // 50-5f P-Z _ - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 60-6f a-o - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 // 70-7a p-z - }; - - /** - * Base64 uses 6-bit fields. - */ - /** Mask used to extract 6 bits, used when encoding */ - private static final int MASK_6BITS = 0x3f; - - // The static final fields above are used for the original static byte[] methods on Base64. - // The private member fields below are used with the new streaming approach, which requires - // some state be preserved between calls of encode() and decode(). - - /** - * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able - * to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member variable so we can switch - * between the two modes. - */ - private final byte[] encodeTable; - - // Only one decode table currently; keep for consistency with Base32 code - private final byte[] decodeTable = DECODE_TABLE; - - /** - * Line separator for encoding. Not used when decoding. Only used if lineLength > 0. - */ - private final byte[] lineSeparator; - - /** - * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. - * decodeSize = 3 + lineSeparator.length; - */ - private final int decodeSize; - - /** - * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. - * encodeSize = 4 + lineSeparator.length; - */ - private final int encodeSize; - - /** - * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. - *

- * When encoding the line length is 0 (no chunking), and the encoding table is STANDARD_ENCODE_TABLE. - *

- * - *

- * When decoding all variants are supported. - *

- */ - public Base64() { - this(0); - } - - /** - * Creates a Base64 codec used for decoding (all modes) and encoding in the given URL-safe mode. - *

- * When encoding the line length is 76, the line separator is CRLF, and the encoding table is STANDARD_ENCODE_TABLE. - *

- * - *

- * When decoding all variants are supported. - *

- * - * @param urlSafe - * if true, URL-safe encoding is used. In most cases this should be set to - * false. - * @since 1.4 - */ - public Base64(final boolean urlSafe) { - this(MIME_CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe); + /** + * BASE32 characters are 6 bits in length. They are formed by taking a block of 3 octets to form a + * 24-bit string, which is converted into 4 BASE64 characters. + */ + private static final int BITS_PER_ENCODED_BYTE = 6; + + private static final int BYTES_PER_UNENCODED_BLOCK = 3; + private static final int BYTES_PER_ENCODED_BLOCK = 4; + + /** + * Chunk separator per RFC 2045 section 2.1. + * + *

N.B. The next major release may break compatibility and make this field private. + * + * @see RFC 2045 section 2.1 + */ + static final byte[] CHUNK_SEPARATOR = {'\r', '\n'}; + + /** + * This array is a lookup table that translates 6-bit positive integer index values into their + * "Base64 Alphabet" equivalents as specified in Table 1 of RFC 2045. + * + *

Thanks to "commons" project in ws.apache.org for this code. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + */ + private static final byte[] STANDARD_ENCODE_TABLE = { + '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', '+', '/' + }; + + /** + * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and / changed to - and _ to make + * the encoded Base64 results more URL-SAFE. This table is only used when the Base64's mode is set + * to URL-SAFE. + */ + private static final byte[] URL_SAFE_ENCODE_TABLE = { + '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', '-', '_' + }; + + /** + * This array is a lookup table that translates Unicode characters drawn from the "Base64 + * Alphabet" (as specified in Table 1 of RFC 2045) into their 6-bit positive integer equivalents. + * Characters that are not in the Base64 alphabet but fall within the bounds of the array are + * translated to -1. + * + *

Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder + * seamlessly handles both URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to + * know ahead of time what to emit). + * + *

Thanks to "commons" project in ws.apache.org for this code. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + */ + private static final byte[] DECODE_TABLE = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, // 20-2f + - / + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 30-3f 0-9 + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40-4f A-O + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, // 50-5f P-Z _ + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 60-6f a-o + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 // 70-7a p-z + }; + + /** Base64 uses 6-bit fields. */ + /** Mask used to extract 6 bits, used when encoding */ + private static final int MASK_6BITS = 0x3f; + + // The static final fields above are used for the original static byte[] methods on Base64. + // The private member fields below are used with the new streaming approach, which requires + // some state be preserved between calls of encode() and decode(). + + /** + * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static + * because it is able to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a + * member variable so we can switch between the two modes. + */ + private final byte[] encodeTable; + + // Only one decode table currently; keep for consistency with Base32 code + private final byte[] decodeTable = DECODE_TABLE; + + /** Line separator for encoding. Not used when decoding. Only used if lineLength > 0. */ + private final byte[] lineSeparator; + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs + * resizing. decodeSize = 3 + lineSeparator.length; + */ + private final int decodeSize; + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs + * resizing. encodeSize = 4 + lineSeparator.length; + */ + private final int encodeSize; + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + * + *

When encoding the line length is 0 (no chunking), and the encoding table is + * STANDARD_ENCODE_TABLE. + * + *

When decoding all variants are supported. + */ + public Base64() { + this(0); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in the given URL-safe mode. + * + *

When encoding the line length is 76, the line separator is CRLF, and the encoding table is + * STANDARD_ENCODE_TABLE. + * + *

When decoding all variants are supported. + * + * @param urlSafe if true, URL-safe encoding is used. In most cases this should be + * set to false. + * @since 1.4 + */ + public Base64(final boolean urlSafe) { + this(MIME_CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + * + *

When encoding the line length is given in the constructor, the line separator is CRLF, and + * the encoding table is STANDARD_ENCODE_TABLE. + * + *

Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 + * in the encoded data. + * + *

When decoding all variants are supported. + * + * @param lineLength Each line of encoded data will be at most of the given length (rounded down + * to nearest multiple of 4). If lineLength <= 0, then the output will not be divided into + * lines (chunks). Ignored when decoding. + * @since 1.4 + */ + public Base64(final int lineLength) { + this(lineLength, CHUNK_SEPARATOR); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + * + *

When encoding the line length and line separator are given in the constructor, and the + * encoding table is STANDARD_ENCODE_TABLE. + * + *

Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 + * in the encoded data. + * + *

When decoding all variants are supported. + * + * @param lineLength Each line of encoded data will be at most of the given length (rounded down + * to nearest multiple of 4). If lineLength <= 0, then the output will not be divided into + * lines (chunks). Ignored when decoding. + * @param lineSeparator Each line of encoded data will end with this sequence of bytes. + * @throws IllegalArgumentException Thrown when the provided lineSeparator included some base64 + * characters. + * @since 1.4 + */ + public Base64(final int lineLength, final byte[] lineSeparator) { + this(lineLength, lineSeparator, false); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + * + *

When encoding the line length and line separator are given in the constructor, and the + * encoding table is STANDARD_ENCODE_TABLE. + * + *

Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 + * in the encoded data. + * + *

When decoding all variants are supported. + * + * @param lineLength Each line of encoded data will be at most of the given length (rounded down + * to nearest multiple of 4). If lineLength <= 0, then the output will not be divided into + * lines (chunks). Ignored when decoding. + * @param lineSeparator Each line of encoded data will end with this sequence of bytes. + * @param urlSafe Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is + * only applied to encode operations. Decoding seamlessly handles both modes. Note: no + * padding is added when using the URL-safe alphabet. + * @throws IllegalArgumentException The provided lineSeparator included some base64 characters. + * That's not going to work! + * @since 1.4 + */ + public Base64(final int lineLength, final byte[] lineSeparator, final boolean urlSafe) { + super( + BYTES_PER_UNENCODED_BLOCK, + BYTES_PER_ENCODED_BLOCK, + lineLength, + lineSeparator == null ? 0 : lineSeparator.length); + // TODO could be simplified if there is no requirement to reject invalid line sep when length + // <=0 + // @see test case Base64Test.testConstructors() + if (lineSeparator != null) { + if (containsAlphabetOrPad(lineSeparator)) { + final String sep = StringUtils.newStringUtf8(lineSeparator); + throw new IllegalArgumentException( + "lineSeparator must not contain base64 characters: [" + sep + "]"); + } + if (lineLength > 0) { // null line-sep forces no chunking rather than throwing IAE + this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length; + this.lineSeparator = new byte[lineSeparator.length]; + System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length); + } else { + this.encodeSize = BYTES_PER_ENCODED_BLOCK; + this.lineSeparator = null; + } + } else { + this.encodeSize = BYTES_PER_ENCODED_BLOCK; + this.lineSeparator = null; } - - /** - * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. - *

- * When encoding the line length is given in the constructor, the line separator is CRLF, and the encoding table is - * STANDARD_ENCODE_TABLE. - *

- *

- * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. - *

- *

- * When decoding all variants are supported. - *

- * - * @param lineLength - * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of - * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when - * decoding. - * @since 1.4 - */ - public Base64(final int lineLength) { - this(lineLength, CHUNK_SEPARATOR); - } - - /** - * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. - *

- * When encoding the line length and line separator are given in the constructor, and the encoding table is - * STANDARD_ENCODE_TABLE. - *

- *

- * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. - *

- *

- * When decoding all variants are supported. - *

- * - * @param lineLength - * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of - * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when - * decoding. - * @param lineSeparator - * Each line of encoded data will end with this sequence of bytes. - * @throws IllegalArgumentException - * Thrown when the provided lineSeparator included some base64 characters. - * @since 1.4 - */ - public Base64(final int lineLength, final byte[] lineSeparator) { - this(lineLength, lineSeparator, false); + this.decodeSize = this.encodeSize - 1; + this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE; + } + + /** + * Returns our current encode mode. True if we're URL-SAFE, false otherwise. + * + * @return true if we're in URL-SAFE mode, false otherwise. + * @since 1.4 + */ + public boolean isUrlSafe() { + return this.encodeTable == URL_SAFE_ENCODE_TABLE; + } + + /** + * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least + * twice: once with the data to encode, and once with inAvail set to "-1" to alert encoder that + * EOF has been reached, to flush last remaining bytes (if not multiple of 3). + * + *

Note: no padding is added when encoding using the URL-safe alphabet. + * + *

Thanks to "commons" project in ws.apache.org for the bitwise operations, and general + * approach. http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + * + * @param in byte[] array of binary data to base64 encode. + * @param inPos Position to start reading data from. + * @param inAvail Amount of bytes available from input for encoding. + * @param context the context to be used + */ + @Override + void encode(final byte[] in, int inPos, final int inAvail, final Context context) { + if (context.eof) { + return; } - - /** - * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. - *

- * When encoding the line length and line separator are given in the constructor, and the encoding table is - * STANDARD_ENCODE_TABLE. - *

- *

- * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. - *

- *

- * When decoding all variants are supported. - *

- * - * @param lineLength - * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of - * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when - * decoding. - * @param lineSeparator - * Each line of encoded data will end with this sequence of bytes. - * @param urlSafe - * Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode - * operations. Decoding seamlessly handles both modes. - * Note: no padding is added when using the URL-safe alphabet. - * @throws IllegalArgumentException - * The provided lineSeparator included some base64 characters. That's not going to work! - * @since 1.4 - */ - public Base64(final int lineLength, final byte[] lineSeparator, final boolean urlSafe) { - super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK, - lineLength, - lineSeparator == null ? 0 : lineSeparator.length); - // TODO could be simplified if there is no requirement to reject invalid line sep when length <=0 - // @see test case Base64Test.testConstructors() - if (lineSeparator != null) { - if (containsAlphabetOrPad(lineSeparator)) { - final String sep = StringUtils.newStringUtf8(lineSeparator); - throw new IllegalArgumentException("lineSeparator must not contain base64 characters: [" + sep + "]"); - } - if (lineLength > 0){ // null line-sep forces no chunking rather than throwing IAE - this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length; - this.lineSeparator = new byte[lineSeparator.length]; - System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length); - } else { - this.encodeSize = BYTES_PER_ENCODED_BLOCK; - this.lineSeparator = null; - } - } else { - this.encodeSize = BYTES_PER_ENCODED_BLOCK; - this.lineSeparator = null; + // inAvail < 0 is how we're informed of EOF in the underlying data we're + // encoding. + if (inAvail < 0) { + context.eof = true; + if (0 == context.modulus && lineLength == 0) { + return; // no leftovers to process and not using chunking + } + final byte[] buffer = ensureBufferSize(encodeSize, context); + final int savedPos = context.pos; + switch (context.modulus) { // 0-2 + case 0: // nothing to do here + break; + case 1: // 8 bits = 6 + 2 + // top 6 bits: + buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 2) & MASK_6BITS]; + // remaining 2: + buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 4) & MASK_6BITS]; + // URL-SAFE skips the padding to further reduce size. + if (encodeTable == STANDARD_ENCODE_TABLE) { + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + } + break; + + case 2: // 16 bits = 6 + 6 + 4 + buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 10) & MASK_6BITS]; + buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 4) & MASK_6BITS]; + buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 2) & MASK_6BITS]; + // URL-SAFE skips the padding to further reduce size. + if (encodeTable == STANDARD_ENCODE_TABLE) { + buffer[context.pos++] = pad; + } + break; + default: + throw new IllegalStateException("Impossible modulus " + context.modulus); + } + context.currentLinePos += context.pos - savedPos; // keep track of current line position + // if currentPos == 0 we are at the start of a line, so don't add CRLF + if (lineLength > 0 && context.currentLinePos > 0) { + System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); + context.pos += lineSeparator.length; + } + } else { + for (int i = 0; i < inAvail; i++) { + final byte[] buffer = ensureBufferSize(encodeSize, context); + context.modulus = (context.modulus + 1) % BYTES_PER_UNENCODED_BLOCK; + int b = in[inPos++]; + if (b < 0) { + b += 256; } - this.decodeSize = this.encodeSize - 1; - this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE; - } - - /** - * Returns our current encode mode. True if we're URL-SAFE, false otherwise. - * - * @return true if we're in URL-SAFE mode, false otherwise. - * @since 1.4 - */ - public boolean isUrlSafe() { - return this.encodeTable == URL_SAFE_ENCODE_TABLE; - } - - /** - *

- * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with - * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, to flush last - * remaining bytes (if not multiple of 3). - *

- *

Note: no padding is added when encoding using the URL-safe alphabet.

- *

- * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ - *

- * - * @param in - * byte[] array of binary data to base64 encode. - * @param inPos - * Position to start reading data from. - * @param inAvail - * Amount of bytes available from input for encoding. - * @param context - * the context to be used - */ - @Override - void encode(final byte[] in, int inPos, final int inAvail, final Context context) { - if (context.eof) { - return; - } - // inAvail < 0 is how we're informed of EOF in the underlying data we're - // encoding. - if (inAvail < 0) { - context.eof = true; - if (0 == context.modulus && lineLength == 0) { - return; // no leftovers to process and not using chunking - } - final byte[] buffer = ensureBufferSize(encodeSize, context); - final int savedPos = context.pos; - switch (context.modulus) { // 0-2 - case 0 : // nothing to do here - break; - case 1 : // 8 bits = 6 + 2 - // top 6 bits: - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 2) & MASK_6BITS]; - // remaining 2: - buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 4) & MASK_6BITS]; - // URL-SAFE skips the padding to further reduce size. - if (encodeTable == STANDARD_ENCODE_TABLE) { - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - } - break; - - case 2 : // 16 bits = 6 + 6 + 4 - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 10) & MASK_6BITS]; - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 4) & MASK_6BITS]; - buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 2) & MASK_6BITS]; - // URL-SAFE skips the padding to further reduce size. - if (encodeTable == STANDARD_ENCODE_TABLE) { - buffer[context.pos++] = pad; - } - break; - default: - throw new IllegalStateException("Impossible modulus "+context.modulus); - } - context.currentLinePos += context.pos - savedPos; // keep track of current line position - // if currentPos == 0 we are at the start of a line, so don't add CRLF - if (lineLength > 0 && context.currentLinePos > 0) { - System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); - context.pos += lineSeparator.length; - } - } else { - for (int i = 0; i < inAvail; i++) { - final byte[] buffer = ensureBufferSize(encodeSize, context); - context.modulus = (context.modulus+1) % BYTES_PER_UNENCODED_BLOCK; - int b = in[inPos++]; - if (b < 0) { - b += 256; - } - context.ibitWorkArea = (context.ibitWorkArea << 8) + b; // BITS_PER_BYTE - if (0 == context.modulus) { // 3 bytes = 24 bits = 4 * 6 bits to extract - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 18) & MASK_6BITS]; - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 12) & MASK_6BITS]; - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 6) & MASK_6BITS]; - buffer[context.pos++] = encodeTable[context.ibitWorkArea & MASK_6BITS]; - context.currentLinePos += BYTES_PER_ENCODED_BLOCK; - if (lineLength > 0 && lineLength <= context.currentLinePos) { - System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); - context.pos += lineSeparator.length; - context.currentLinePos = 0; - } - } - } + context.ibitWorkArea = (context.ibitWorkArea << 8) + b; // BITS_PER_BYTE + if (0 == context.modulus) { // 3 bytes = 24 bits = 4 * 6 bits to extract + buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 18) & MASK_6BITS]; + buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 12) & MASK_6BITS]; + buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 6) & MASK_6BITS]; + buffer[context.pos++] = encodeTable[context.ibitWorkArea & MASK_6BITS]; + context.currentLinePos += BYTES_PER_ENCODED_BLOCK; + if (lineLength > 0 && lineLength <= context.currentLinePos) { + System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); + context.pos += lineSeparator.length; + context.currentLinePos = 0; + } } + } } - - /** - *

- * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once - * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1" - * call is not necessary when decoding, but it doesn't hurt, either. - *

- *

- * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are - * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in, - * garbage-out philosophy: it will not check the provided data for validity. - *

- *

- * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ - *

- * - * @param in - * byte[] array of ascii data to base64 decode. - * @param inPos - * Position to start reading data from. - * @param inAvail - * Amount of bytes available from input for encoding. - * @param context - * the context to be used - */ - @Override - void decode(final byte[] in, int inPos, final int inAvail, final Context context) { - if (context.eof) { - return; - } - if (inAvail < 0) { - context.eof = true; - } - for (int i = 0; i < inAvail; i++) { - final byte[] buffer = ensureBufferSize(decodeSize, context); - final byte b = in[inPos++]; - if (b == pad) { - // We're done. - context.eof = true; - break; - } - if (b >= 0 && b < DECODE_TABLE.length) { - final int result = DECODE_TABLE[b]; - if (result >= 0) { - context.modulus = (context.modulus+1) % BYTES_PER_ENCODED_BLOCK; - context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE) + result; - if (context.modulus == 0) { - buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 16) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS); - buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS); - } - } - } - } - - // Two forms of EOF as far as base64 decoder is concerned: actual - // EOF (-1) and first time '=' character is encountered in stream. - // This approach makes the '=' padding characters completely optional. - if (context.eof && context.modulus != 0) { - final byte[] buffer = ensureBufferSize(decodeSize, context); - - // We have some spare bits remaining - // Output all whole multiples of 8 bits and ignore the rest - switch (context.modulus) { -// case 0 : // impossible, as excluded above - case 1 : // 6 bits - ignore entirely - // TODO not currently tested; perhaps it is impossible? - break; - case 2 : // 12 bits = 8 + 4 - context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits - buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); - break; - case 3 : // 18 bits = 8 + 8 + 2 - context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits - buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); - break; - default: - throw new IllegalStateException("Impossible modulus "+context.modulus); - } - } - } - - /** - * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the - * method treats whitespace as valid. - * - * @param arrayOctet - * byte array to test - * @return true if all bytes are valid characters in the Base64 alphabet or if the byte array is empty; - * false, otherwise - * @deprecated 1.5 Use {@link #isBase64(byte[])}, will be removed in 2.0. - */ - @Deprecated - public static boolean isArrayByteBase64(final byte[] arrayOctet) { - return isBase64(arrayOctet); + } + + /** + * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at + * least twice: once with the data to decode, and once with inAvail set to "-1" to alert decoder + * that EOF has been reached. The "-1" call is not necessary when decoding, but it doesn't hurt, + * either. + * + *

Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, + * since CR and LF are silently ignored, but has implications for other bytes, too. This method + * subscribes to the garbage-in, garbage-out philosophy: it will not check the provided data for + * validity. + * + *

Thanks to "commons" project in ws.apache.org for the bitwise operations, and general + * approach. http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + * + * @param in byte[] array of ascii data to base64 decode. + * @param inPos Position to start reading data from. + * @param inAvail Amount of bytes available from input for encoding. + * @param context the context to be used + */ + @Override + void decode(final byte[] in, int inPos, final int inAvail, final Context context) { + if (context.eof) { + return; } - - /** - * Returns whether or not the octet is in the base 64 alphabet. - * - * @param octet - * The value to test - * @return true if the value is defined in the the base 64 alphabet, false otherwise. - * @since 1.4 - */ - public static boolean isBase64(final byte octet) { - return octet == PAD_DEFAULT || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1); - } - - /** - * Tests a given String to see if it contains only valid characters within the Base64 alphabet. Currently the - * method treats whitespace as valid. - * - * @param base64 - * String to test - * @return true if all characters in the String are valid characters in the Base64 alphabet or if - * the String is empty; false, otherwise - * @since 1.5 - */ - public static boolean isBase64(final String base64) { - return isBase64(StringUtils.getBytesUtf8(base64)); + if (inAvail < 0) { + context.eof = true; } - - /** - * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the - * method treats whitespace as valid. - * - * @param arrayOctet - * byte array to test - * @return true if all bytes are valid characters in the Base64 alphabet or if the byte array is empty; - * false, otherwise - * @since 1.5 - */ - public static boolean isBase64(final byte[] arrayOctet) { - for (int i = 0; i < arrayOctet.length; i++) { - if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) { - return false; - } + for (int i = 0; i < inAvail; i++) { + final byte[] buffer = ensureBufferSize(decodeSize, context); + final byte b = in[inPos++]; + if (b == pad) { + // We're done. + context.eof = true; + break; + } + if (b >= 0 && b < DECODE_TABLE.length) { + final int result = DECODE_TABLE[b]; + if (result >= 0) { + context.modulus = (context.modulus + 1) % BYTES_PER_ENCODED_BLOCK; + context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE) + result; + if (context.modulus == 0) { + buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 16) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS); + buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS); + } } - return true; - } - - /** - * Encodes binary data using the base64 algorithm but does not chunk the output. - * - * @param binaryData - * binary data to encode - * @return byte[] containing Base64 characters in their UTF-8 representation. - */ - public static byte[] encodeBase64(final byte[] binaryData) { - return encodeBase64(binaryData, false); + } } - /** - * Encodes binary data using the base64 algorithm but does not chunk the output. - * - * NOTE: We changed the behaviour of this method from multi-line chunking (commons-codec-1.4) to - * single-line non-chunking (commons-codec-1.5). - * - * @param binaryData - * binary data to encode - * @return String containing Base64 characters. - * @since 1.4 (NOTE: 1.4 chunked the output, whereas 1.5 does not). - */ - public static String encodeBase64String(final byte[] binaryData) { - return StringUtils.newStringUsAscii(encodeBase64(binaryData, false)); + // Two forms of EOF as far as base64 decoder is concerned: actual + // EOF (-1) and first time '=' character is encountered in stream. + // This approach makes the '=' padding characters completely optional. + if (context.eof && context.modulus != 0) { + final byte[] buffer = ensureBufferSize(decodeSize, context); + + // We have some spare bits remaining + // Output all whole multiples of 8 bits and ignore the rest + switch (context.modulus) { + // case 0 : // impossible, as excluded above + case 1: // 6 bits - ignore entirely + // TODO not currently tested; perhaps it is impossible? + break; + case 2: // 12 bits = 8 + 4 + context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits + buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); + break; + case 3: // 18 bits = 8 + 8 + 2 + context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits + buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS); + buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); + break; + default: + throw new IllegalStateException("Impossible modulus " + context.modulus); + } } - - /** - * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The - * url-safe variation emits - and _ instead of + and / characters. - * Note: no padding is added. - * @param binaryData - * binary data to encode - * @return byte[] containing Base64 characters in their UTF-8 representation. - * @since 1.4 - */ - public static byte[] encodeBase64URLSafe(final byte[] binaryData) { - return encodeBase64(binaryData, false, true); + } + + /** + * Tests a given byte array to see if it contains only valid characters within the Base64 + * alphabet. Currently the method treats whitespace as valid. + * + * @param arrayOctet byte array to test + * @return true if all bytes are valid characters in the Base64 alphabet or if the + * byte array is empty; false, otherwise + * @deprecated 1.5 Use {@link #isBase64(byte[])}, will be removed in 2.0. + */ + @Deprecated + public static boolean isArrayByteBase64(final byte[] arrayOctet) { + return isBase64(arrayOctet); + } + + /** + * Returns whether or not the octet is in the base 64 alphabet. + * + * @param octet The value to test + * @return true if the value is defined in the the base 64 alphabet, false + * otherwise. + * @since 1.4 + */ + public static boolean isBase64(final byte octet) { + return octet == PAD_DEFAULT + || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1); + } + + /** + * Tests a given String to see if it contains only valid characters within the Base64 alphabet. + * Currently the method treats whitespace as valid. + * + * @param base64 String to test + * @return true if all characters in the String are valid characters in the Base64 + * alphabet or if the String is empty; false, otherwise + * @since 1.5 + */ + public static boolean isBase64(final String base64) { + return isBase64(StringUtils.getBytesUtf8(base64)); + } + + /** + * Tests a given byte array to see if it contains only valid characters within the Base64 + * alphabet. Currently the method treats whitespace as valid. + * + * @param arrayOctet byte array to test + * @return true if all bytes are valid characters in the Base64 alphabet or if the + * byte array is empty; false, otherwise + * @since 1.5 + */ + public static boolean isBase64(final byte[] arrayOctet) { + for (int i = 0; i < arrayOctet.length; i++) { + if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) { + return false; + } } - - /** - * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The - * url-safe variation emits - and _ instead of + and / characters. - * Note: no padding is added. - * @param binaryData - * binary data to encode - * @return String containing Base64 characters - * @since 1.4 - */ - public static String encodeBase64URLSafeString(final byte[] binaryData) { - return StringUtils.newStringUsAscii(encodeBase64(binaryData, false, true)); - } - - /** - * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks - * - * @param binaryData - * binary data to encode - * @return Base64 characters chunked in 76 character blocks - */ - public static byte[] encodeBase64Chunked(final byte[] binaryData) { - return encodeBase64(binaryData, true); + return true; + } + + /** + * Encodes binary data using the base64 algorithm but does not chunk the output. + * + * @param binaryData binary data to encode + * @return byte[] containing Base64 characters in their UTF-8 representation. + */ + public static byte[] encodeBase64(final byte[] binaryData) { + return encodeBase64(binaryData, false); + } + + /** + * Encodes binary data using the base64 algorithm but does not chunk the output. + * + *

NOTE: We changed the behaviour of this method from multi-line chunking (commons-codec-1.4) + * to single-line non-chunking (commons-codec-1.5). + * + * @param binaryData binary data to encode + * @return String containing Base64 characters. + * @since 1.4 (NOTE: 1.4 chunked the output, whereas 1.5 does not). + */ + public static String encodeBase64String(final byte[] binaryData) { + return StringUtils.newStringUsAscii(encodeBase64(binaryData, false)); + } + + /** + * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the + * output. The url-safe variation emits - and _ instead of + and / characters. Note: no padding + * is added. + * + * @param binaryData binary data to encode + * @return byte[] containing Base64 characters in their UTF-8 representation. + * @since 1.4 + */ + public static byte[] encodeBase64URLSafe(final byte[] binaryData) { + return encodeBase64(binaryData, false, true); + } + + /** + * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the + * output. The url-safe variation emits - and _ instead of + and / characters. Note: no padding + * is added. + * + * @param binaryData binary data to encode + * @return String containing Base64 characters + * @since 1.4 + */ + public static String encodeBase64URLSafeString(final byte[] binaryData) { + return StringUtils.newStringUsAscii(encodeBase64(binaryData, false, true)); + } + + /** + * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character + * blocks + * + * @param binaryData binary data to encode + * @return Base64 characters chunked in 76 character blocks + */ + public static byte[] encodeBase64Chunked(final byte[] binaryData) { + return encodeBase64(binaryData, true); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 + * character blocks. + * + * @param binaryData Array containing binary data to encode. + * @param isChunked if true this encoder will chunk the base64 output into 76 + * character blocks + * @return Base64-encoded data. + * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than + * {@link Integer#MAX_VALUE} + */ + public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked) { + return encodeBase64(binaryData, isChunked, false); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 + * character blocks. + * + * @param binaryData Array containing binary data to encode. + * @param isChunked if true this encoder will chunk the base64 output into 76 + * character blocks + * @param urlSafe if true this encoder will emit - and _ instead of the usual + and / + * characters. Note: no padding is added when encoding using the URL-safe alphabet. + * @return Base64-encoded data. + * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than + * {@link Integer#MAX_VALUE} + * @since 1.4 + */ + public static byte[] encodeBase64( + final byte[] binaryData, final boolean isChunked, final boolean urlSafe) { + return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 + * character blocks. + * + * @param binaryData Array containing binary data to encode. + * @param isChunked if true this encoder will chunk the base64 output into 76 + * character blocks + * @param urlSafe if true this encoder will emit - and _ instead of the usual + and / + * characters. Note: no padding is added when encoding using the URL-safe alphabet. + * @param maxResultSize The maximum result size to accept. + * @return Base64-encoded data. + * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than + * maxResultSize + * @since 1.4 + */ + public static byte[] encodeBase64( + final byte[] binaryData, + final boolean isChunked, + final boolean urlSafe, + final int maxResultSize) { + if (binaryData == null || binaryData.length == 0) { + return binaryData; } - /** - * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. - * - * @param binaryData - * Array containing binary data to encode. - * @param isChunked - * if true this encoder will chunk the base64 output into 76 character blocks - * @return Base64-encoded data. - * @throws IllegalArgumentException - * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} - */ - public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked) { - return encodeBase64(binaryData, isChunked, false); + // Create this so can use the super-class method + // Also ensures that the same roundings are performed by the ctor and the code + final Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe); + final long len = b64.getEncodedLength(binaryData); + if (len > maxResultSize) { + throw new IllegalArgumentException( + "Input array too big, the output array would be bigger (" + + len + + ") than the specified maximum size of " + + maxResultSize); } - /** - * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. - * - * @param binaryData - * Array containing binary data to encode. - * @param isChunked - * if true this encoder will chunk the base64 output into 76 character blocks - * @param urlSafe - * if true this encoder will emit - and _ instead of the usual + and / characters. - * Note: no padding is added when encoding using the URL-safe alphabet. - * @return Base64-encoded data. - * @throws IllegalArgumentException - * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} - * @since 1.4 - */ - public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, final boolean urlSafe) { - return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE); + return b64.encode(binaryData); + } + + /** + * Decodes a Base64 String into octets. + * + *

Note: this method seamlessly handles data encoded in URL-safe or normal mode. + * + * @param base64String String containing Base64 data + * @return Array containing decoded data. + * @since 1.4 + */ + public static byte[] decodeBase64(final String base64String) { + return new Base64().decode(base64String); + } + + /** + * Decodes Base64 data into octets. + * + *

Note: this method seamlessly handles data encoded in URL-safe or normal mode. + * + * @param base64Data Byte array containing Base64 data + * @return Array containing decoded data. + */ + public static byte[] decodeBase64(final byte[] base64Data) { + return new Base64().decode(base64Data); + } + + // Implementation of the Encoder Interface + + // Implementation of integer encoding used for crypto + /** + * Decodes a byte64-encoded integer according to crypto standards such as W3C's XML-Signature. + * + * @param pArray a byte array containing base64 character data + * @return A BigInteger + * @since 1.4 + */ + public static BigInteger decodeInteger(final byte[] pArray) { + return new BigInteger(1, decodeBase64(pArray)); + } + + /** + * Encodes to a byte64-encoded integer according to crypto standards such as W3C's XML-Signature. + * + * @param bigInt a BigInteger + * @return A byte array containing base64 character data + * @throws NullPointerException if null is passed in + * @since 1.4 + */ + public static byte[] encodeInteger(final BigInteger bigInt) { + if (bigInt == null) { + throw new NullPointerException("encodeInteger called with null parameter"); } - - /** - * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. - * - * @param binaryData - * Array containing binary data to encode. - * @param isChunked - * if true this encoder will chunk the base64 output into 76 character blocks - * @param urlSafe - * if true this encoder will emit - and _ instead of the usual + and / characters. - * Note: no padding is added when encoding using the URL-safe alphabet. - * @param maxResultSize - * The maximum result size to accept. - * @return Base64-encoded data. - * @throws IllegalArgumentException - * Thrown when the input array needs an output array bigger than maxResultSize - * @since 1.4 - */ - public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, - final boolean urlSafe, final int maxResultSize) { - if (binaryData == null || binaryData.length == 0) { - return binaryData; - } - - // Create this so can use the super-class method - // Also ensures that the same roundings are performed by the ctor and the code - final Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe); - final long len = b64.getEncodedLength(binaryData); - if (len > maxResultSize) { - throw new IllegalArgumentException("Input array too big, the output array would be bigger (" + - len + - ") than the specified maximum size of " + - maxResultSize); - } - - return b64.encode(binaryData); + return encodeBase64(toIntegerBytes(bigInt), false); + } + + /** + * Returns a byte-array representation of a BigInteger without sign bit. + * + * @param bigInt BigInteger to be converted + * @return a byte array representation of the BigInteger parameter + */ + static byte[] toIntegerBytes(final BigInteger bigInt) { + int bitlen = bigInt.bitLength(); + // round bitlen + bitlen = ((bitlen + 7) >> 3) << 3; + final byte[] bigBytes = bigInt.toByteArray(); + + if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) { + return bigBytes; } - - /** - * Decodes a Base64 String into octets. - *

- * Note: this method seamlessly handles data encoded in URL-safe or normal mode. - *

- * - * @param base64String - * String containing Base64 data - * @return Array containing decoded data. - * @since 1.4 - */ - public static byte[] decodeBase64(final String base64String) { - return new Base64().decode(base64String); + // set up params for copying everything but sign bit + int startSrc = 0; + int len = bigBytes.length; + + // if bigInt is exactly byte-aligned, just skip signbit in copy + if ((bigInt.bitLength() % 8) == 0) { + startSrc = 1; + len--; } - - /** - * Decodes Base64 data into octets. - *

- * Note: this method seamlessly handles data encoded in URL-safe or normal mode. - *

- * - * @param base64Data - * Byte array containing Base64 data - * @return Array containing decoded data. - */ - public static byte[] decodeBase64(final byte[] base64Data) { - return new Base64().decode(base64Data); - } - - // Implementation of the Encoder Interface - - // Implementation of integer encoding used for crypto - /** - * Decodes a byte64-encoded integer according to crypto standards such as W3C's XML-Signature. - * - * @param pArray - * a byte array containing base64 character data - * @return A BigInteger - * @since 1.4 - */ - public static BigInteger decodeInteger(final byte[] pArray) { - return new BigInteger(1, decodeBase64(pArray)); - } - - /** - * Encodes to a byte64-encoded integer according to crypto standards such as W3C's XML-Signature. - * - * @param bigInt - * a BigInteger - * @return A byte array containing base64 character data - * @throws NullPointerException - * if null is passed in - * @since 1.4 - */ - public static byte[] encodeInteger(final BigInteger bigInt) { - if (bigInt == null) { - throw new NullPointerException("encodeInteger called with null parameter"); - } - return encodeBase64(toIntegerBytes(bigInt), false); - } - - /** - * Returns a byte-array representation of a BigInteger without sign bit. - * - * @param bigInt - * BigInteger to be converted - * @return a byte array representation of the BigInteger parameter - */ - static byte[] toIntegerBytes(final BigInteger bigInt) { - int bitlen = bigInt.bitLength(); - // round bitlen - bitlen = ((bitlen + 7) >> 3) << 3; - final byte[] bigBytes = bigInt.toByteArray(); - - if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) { - return bigBytes; - } - // set up params for copying everything but sign bit - int startSrc = 0; - int len = bigBytes.length; - - // if bigInt is exactly byte-aligned, just skip signbit in copy - if ((bigInt.bitLength() % 8) == 0) { - startSrc = 1; - len--; - } - final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec - final byte[] resizedBytes = new byte[bitlen / 8]; - System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len); - return resizedBytes; - } - - /** - * Returns whether or not the octet is in the Base64 alphabet. - * - * @param octet - * The value to test - * @return true if the value is defined in the the Base64 alphabet false otherwise. - */ - @Override - protected boolean isInAlphabet(final byte octet) { - return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1; - } - + final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec + final byte[] resizedBytes = new byte[bitlen / 8]; + System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len); + return resizedBytes; + } + + /** + * Returns whether or not the octet is in the Base64 alphabet. + * + * @param octet The value to test + * @return true if the value is defined in the the Base64 alphabet false + * otherwise. + */ + @Override + protected boolean isInAlphabet(final byte octet) { + return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1; + } } diff --git a/src/main/java/io/ipfs/multibase/binary/BaseNCodec.java b/src/main/java/io/ipfs/multibase/binary/BaseNCodec.java index 8af43f3..704fd25 100644 --- a/src/main/java/io/ipfs/multibase/binary/BaseNCodec.java +++ b/src/main/java/io/ipfs/multibase/binary/BaseNCodec.java @@ -25,509 +25,488 @@ /** * Abstract superclass for Base-N encoders and decoders. * - * From https://commons.apache.org/proper/commons-codec/ + *

From https://commons.apache.org/proper/commons-codec/ * - *

- * This class is thread-safe. - *

+ *

This class is thread-safe. * * @version $Id$ */ public abstract class BaseNCodec implements BinaryEncoder, BinaryDecoder { - /** - * Holds thread context so classes can be thread-safe. - * - * This class is not itself thread-safe; each thread must allocate its own copy. - * - * @since 1.7 - */ - static class Context { - - /** - * Place holder for the bytes we're dealing with for our based logic. - * Bitwise operations store and extract the encoding or decoding from this variable. - */ - int ibitWorkArea; - - /** - * Place holder for the bytes we're dealing with for our based logic. - * Bitwise operations store and extract the encoding or decoding from this variable. - */ - long lbitWorkArea; - - /** - * Buffer for streaming. - */ - byte[] buffer; - - /** - * Position where next character should be written in the buffer. - */ - int pos; - - /** - * Position where next character should be read from the buffer. - */ - int readPos; - - /** - * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless, - * and must be thrown away. - */ - boolean eof; - - /** - * Variable tracks how many characters have been written to the current line. Only used when encoding. We use - * it to make sure each encoded line never goes beyond lineLength (if lineLength > 0). - */ - int currentLinePos; - - /** - * Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding. This - * variable helps track that. - */ - int modulus; - - Context() {} - } - - /** - * EOF - * - * @since 1.7 - */ - static final int EOF = -1; - - /** - * MIME chunk size per RFC 2045 section 6.8. - * - *

- * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any - * equal signs. - *

- * - * @see RFC 2045 section 6.8 - */ - public static final int MIME_CHUNK_SIZE = 76; - - /** - * PEM chunk size per RFC 1421 section 4.3.2.4. - * - *

- * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any - * equal signs. - *

- * - * @see RFC 1421 section 4.3.2.4 - */ - public static final int PEM_CHUNK_SIZE = 64; - - private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2; - - /** - * Defines the default buffer size - currently {@value} - * - must be large enough for at least one encoded block+separator - */ - private static final int DEFAULT_BUFFER_SIZE = 8192; - - /** Mask used to extract 8 bits, used in decoding bytes */ - protected static final int MASK_8BITS = 0xff; + /** + * Holds thread context so classes can be thread-safe. + * + *

This class is not itself thread-safe; each thread must allocate its own copy. + * + * @since 1.7 + */ + static class Context { /** - * Byte used to pad output. + * Place holder for the bytes we're dealing with for our based logic. Bitwise operations store + * and extract the encoding or decoding from this variable. */ - protected static final byte PAD_DEFAULT = '='; // Allow static access to default + int ibitWorkArea; /** - * @deprecated Use {@link #pad}. Will be removed in 2.0. + * Place holder for the bytes we're dealing with for our based logic. Bitwise operations store + * and extract the encoding or decoding from this variable. */ - @Deprecated - protected final byte PAD = PAD_DEFAULT; // instance variable just in case it needs to vary later - - protected final byte pad; // instance variable just in case it needs to vary later + long lbitWorkArea; - /** Number of bytes in each full block of unencoded data, e.g. 4 for Base64 and 5 for Base32 */ - private final int unencodedBlockSize; + /** Buffer for streaming. */ + byte[] buffer; - /** Number of bytes in each full block of encoded data, e.g. 3 for Base64 and 8 for Base32 */ - private final int encodedBlockSize; + /** Position where next character should be written in the buffer. */ + int pos; - /** - * Chunksize for encoding. Not used when decoding. - * A value of zero or less implies no chunking of the encoded data. - * Rounded down to nearest multiple of encodedBlockSize. - */ - protected final int lineLength; + /** Position where next character should be read from the buffer. */ + int readPos; /** - * Size of chunk separator. Not used unless {@link #lineLength} > 0. + * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object + * becomes useless, and must be thrown away. */ - private final int chunkSeparatorLength; + boolean eof; /** - * Note lineLength is rounded down to the nearest multiple of {@link #encodedBlockSize} - * If chunkSeparatorLength is zero, then chunking is disabled. - * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) - * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) - * @param lineLength if > 0, use chunking with a length lineLength - * @param chunkSeparatorLength the chunk separator length, if relevant + * Variable tracks how many characters have been written to the current line. Only used when + * encoding. We use it to make sure each encoded line never goes beyond lineLength (if + * lineLength > 0). */ - protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, - final int lineLength, final int chunkSeparatorLength) { - this(unencodedBlockSize, encodedBlockSize, lineLength, chunkSeparatorLength, PAD_DEFAULT); - } + int currentLinePos; /** - * Note lineLength is rounded down to the nearest multiple of {@link #encodedBlockSize} - * If chunkSeparatorLength is zero, then chunking is disabled. - * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) - * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) - * @param lineLength if > 0, use chunking with a length lineLength - * @param chunkSeparatorLength the chunk separator length, if relevant - * @param pad byte used as padding byte. + * Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when + * decoding. This variable helps track that. */ - protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, - final int lineLength, final int chunkSeparatorLength, final byte pad) { - this.unencodedBlockSize = unencodedBlockSize; - this.encodedBlockSize = encodedBlockSize; - final boolean useChunking = lineLength > 0 && chunkSeparatorLength > 0; - this.lineLength = useChunking ? (lineLength / encodedBlockSize) * encodedBlockSize : 0; - this.chunkSeparatorLength = chunkSeparatorLength; - - this.pad = pad; + int modulus; + + Context() {} + } + + /** + * EOF + * + * @since 1.7 + */ + static final int EOF = -1; + + /** + * MIME chunk size per RFC 2045 section 6.8. + * + *

The {@value} character limit does not count the trailing CRLF, but counts all other + * characters, including any equal signs. + * + * @see RFC 2045 section 6.8 + */ + public static final int MIME_CHUNK_SIZE = 76; + + /** + * PEM chunk size per RFC 1421 section 4.3.2.4. + * + *

The {@value} character limit does not count the trailing CRLF, but counts all other + * characters, including any equal signs. + * + * @see RFC 1421 section 4.3.2.4 + */ + public static final int PEM_CHUNK_SIZE = 64; + + private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2; + + /** + * Defines the default buffer size - currently {@value} - must be large enough for at least one + * encoded block+separator + */ + private static final int DEFAULT_BUFFER_SIZE = 8192; + + /** Mask used to extract 8 bits, used in decoding bytes */ + protected static final int MASK_8BITS = 0xff; + + /** Byte used to pad output. */ + protected static final byte PAD_DEFAULT = '='; // Allow static access to default + + /** + * @deprecated Use {@link #pad}. Will be removed in 2.0. + */ + @Deprecated + protected final byte PAD = PAD_DEFAULT; // instance variable just in case it needs to vary later + + protected final byte pad; // instance variable just in case it needs to vary later + + /** Number of bytes in each full block of unencoded data, e.g. 4 for Base64 and 5 for Base32 */ + private final int unencodedBlockSize; + + /** Number of bytes in each full block of encoded data, e.g. 3 for Base64 and 8 for Base32 */ + private final int encodedBlockSize; + + /** + * Chunksize for encoding. Not used when decoding. A value of zero or less implies no chunking of + * the encoded data. Rounded down to nearest multiple of encodedBlockSize. + */ + protected final int lineLength; + + /** Size of chunk separator. Not used unless {@link #lineLength} > 0. */ + private final int chunkSeparatorLength; + + /** + * Note lineLength is rounded down to the nearest multiple of {@link + * #encodedBlockSize} If chunkSeparatorLength is zero, then chunking is disabled. + * + * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) + * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) + * @param lineLength if > 0, use chunking with a length lineLength + * @param chunkSeparatorLength the chunk separator length, if relevant + */ + protected BaseNCodec( + final int unencodedBlockSize, + final int encodedBlockSize, + final int lineLength, + final int chunkSeparatorLength) { + this(unencodedBlockSize, encodedBlockSize, lineLength, chunkSeparatorLength, PAD_DEFAULT); + } + + /** + * Note lineLength is rounded down to the nearest multiple of {@link + * #encodedBlockSize} If chunkSeparatorLength is zero, then chunking is disabled. + * + * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) + * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) + * @param lineLength if > 0, use chunking with a length lineLength + * @param chunkSeparatorLength the chunk separator length, if relevant + * @param pad byte used as padding byte. + */ + protected BaseNCodec( + final int unencodedBlockSize, + final int encodedBlockSize, + final int lineLength, + final int chunkSeparatorLength, + final byte pad) { + this.unencodedBlockSize = unencodedBlockSize; + this.encodedBlockSize = encodedBlockSize; + final boolean useChunking = lineLength > 0 && chunkSeparatorLength > 0; + this.lineLength = useChunking ? (lineLength / encodedBlockSize) * encodedBlockSize : 0; + this.chunkSeparatorLength = chunkSeparatorLength; + + this.pad = pad; + } + + /** + * Returns true if this object has buffered data for reading. + * + * @param context the context to be used + * @return true if there is data still available for reading. + */ + boolean hasData(final Context context) { // package protected for access from I/O streams + return context.buffer != null; + } + + /** + * Returns the amount of buffered data available for reading. + * + * @param context the context to be used + * @return The amount of buffered data available for reading. + */ + int available(final Context context) { // package protected for access from I/O streams + return context.buffer != null ? context.pos - context.readPos : 0; + } + + /** + * Get the default buffer size. Can be overridden. + * + * @return {@link #DEFAULT_BUFFER_SIZE} + */ + protected int getDefaultBufferSize() { + return DEFAULT_BUFFER_SIZE; + } + + /** + * Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}. + * + * @param context the context to be used + */ + private byte[] resizeBuffer(final Context context) { + if (context.buffer == null) { + context.buffer = new byte[getDefaultBufferSize()]; + context.pos = 0; + context.readPos = 0; + } else { + final byte[] b = new byte[context.buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR]; + System.arraycopy(context.buffer, 0, b, 0, context.buffer.length); + context.buffer = b; } - - /** - * Returns true if this object has buffered data for reading. - * - * @param context the context to be used - * @return true if there is data still available for reading. - */ - boolean hasData(final Context context) { // package protected for access from I/O streams - return context.buffer != null; + return context.buffer; + } + + /** + * Ensure that the buffer has room for size bytes + * + * @param size minimum spare space required + * @param context the context to be used + * @return the buffer + */ + protected byte[] ensureBufferSize(final int size, final Context context) { + if ((context.buffer == null) || (context.buffer.length < context.pos + size)) { + return resizeBuffer(context); } - - /** - * Returns the amount of buffered data available for reading. - * - * @param context the context to be used - * @return The amount of buffered data available for reading. - */ - int available(final Context context) { // package protected for access from I/O streams - return context.buffer != null ? context.pos - context.readPos : 0; - } - - /** - * Get the default buffer size. Can be overridden. - * - * @return {@link #DEFAULT_BUFFER_SIZE} - */ - protected int getDefaultBufferSize() { - return DEFAULT_BUFFER_SIZE; - } - - /** - * Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}. - * @param context the context to be used - */ - private byte[] resizeBuffer(final Context context) { - if (context.buffer == null) { - context.buffer = new byte[getDefaultBufferSize()]; - context.pos = 0; - context.readPos = 0; - } else { - final byte[] b = new byte[context.buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR]; - System.arraycopy(context.buffer, 0, b, 0, context.buffer.length); - context.buffer = b; - } - return context.buffer; - } - - /** - * Ensure that the buffer has room for size bytes - * - * @param size minimum spare space required - * @param context the context to be used - * @return the buffer - */ - protected byte[] ensureBufferSize(final int size, final Context context){ - if ((context.buffer == null) || (context.buffer.length < context.pos + size)){ - return resizeBuffer(context); - } - return context.buffer; - } - - /** - * Extracts buffered data into the provided byte[] array, starting at position bPos, up to a maximum of bAvail - * bytes. Returns how many bytes were actually extracted. - *

- * Package protected for access from I/O streams. - * - * @param b - * byte[] array to extract the buffered data into. - * @param bPos - * position in byte[] array to start extraction at. - * @param bAvail - * amount of bytes we're allowed to extract. We may extract fewer (if fewer are available). - * @param context - * the context to be used - * @return The number of bytes successfully extracted into the provided byte[] array. - */ - int readResults(final byte[] b, final int bPos, final int bAvail, final Context context) { - if (context.buffer != null) { - final int len = Math.min(available(context), bAvail); - System.arraycopy(context.buffer, context.readPos, b, bPos, len); - context.readPos += len; - if (context.readPos >= context.pos) { - context.buffer = null; // so hasData() will return false, and this method can return -1 - } - return len; - } - return context.eof ? EOF : 0; - } - - /** - * Checks if a byte value is whitespace or not. - * Whitespace is taken to mean: space, tab, CR, LF - * @param byteToCheck - * the byte to check - * @return true if byte is whitespace, false otherwise - */ - protected static boolean isWhiteSpace(final byte byteToCheck) { - switch (byteToCheck) { - case ' ' : - case '\n' : - case '\r' : - case '\t' : - return true; - default : - return false; - } + return context.buffer; + } + + /** + * Extracts buffered data into the provided byte[] array, starting at position bPos, up to a + * maximum of bAvail bytes. Returns how many bytes were actually extracted. + * + *

Package protected for access from I/O streams. + * + * @param b byte[] array to extract the buffered data into. + * @param bPos position in byte[] array to start extraction at. + * @param bAvail amount of bytes we're allowed to extract. We may extract fewer (if fewer are + * available). + * @param context the context to be used + * @return The number of bytes successfully extracted into the provided byte[] array. + */ + int readResults(final byte[] b, final int bPos, final int bAvail, final Context context) { + if (context.buffer != null) { + final int len = Math.min(available(context), bAvail); + System.arraycopy(context.buffer, context.readPos, b, bPos, len); + context.readPos += len; + if (context.readPos >= context.pos) { + context.buffer = null; // so hasData() will return false, and this method can return -1 + } + return len; } - - /** - * Encodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of - * the Encoder interface, and will throw an EncoderException if the supplied object is not of type byte[]. - * - * @param obj - * Object to encode - * @return An object (of type byte[]) containing the Base-N encoded data which corresponds to the byte[] supplied. - * @throws EncoderException - * if the parameter supplied is not of type byte[] - */ - @Override - public Object encode(final Object obj) throws EncoderException { - if (!(obj instanceof byte[])) { - throw new EncoderException("Parameter supplied to Base-N encode is not a byte[]"); - } - return encode((byte[]) obj); + return context.eof ? EOF : 0; + } + + /** + * Checks if a byte value is whitespace or not. Whitespace is taken to mean: space, tab, CR, LF + * + * @param byteToCheck the byte to check + * @return true if byte is whitespace, false otherwise + */ + protected static boolean isWhiteSpace(final byte byteToCheck) { + switch (byteToCheck) { + case ' ': + case '\n': + case '\r': + case '\t': + return true; + default: + return false; } - - /** - * Encodes a byte[] containing binary data, into a String containing characters in the Base-N alphabet. - * Uses UTF8 encoding. - * - * @param pArray - * a byte array containing binary data - * @return A String containing only Base-N character data - */ - public String encodeToString(final byte[] pArray) { - return StringUtils.newStringUtf8(encode(pArray)); + } + + /** + * Encodes an Object using the Base-N algorithm. This method is provided in order to satisfy the + * requirements of the Encoder interface, and will throw an EncoderException if the supplied + * object is not of type byte[]. + * + * @param obj Object to encode + * @return An object (of type byte[]) containing the Base-N encoded data which corresponds to the + * byte[] supplied. + * @throws EncoderException if the parameter supplied is not of type byte[] + */ + @Override + public Object encode(final Object obj) throws EncoderException { + if (!(obj instanceof byte[])) { + throw new EncoderException("Parameter supplied to Base-N encode is not a byte[]"); } - - /** - * Encodes a byte[] containing binary data, into a String containing characters in the appropriate alphabet. - * Uses UTF8 encoding. - * - * @param pArray a byte array containing binary data - * @return String containing only character data in the appropriate alphabet. - * @since 1.5 - * This is a duplicate of {@link #encodeToString(byte[])}; it was merged during refactoring. - */ - public String encodeAsString(final byte[] pArray){ - return StringUtils.newStringUtf8(encode(pArray)); + return encode((byte[]) obj); + } + + /** + * Encodes a byte[] containing binary data, into a String containing characters in the Base-N + * alphabet. Uses UTF8 encoding. + * + * @param pArray a byte array containing binary data + * @return A String containing only Base-N character data + */ + public String encodeToString(final byte[] pArray) { + return StringUtils.newStringUtf8(encode(pArray)); + } + + /** + * Encodes a byte[] containing binary data, into a String containing characters in the appropriate + * alphabet. Uses UTF8 encoding. + * + * @param pArray a byte array containing binary data + * @return String containing only character data in the appropriate alphabet. + * @since 1.5 This is a duplicate of {@link #encodeToString(byte[])}; it was merged during + * refactoring. + */ + public String encodeAsString(final byte[] pArray) { + return StringUtils.newStringUtf8(encode(pArray)); + } + + /** + * Decodes an Object using the Base-N algorithm. This method is provided in order to satisfy the + * requirements of the Decoder interface, and will throw a DecoderException if the supplied object + * is not of type byte[] or String. + * + * @param obj Object to decode + * @return An object (of type byte[]) containing the binary data which corresponds to the byte[] + * or String supplied. + * @throws DecoderException if the parameter supplied is not of type byte[] + */ + @Override + public Object decode(final Object obj) throws DecoderException { + if (obj instanceof byte[]) { + return decode((byte[]) obj); + } else if (obj instanceof String) { + return decode((String) obj); + } else { + throw new DecoderException("Parameter supplied to Base-N decode is not a byte[] or a String"); } - - /** - * Decodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of - * the Decoder interface, and will throw a DecoderException if the supplied object is not of type byte[] or String. - * - * @param obj - * Object to decode - * @return An object (of type byte[]) containing the binary data which corresponds to the byte[] or String - * supplied. - * @throws DecoderException - * if the parameter supplied is not of type byte[] - */ - @Override - public Object decode(final Object obj) throws DecoderException { - if (obj instanceof byte[]) { - return decode((byte[]) obj); - } else if (obj instanceof String) { - return decode((String) obj); - } else { - throw new DecoderException("Parameter supplied to Base-N decode is not a byte[] or a String"); - } + } + + /** + * Decodes a String containing characters in the Base-N alphabet. + * + * @param pArray A String containing Base-N character data + * @return a byte array containing binary data + */ + public byte[] decode(final String pArray) { + return decode(StringUtils.getBytesUtf8(pArray)); + } + + /** + * Decodes a byte[] containing characters in the Base-N alphabet. + * + * @param pArray A byte array containing Base-N character data + * @return a byte array containing binary data + */ + @Override + public byte[] decode(final byte[] pArray) { + if (pArray == null || pArray.length == 0) { + return pArray; } - - /** - * Decodes a String containing characters in the Base-N alphabet. - * - * @param pArray - * A String containing Base-N character data - * @return a byte array containing binary data - */ - public byte[] decode(final String pArray) { - return decode(StringUtils.getBytesUtf8(pArray)); + final Context context = new Context(); + decode(pArray, 0, pArray.length, context); + decode(pArray, 0, EOF, context); // Notify decoder of EOF. + final byte[] result = new byte[context.pos]; + readResults(result, 0, result.length, context); + return result; + } + + /** + * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet. + * + * @param pArray a byte array containing binary data + * @return A byte array containing only the base N alphabetic character data + */ + @Override + public byte[] encode(final byte[] pArray) { + if (pArray == null || pArray.length == 0) { + return pArray; } - - /** - * Decodes a byte[] containing characters in the Base-N alphabet. - * - * @param pArray - * A byte array containing Base-N character data - * @return a byte array containing binary data - */ - @Override - public byte[] decode(final byte[] pArray) { - if (pArray == null || pArray.length == 0) { - return pArray; - } - final Context context = new Context(); - decode(pArray, 0, pArray.length, context); - decode(pArray, 0, EOF, context); // Notify decoder of EOF. - final byte[] result = new byte[context.pos]; - readResults(result, 0, result.length, context); - return result; + return encode(pArray, 0, pArray.length); + } + + /** + * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet. + * + * @param pArray a byte array containing binary data + * @param offset initial offset of the subarray. + * @param length length of the subarray. + * @return A byte array containing only the base N alphabetic character data + * @since 1.11 + */ + public byte[] encode(final byte[] pArray, final int offset, final int length) { + if (pArray == null || pArray.length == 0) { + return pArray; } - - /** - * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet. - * - * @param pArray - * a byte array containing binary data - * @return A byte array containing only the base N alphabetic character data - */ - @Override - public byte[] encode(final byte[] pArray) { - if (pArray == null || pArray.length == 0) { - return pArray; - } - return encode(pArray, 0, pArray.length); + final Context context = new Context(); + encode(pArray, offset, length, context); + encode(pArray, offset, EOF, context); // Notify encoder of EOF. + final byte[] buf = new byte[context.pos - context.readPos]; + readResults(buf, 0, buf.length, context); + return buf; + } + + // package protected for access from I/O streams + abstract void encode(byte[] pArray, int i, int length, Context context); + + // package protected for access from I/O streams + abstract void decode(byte[] pArray, int i, int length, Context context); + + /** + * Returns whether or not the octet is in the current alphabet. Does not allow + * whitespace or pad. + * + * @param value The value to test + * @return true if the value is defined in the current alphabet, false + * otherwise. + */ + protected abstract boolean isInAlphabet(byte value); + + /** + * Tests a given byte array to see if it contains only valid characters within the alphabet. The + * method optionally treats whitespace and pad as valid. + * + * @param arrayOctet byte array to test + * @param allowWSPad if true, then whitespace and PAD are also allowed + * @return true if all bytes are valid characters in the alphabet or if the byte + * array is empty; false, otherwise + */ + public boolean isInAlphabet(final byte[] arrayOctet, final boolean allowWSPad) { + for (final byte octet : arrayOctet) { + if (!isInAlphabet(octet) && (!allowWSPad || (octet != pad) && !isWhiteSpace(octet))) { + return false; + } } - - /** - * Encodes a byte[] containing binary data, into a byte[] containing - * characters in the alphabet. - * - * @param pArray - * a byte array containing binary data - * @param offset - * initial offset of the subarray. - * @param length - * length of the subarray. - * @return A byte array containing only the base N alphabetic character data - * @since 1.11 - */ - public byte[] encode(final byte[] pArray, final int offset, final int length) { - if (pArray == null || pArray.length == 0) { - return pArray; - } - final Context context = new Context(); - encode(pArray, offset, length, context); - encode(pArray, offset, EOF, context); // Notify encoder of EOF. - final byte[] buf = new byte[context.pos - context.readPos]; - readResults(buf, 0, buf.length, context); - return buf; + return true; + } + + /** + * Tests a given String to see if it contains only valid characters within the alphabet. The + * method treats whitespace and PAD as valid. + * + * @param basen String to test + * @return true if all characters in the String are valid characters in the alphabet + * or if the String is empty; false, otherwise + * @see #isInAlphabet(byte[], boolean) + */ + public boolean isInAlphabet(final String basen) { + return isInAlphabet(StringUtils.getBytesUtf8(basen), true); + } + + /** + * Tests a given byte array to see if it contains any characters within the alphabet or PAD. + * + *

Intended for use in checking line-ending arrays + * + * @param arrayOctet byte array to test + * @return true if any byte is a valid character in the alphabet or PAD; false + * otherwise + */ + protected boolean containsAlphabetOrPad(final byte[] arrayOctet) { + if (arrayOctet == null) { + return false; } - - // package protected for access from I/O streams - abstract void encode(byte[] pArray, int i, int length, Context context); - - // package protected for access from I/O streams - abstract void decode(byte[] pArray, int i, int length, Context context); - - /** - * Returns whether or not the octet is in the current alphabet. - * Does not allow whitespace or pad. - * - * @param value The value to test - * - * @return true if the value is defined in the current alphabet, false otherwise. - */ - protected abstract boolean isInAlphabet(byte value); - - /** - * Tests a given byte array to see if it contains only valid characters within the alphabet. - * The method optionally treats whitespace and pad as valid. - * - * @param arrayOctet byte array to test - * @param allowWSPad if true, then whitespace and PAD are also allowed - * - * @return true if all bytes are valid characters in the alphabet or if the byte array is empty; - * false, otherwise - */ - public boolean isInAlphabet(final byte[] arrayOctet, final boolean allowWSPad) { - for (final byte octet : arrayOctet) { - if (!isInAlphabet(octet) && - (!allowWSPad || (octet != pad) && !isWhiteSpace(octet))) { - return false; - } - } + for (final byte element : arrayOctet) { + if (pad == element || isInAlphabet(element)) { return true; + } } - - /** - * Tests a given String to see if it contains only valid characters within the alphabet. - * The method treats whitespace and PAD as valid. - * - * @param basen String to test - * @return true if all characters in the String are valid characters in the alphabet or if - * the String is empty; false, otherwise - * @see #isInAlphabet(byte[], boolean) - */ - public boolean isInAlphabet(final String basen) { - return isInAlphabet(StringUtils.getBytesUtf8(basen), true); - } - - /** - * Tests a given byte array to see if it contains any characters within the alphabet or PAD. - * - * Intended for use in checking line-ending arrays - * - * @param arrayOctet - * byte array to test - * @return true if any byte is a valid character in the alphabet or PAD; false otherwise - */ - protected boolean containsAlphabetOrPad(final byte[] arrayOctet) { - if (arrayOctet == null) { - return false; - } - for (final byte element : arrayOctet) { - if (pad == element || isInAlphabet(element)) { - return true; - } - } - return false; - } - - /** - * Calculates the amount of space needed to encode the supplied array. - * - * @param pArray byte[] array which will later be encoded - * - * @return amount of space needed to encoded the supplied array. - * Returns a long since a max-len array will require > Integer.MAX_VALUE - */ - public long getEncodedLength(final byte[] pArray) { - // Calculate non-chunked size - rounded up to allow for padding - // cast to long is needed to avoid possibility of overflow - long len = ((pArray.length + unencodedBlockSize-1) / unencodedBlockSize) * (long) encodedBlockSize; - if (lineLength > 0) { // We're using chunking - // Round up to nearest multiple - len += ((len + lineLength-1) / lineLength) * chunkSeparatorLength; - } - return len; + return false; + } + + /** + * Calculates the amount of space needed to encode the supplied array. + * + * @param pArray byte[] array which will later be encoded + * @return amount of space needed to encoded the supplied array. Returns a long since a max-len + * array will require > Integer.MAX_VALUE + */ + public long getEncodedLength(final byte[] pArray) { + // Calculate non-chunked size - rounded up to allow for padding + // cast to long is needed to avoid possibility of overflow + long len = + ((pArray.length + unencodedBlockSize - 1) / unencodedBlockSize) * (long) encodedBlockSize; + if (lineLength > 0) { // We're using chunking + // Round up to nearest multiple + len += ((len + lineLength - 1) / lineLength) * chunkSeparatorLength; } + return len; + } } diff --git a/src/main/java/io/ipfs/multibase/binary/StringUtils.java b/src/main/java/io/ipfs/multibase/binary/StringUtils.java index 00ae2ac..9d8bd67 100644 --- a/src/main/java/io/ipfs/multibase/binary/StringUtils.java +++ b/src/main/java/io/ipfs/multibase/binary/StringUtils.java @@ -17,104 +17,99 @@ package io.ipfs.multibase.binary; -import java.nio.charset.Charset; - import io.ipfs.multibase.CharEncoding; import io.ipfs.multibase.Charsets; +import java.nio.charset.Charset; /** - * Converts String to and from bytes using the encodings required by the Java specification. These encodings are - * specified in - * Standard charsets. + * Converts String to and from bytes using the encodings required by the Java specification. These + * encodings are specified in Standard + * charsets. * - *

This class is immutable and thread-safe.

+ *

This class is immutable and thread-safe. * * @see CharEncoding - * @see Standard charsets + * @see Standard + * charsets * @version $Id$ * @since 1.4 */ public class StringUtils { - /** - * Calls {@link String#getBytes(Charset)} - * - * @param string - * The string to encode (if null, return null). - * @param charset - * The {@link Charset} to encode the String - * @return the encoded bytes - */ - private static byte[] getBytes(final String string, final Charset charset) { - if (string == null) { - return null; - } - return string.getBytes(charset); - } - - /** - * Encodes the given string into a sequence of bytes using the UTF-8 charset, storing the result into a new byte - * array. - * - * @param string - * the String to encode, may be null - * @return encoded bytes, or null if the input string was null - * @throws NullPointerException - * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is - * required by the Java platform specification. - * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException - * @see Standard charsets - */ - public static byte[] getBytesUtf8(final String string) { - return getBytes(string, Charset.forName("UTF-8")); + /** + * Calls {@link String#getBytes(Charset)} + * + * @param string The string to encode (if null, return null). + * @param charset The {@link Charset} to encode the String + * @return the encoded bytes + */ + private static byte[] getBytes(final String string, final Charset charset) { + if (string == null) { + return null; } + return string.getBytes(charset); + } - /** - * Constructs a new String by decoding the specified array of bytes using the given charset. - * - * @param bytes - * The bytes to be decoded into characters - * @param charset - * The {@link Charset} to encode the String; not {@code null} - * @return A new String decoded from the specified array of bytes using the given charset, - * or null if the input byte array was null. - * @throws NullPointerException - * Thrown if charset is {@code null} - */ - private static String newString(final byte[] bytes, final Charset charset) { - return bytes == null ? null : new String(bytes, charset); - } + /** + * Encodes the given string into a sequence of bytes using the UTF-8 charset, storing the result + * into a new byte array. + * + * @param string the String to encode, may be null + * @return encoded bytes, or null if the input string was null + * @throws NullPointerException Thrown if {@link Charsets#UTF_8} is not initialized, which should + * never happen since it is required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + * @see Standard + * charsets + */ + public static byte[] getBytesUtf8(final String string) { + return getBytes(string, Charset.forName("UTF-8")); + } - /** - * Constructs a new String by decoding the specified array of bytes using the US-ASCII charset. - * - * @param bytes - * The bytes to be decoded into characters - * @return A new String decoded from the specified array of bytes using the US-ASCII charset, - * or null if the input byte array was null. - * @throws NullPointerException - * Thrown if {@link Charsets#US_ASCII} is not initialized, which should never happen since it is - * required by the Java platform specification. - * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException - */ - public static String newStringUsAscii(final byte[] bytes) { - return newString(bytes, Charset.forName("US-ASCII")); - } + /** + * Constructs a new String by decoding the specified array of bytes using the given + * charset. + * + * @param bytes The bytes to be decoded into characters + * @param charset The {@link Charset} to encode the String; not {@code null} + * @return A new String decoded from the specified array of bytes using the given + * charset, or null if the input byte array was null. + * @throws NullPointerException Thrown if charset is {@code null} + */ + private static String newString(final byte[] bytes, final Charset charset) { + return bytes == null ? null : new String(bytes, charset); + } - /** - * Constructs a new String by decoding the specified array of bytes using the UTF-8 charset. - * - * @param bytes - * The bytes to be decoded into characters - * @return A new String decoded from the specified array of bytes using the UTF-8 charset, - * or null if the input byte array was null. - * @throws NullPointerException - * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is - * required by the Java platform specification. - * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException - */ - public static String newStringUtf8(final byte[] bytes) { - return newString(bytes, Charset.forName("UTF-8")); - } + /** + * Constructs a new String by decoding the specified array of bytes using the + * US-ASCII charset. + * + * @param bytes The bytes to be decoded into characters + * @return A new String decoded from the specified array of bytes using the US-ASCII + * charset, or null if the input byte array was null. + * @throws NullPointerException Thrown if {@link Charsets#US_ASCII} is not initialized, which + * should never happen since it is required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + */ + public static String newStringUsAscii(final byte[] bytes) { + return newString(bytes, Charset.forName("US-ASCII")); + } + /** + * Constructs a new String by decoding the specified array of bytes using the UTF-8 + * charset. + * + * @param bytes The bytes to be decoded into characters + * @return A new String decoded from the specified array of bytes using the UTF-8 + * charset, or null if the input byte array was null. + * @throws NullPointerException Thrown if {@link Charsets#UTF_8} is not initialized, which should + * never happen since it is required by the Java platform specification. + * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException + */ + public static String newStringUtf8(final byte[] bytes) { + return newString(bytes, Charset.forName("UTF-8")); + } } diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index f9feddd..865dfda 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,4 +1,4 @@ module io.ipfs.multibase { - exports io.ipfs.multibase; - exports io.ipfs.multibase.binary; + exports io.ipfs.multibase; + exports io.ipfs.multibase.binary; } diff --git a/src/main/main.iml b/src/main/main.iml deleted file mode 100644 index 545b73d..0000000 --- a/src/main/main.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/java/io/ipfs/multibase/MultibaseBadInputsTest.java b/src/test/java/io/ipfs/multibase/MultibaseBadInputsTest.java index 2f9521f..d252919 100755 --- a/src/test/java/io/ipfs/multibase/MultibaseBadInputsTest.java +++ b/src/test/java/io/ipfs/multibase/MultibaseBadInputsTest.java @@ -5,42 +5,43 @@ import java.util.Arrays; import java.util.Collection; - import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; public class MultibaseBadInputsTest { - public static Collection data() { - return Arrays.asList( - "f012", // Hex string of odd length, not allowed in Base16 - "f0g", // 'g' char is not allowed in Base16 - "zt1Zv2yaI", // 'I' char is not allowed in Base58 - "2", // '2' is not a valid encoding marker - "", // Empty string is not a valid multibase - "๐Ÿš€๐Ÿซ•" // This Emoji (Swiss Fondue) is not part of the Base256Emoji table + public static Collection data() { + return Arrays.asList( + "f012", // Hex string of odd length, not allowed in Base16 + "f0g", // 'g' char is not allowed in Base16 + "zt1Zv2yaI", // 'I' char is not allowed in Base58 + "2", // '2' is not a valid encoding marker + "", // Empty string is not a valid multibase + "๐Ÿš€๐Ÿซ•" // This Emoji (Swiss Fondue) is not part of the Base256Emoji table ); - } - - @MethodSource("data") - @ParameterizedTest(name = "{index}: \"{0}\"") - public void badInputTest(String input) { - assertThrows(IllegalArgumentException.class, () -> { - Multibase.decode(input); + } + + @MethodSource("data") + @ParameterizedTest(name = "{index}: \"{0}\"") + public void badInputTest(String input) { + assertThrows( + IllegalArgumentException.class, + () -> { + Multibase.decode(input); }); - } + } - public static Collection invalidPrefix() { - return Arrays.asList( - "2", // '2' is not a valid encoding marker - "", // Empty string is not a valid multibase - "๐Ÿซ•๐Ÿš€" // This not a valid Emoji Multibase (note how it's inverted) + public static Collection invalidPrefix() { + return Arrays.asList( + "2", // '2' is not a valid encoding marker + "", // Empty string is not a valid multibase + "๐Ÿซ•๐Ÿš€" // This not a valid Emoji Multibase (note how it's inverted) ); - } + } - @MethodSource("invalidPrefix") - @ParameterizedTest(name = "{index}: \"{0}\"") - public void invalidPrefix(String input) { - assertFalse(Multibase.hasValidPrefix(input)); - } + @MethodSource("invalidPrefix") + @ParameterizedTest(name = "{index}: \"{0}\"") + public void invalidPrefix(String input) { + assertFalse(Multibase.hasValidPrefix(input)); + } } diff --git a/src/test/java/io/ipfs/multibase/MultibaseTest.java b/src/test/java/io/ipfs/multibase/MultibaseTest.java index 71d30a6..a192d53 100644 --- a/src/test/java/io/ipfs/multibase/MultibaseTest.java +++ b/src/test/java/io/ipfs/multibase/MultibaseTest.java @@ -1,101 +1,188 @@ package io.ipfs.multibase; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.util.Arrays; import java.util.Collection; - import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - public class MultibaseTest { - public static Collection data() { - return Arrays.asList(new Object[][]{ - {Multibase.Base.Base58BTC, hexToBytes("1220120F6AF601D46E10B2D2E11ED71C55D25F3042C22501E41D1246E7A1E9D3D8EC"), "zQmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB"}, - {Multibase.Base.Base58BTC, hexToBytes("1220BA8632EF1A07986B171B3C8FAF0F79B3EE01B6C30BBE15A13261AD6CB0D02E3A"), "zQmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy"}, - {Multibase.Base.Base58BTC, new byte[1], "z1"}, - {Multibase.Base.Base58BTC, new byte[2], "z11"}, - {Multibase.Base.Base58BTC, new byte[4], "z1111"}, - {Multibase.Base.Base58BTC, new byte[8], "z11111111"}, - {Multibase.Base.Base58BTC, new byte[16], "z1111111111111111"}, - {Multibase.Base.Base58BTC, new byte[32], "z11111111111111111111111111111111"}, - {Multibase.Base.Base16, hexToBytes("234ABED8DEBEDE"), "f234abed8debede"}, - {Multibase.Base.Base16, hexToBytes("87AD873DEFC2B288"), "f87ad873defc2b288"}, - {Multibase.Base.Base16, hexToBytes(""), "f"}, - {Multibase.Base.Base16, hexToBytes("01"), "f01"}, - {Multibase.Base.Base16, hexToBytes("0123456789ABCDEF"), "f0123456789abcdef"}, - {Multibase.Base.Base16Upper, hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), "F446563656E7472616C697A652065766572797468696E67212121"}, - {Multibase.Base.Base32, hexToBytes("4D756C74696261736520697320617765736F6D6521205C6F2F"), "bjv2wy5djmjqxgzjanfzsaylxmvzw63lfeeqfy3zp"}, - {Multibase.Base.Base32, hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), "birswgzloorzgc3djpjssazlwmvzhs5dinfxgoijbee"}, - {Multibase.Base.Base32, hexToBytes("01711220bb6ef01d25459cc803d0864cde4227cd2b779965eb1df34abeaec22c20fa42ea"), "bafyreif3n3yb2jkftteahuegjtpeej6nfn3zszpldxzuvpvoyiwcb6sc5i"}, - {Multibase.Base.Base32, hexToBytes("0000000000"), "baaaaaaaa"}, - {Multibase.Base.Base32, hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), "birswgzloorzgc3djpjssazlwmvzhs5dinfxgoijbee"}, - {Multibase.Base.Base32Pad, hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), "cirswgzloorzgc3djpjssazlwmvzhs5dinfxgoijbee======"}, - {Multibase.Base.Base32PadUpper, hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), "CIRSWGZLOORZGC3DJPJSSAZLWMVZHS5DINFXGOIJBEE======"}, - {Multibase.Base.Base32Upper, hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), "BIRSWGZLOORZGC3DJPJSSAZLWMVZHS5DINFXGOIJBEE"}, - {Multibase.Base.Base32Hex, hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), "v8him6pbeehp62r39f9ii0pbmclp7it38d5n6e89144"}, - {Multibase.Base.Base32HexPad, hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), "t8him6pbeehp62r39f9ii0pbmclp7it38d5n6e89144======"}, - {Multibase.Base.Base32HexPadUpper, hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), "T8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E89144======"}, - {Multibase.Base.Base32HexUpper, hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), "V8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E89144"}, - {Multibase.Base.Base36, hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), "km552ng4dabi4neu1oo8l4i5mndwmpc3mkukwtxy9"}, - {Multibase.Base.Base36, hexToBytes("00446563656e7472616c697a652065766572797468696e67212121"), "k0m552ng4dabi4neu1oo8l4i5mndwmpc3mkukwtxy9"}, - {Multibase.Base.Base36Upper, hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), "KM552NG4DABI4NEU1OO8L4I5MNDWMPC3MKUKWTXY9"}, - {Multibase.Base.Base58BTC, hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), "z36UQrhJq9fNDS7DiAHM9YXqDHMPfr4EMArvt"}, - {Multibase.Base.Base64, hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), "mRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE"}, - {Multibase.Base.Base64Url, hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), "uRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE"}, - {Multibase.Base.Base64Pad, hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), "MRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE="}, - {Multibase.Base.Base64UrlPad, hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), "URGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE="}, - - {Multibase.Base.Base256Emoji, hexToBytes(""), "๐Ÿš€"}, - {Multibase.Base.Base256Emoji, hexToBytes("0107FF"), "๐Ÿš€๐Ÿช๐ŸŒ“๐Ÿฅ‚"}, + public static Collection data() { + return Arrays.asList( + new Object[][] { + { + Multibase.Base.Base58BTC, + hexToBytes("1220120F6AF601D46E10B2D2E11ED71C55D25F3042C22501E41D1246E7A1E9D3D8EC"), + "zQmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB" + }, + { + Multibase.Base.Base58BTC, + hexToBytes("1220BA8632EF1A07986B171B3C8FAF0F79B3EE01B6C30BBE15A13261AD6CB0D02E3A"), + "zQmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy" + }, + {Multibase.Base.Base58BTC, new byte[1], "z1"}, + {Multibase.Base.Base58BTC, new byte[2], "z11"}, + {Multibase.Base.Base58BTC, new byte[4], "z1111"}, + {Multibase.Base.Base58BTC, new byte[8], "z11111111"}, + {Multibase.Base.Base58BTC, new byte[16], "z1111111111111111"}, + {Multibase.Base.Base58BTC, new byte[32], "z11111111111111111111111111111111"}, + {Multibase.Base.Base16, hexToBytes("234ABED8DEBEDE"), "f234abed8debede"}, + {Multibase.Base.Base16, hexToBytes("87AD873DEFC2B288"), "f87ad873defc2b288"}, + {Multibase.Base.Base16, hexToBytes(""), "f"}, + {Multibase.Base.Base16, hexToBytes("01"), "f01"}, + {Multibase.Base.Base16, hexToBytes("0123456789ABCDEF"), "f0123456789abcdef"}, + { + Multibase.Base.Base16Upper, + hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), + "F446563656E7472616C697A652065766572797468696E67212121" + }, + { + Multibase.Base.Base32, + hexToBytes("4D756C74696261736520697320617765736F6D6521205C6F2F"), + "bjv2wy5djmjqxgzjanfzsaylxmvzw63lfeeqfy3zp" + }, + { + Multibase.Base.Base32, + hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), + "birswgzloorzgc3djpjssazlwmvzhs5dinfxgoijbee" + }, + { + Multibase.Base.Base32, + hexToBytes("01711220bb6ef01d25459cc803d0864cde4227cd2b779965eb1df34abeaec22c20fa42ea"), + "bafyreif3n3yb2jkftteahuegjtpeej6nfn3zszpldxzuvpvoyiwcb6sc5i" + }, + {Multibase.Base.Base32, hexToBytes("0000000000"), "baaaaaaaa"}, + { + Multibase.Base.Base32, + hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), + "birswgzloorzgc3djpjssazlwmvzhs5dinfxgoijbee" + }, + { + Multibase.Base.Base32Pad, + hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), + "cirswgzloorzgc3djpjssazlwmvzhs5dinfxgoijbee======" + }, + { + Multibase.Base.Base32PadUpper, + hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), + "CIRSWGZLOORZGC3DJPJSSAZLWMVZHS5DINFXGOIJBEE======" + }, + { + Multibase.Base.Base32Upper, + hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), + "BIRSWGZLOORZGC3DJPJSSAZLWMVZHS5DINFXGOIJBEE" + }, + { + Multibase.Base.Base32Hex, + hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), + "v8him6pbeehp62r39f9ii0pbmclp7it38d5n6e89144" + }, + { + Multibase.Base.Base32HexPad, + hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), + "t8him6pbeehp62r39f9ii0pbmclp7it38d5n6e89144======" + }, + { + Multibase.Base.Base32HexPadUpper, + hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), + "T8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E89144======" + }, + { + Multibase.Base.Base32HexUpper, + hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), + "V8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E89144" + }, + { + Multibase.Base.Base36, + hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), + "km552ng4dabi4neu1oo8l4i5mndwmpc3mkukwtxy9" + }, + { + Multibase.Base.Base36, + hexToBytes("00446563656e7472616c697a652065766572797468696e67212121"), + "k0m552ng4dabi4neu1oo8l4i5mndwmpc3mkukwtxy9" + }, + { + Multibase.Base.Base36Upper, + hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), + "KM552NG4DABI4NEU1OO8L4I5MNDWMPC3MKUKWTXY9" + }, + { + Multibase.Base.Base58BTC, + hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), + "z36UQrhJq9fNDS7DiAHM9YXqDHMPfr4EMArvt" + }, + { + Multibase.Base.Base64, + hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), + "mRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE" + }, + { + Multibase.Base.Base64Url, + hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), + "uRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE" + }, + { + Multibase.Base.Base64Pad, + hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), + "MRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE=" + }, + { + Multibase.Base.Base64UrlPad, + hexToBytes("446563656e7472616c697a652065766572797468696e67212121"), + "URGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE=" + }, + {Multibase.Base.Base256Emoji, hexToBytes(""), "๐Ÿš€"}, + {Multibase.Base.Base256Emoji, hexToBytes("0107FF"), "๐Ÿš€๐Ÿช๐ŸŒ“๐Ÿฅ‚"}, }); - } + } - @MethodSource("data") - @ParameterizedTest(name = "{index}: {0}, {2}") - public void testEncode(Multibase.Base base, byte[] raw, String encoded) { - String output = Multibase.encode(base, raw); - assertEquals(encoded, output); - } + @MethodSource("data") + @ParameterizedTest(name = "{index}: {0}, {2}") + public void testEncode(Multibase.Base base, byte[] raw, String encoded) { + String output = Multibase.encode(base, raw); + assertEquals(encoded, output); + } - @MethodSource("data") - @ParameterizedTest(name = "{index}: {0}, {2}") - public void testDecode(Multibase.Base base, byte[] raw, String encoded) { - byte[] output = Multibase.decode(encoded); - assertArrayEquals(raw, output, String.format("Expected %s, but got %s", bytesToHex(raw), bytesToHex(output))); - } + @MethodSource("data") + @ParameterizedTest(name = "{index}: {0}, {2}") + public void testDecode(Multibase.Base base, byte[] raw, String encoded) { + byte[] output = Multibase.decode(encoded); + assertArrayEquals( + raw, output, String.format("Expected %s, but got %s", bytesToHex(raw), bytesToHex(output))); + } - @MethodSource("data") - @ParameterizedTest(name = "{index}: {0}, {2}") - public void testHasValidPrefix(Multibase.Base base, byte[] raw, String encoded) { - assertTrue(Multibase.hasValidPrefix(encoded)); - } + @MethodSource("data") + @ParameterizedTest(name = "{index}: {0}, {2}") + public void testHasValidPrefix(Multibase.Base base, byte[] raw, String encoded) { + assertTrue(Multibase.hasValidPrefix(encoded)); + } - //Copied from https://stackoverflow.com/a/140861 - private static byte[] hexToBytes(String s) { - int len = s.length(); - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) - + Character.digit(s.charAt(i + 1), 16)); - } - return data; + // Copied from https://stackoverflow.com/a/140861 + private static byte[] hexToBytes(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = + (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); } + return data; + } - //Copied from https://stackoverflow.com/a/9855338 - private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + // Copied from https://stackoverflow.com/a/9855338 + private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); - private static String bytesToHex(byte[] bytes) { - 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); + private static String bytesToHex(byte[] bytes) { + 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); + } } diff --git a/src/test/test.iml b/src/test/test.iml deleted file mode 100644 index 15bf3f2..0000000 --- a/src/test/test.iml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file