pax_global_header00006660000000000000000000000064146033253010014507gustar00rootroot0000000000000052 comment=9d93b72eebef8dab16bce1147729975bae326b4d qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/000077500000000000000000000000001460332530100233745ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/.editorconfig000066400000000000000000000011551460332530100260530ustar00rootroot00000000000000# EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs # editorconfig.org root = true [*] indent_style = space indent_size = 4 charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true max_line_length = 150 # From https://maven.apache.org/developers/conventions/code.html [*.xml] indent_size = 2 max_line_length = 240 [*.java] # ij_continuation_indent_size is non-standard and only works in IntelliJ. Currently continuation_indent_size is not part of the # .editorconfig standard. ij_continuation_indent_size = 4 [*.md] max_line_length = 120 qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/.gitignore000066400000000000000000000005521460332530100253660ustar00rootroot00000000000000target/ pom.xml.tag pom.xml.releaseBackup pom.xml.versionsBackup pom.xml.next release.properties dependency-reduced-pom.xml buildNumber.properties .mvn/timing.properties # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) !/.mvn/wrapper/maven-wrapper.jar # intellij java .idea/ *.iml # eclipse java .classpath .project *.prefs /.metadata/qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/CONTRIBUTING.md000066400000000000000000000045451460332530100256350ustar00rootroot00000000000000# Contributor's Guide:
Extensions for Apache Proton-J library ## Code of conduct This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. ## Getting started ### Prerequisites To build and test this locally, make sure you install: - Java Development Kit (JDK) with version 8 or above. - [Maven](https://maven.apache.org/). ### Building and packaging Open a command prompt/terminal: 1. Execute `git clone https://github.com/Azure/qpid-proton-j-extensions.git`. 1. Traverse to the repository root. 1. Execute `mvn install`. This should successfully run all unit/integration tests, build the qpid-proton-j-extensions JAR, and install it to your local Maven repository. ## Filing issues You can find the issues that have been filed in the [Issues](https://github.com/Azure/qpid-proton-j-extensions/issues) section of the repository. If you encounter any bugs, would like to request a feature, or have general questions/concerns/comments, feel free to file an issue [here](https://github.com/Azure/qpid-proton-j-extensions/issues/new). ## Pull requests ### Required guidelines When filing a pull request, it must pass our CI build. - Tests have been added to validate changes. - All tests pass. - Zero CheckStyle and Spotbugs violations. - `mvn verify` has no violations. ### General guidelines If you would like to make changes to this library, **break up the change into small, logical, testable chunks, and organize your pull requests accordingly**. This makes for a cleaner, less error-prone development process. If you'd like to get involved, but don't know what to work on, then please reach out to us by opening an issue. If you're new to opening pull requests - or would like some additional guidance - the following list is a good set of best practices! - Title of the pull request is clear and informative. - Commits are small and each have an informative message. - A description of the changes the pull request makes is included, and a reference to the bug/issue the pull request fixes is included, if applicable. - Pull request includes comprehensive test coverage for the included changes. qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/LICENSE000066400000000000000000000022121460332530100243760ustar00rootroot00000000000000 MIT License Copyright (c) Microsoft Corporation. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/README.md000066400000000000000000000033301460332530100246520ustar00rootroot00000000000000

Microsoft Azure Event Hubs

Extensions on qpid-proton-j library

star our repo follow on Twitter

The Advanced Message Queuing Protocol (AMQP) is an open internet protocol for messaging. Several messaging platforms hosted on Azure like Azure Service Bus, Azure Event Hubs and Azure IOT Hub supports AMQP 1.0. To know more about AMQP refer to the [OASIS AMQP 1.0 protocol specification][amqp-spec]. In our Java client libraries we use [qpid-proton-j library][proton-j], an open source implementation of AMQP. Extensions built atop [qpid-proton-j library][proton-j] are hosted here. ## Issues Currently, this library is used only in conjunction with [Azure IOT SDK][azure-iot] or [Azure Event Hubs client library][azure-sdk-for-java]. File the issues directly on the respective projects. ## Contributing This project welcomes contributions and suggestions. Please refer to our [Contribution Guidelines](./CONTRIBUTING.md) for more details. [amqp-spec]: http://docs.oasis-open.org/amqp/core/v1.0/amqp-core-overview-v1.0.html [azure-iot]: https://github.com/Azure/azure-iot-sdk-java [azure-sdk-for-java]: https://github.com/Azure/azure-sdk-for-java [proton-j]: https://github.com/apache/qpid-proton-j qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/SECURITY.md000066400000000000000000000053051460332530100251700ustar00rootroot00000000000000 ## Security Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. ## Reporting Security Issues **Please do not report security vulnerabilities through public GitHub issues.** Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) * Full paths of source file(s) related to the manifestation of the issue * The location of the affected source code (tag/branch/commit or direct URL) * Any special configuration required to reproduce the issue * Step-by-step instructions to reproduce the issue * Proof-of-concept or exploit code (if possible) * Impact of the issue, including how an attacker might exploit the issue This information will help us triage your report more quickly. If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. ## Preferred Languages We prefer all communications to be in English. ## Policy Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/ci.yml000066400000000000000000000120531460332530100245130ustar00rootroot00000000000000trigger: branches: include: - master - feature/* - release/* - hotfix/* pr: branches: include: - master - feature/* - release/* - hotfix/* extends: template: /eng/templates/1es-redirect.yml parameters: stages: - stage: Build variables: - name: DefaultOptions value: '--batch-mode --fail-at-end -Dmaven.wagon.http.pool=false' - name: LoggingOptions value: '-Dorg.slf4j.simpleLogger.defaultLogLevel=error -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn' - name: MemoryOptions value: '-Xmx3072m' - template: /eng/templates/image.yml jobs: - job: 'Build' pool: name: $(LINUXPOOL) image: $(LINUXVMIMAGE) os: linux strategy: matrix: Java 8: ArtifactName: 'java8-packages' ProfileFlag: '-Djava8' JavaVersion: '1.8' # We name this 'packages' because it is the default version we want to ship with. Java LTS: ArtifactName: 'packages' ProfileFlag: '-Djava-lts' JavaVersion: '1.11' steps: - task: Maven@3 displayName: 'Build and Package' inputs: mavenPomFile: 'pom.xml' goals: 'deploy' options: '$(DefaultOptions) $(ProfileFlag) -T 1C -DskipTests -Dgpg.skip -Dcheckstyle.skip=true -Dspotbugs.skip=true --settings eng/settings.xml -DaltDeploymentRepository="local::default::file:///${project.basedir}/output"' mavenOptions: '$(LoggingOptions) $(MemoryOptions)' javaHomeOption: 'JDKVersion' jdkVersionOption: $(JavaVersion) jdkArchitectureOption: 'x64' publishJUnitResults: false - task: Maven@3 displayName: 'Run SpotBugs, Checkstyle, and Javadoc' inputs: mavenPomFile: pom.xml options: '$(DefaultOptions) --no-transfer-progress -DskipTests -Dgpg.skip' mavenOptions: '$(MemoryOptions)' javaHomeOption: 'JDKVersion' jdkVersionOption: $(JavaVersion) jdkArchitectureOption: 'x64' publishJUnitResults: false goals: 'verify' - script: | cp output/com/microsoft/azure/qpid-proton-j-extensions/**/* $(Build.ArtifactStagingDirectory) rm $(Build.ArtifactStagingDirectory)/*.sha1 rm $(Build.ArtifactStagingDirectory)/*.md5 displayName: Flatten and copy build outputs - task: 1ES.PublishPipelineArtifact@1 displayName: 'Publish outputs to $(ArtifactName) artifact' inputs: artifactName: $(ArtifactName) targetPath: $(Build.ArtifactStagingDirectory) - job: displayName: 'Test' strategy: matrix: Linux - Java 8: OSName: 'Linux' OSVmImage: 'ubuntu-22.04' ProfileFlag: '-Djava8' JavaVersion: '1.8' Linux - Java LTS: OSName: 'Linux' OSVmImage: 'ubuntu-22.04' ProfileFlag: '-Djava-lts' JavaVersion: '1.11' pool: name: $(LINUXPOOL) image: $(LINUXVMIMAGE) os: linux steps: - template: /eng/templates/test-steps.yml - job: displayName: 'Test' strategy: matrix: Windows - Java 8: OSName: 'Windows' OSVmImage: 'windows-2022' ProfileFlag: '-Djava8' JavaVersion: '1.8' Windows - Java LTS: OSName: 'Windows' OSVmImage: 'windows-2022' ProfileFlag: '-Djava-lts' JavaVersion: '1.11' pool: name: $(WINDOWSPOOL) image: $(WINDOWSVMIMAGE) os: windows steps: - template: /eng/templates/test-steps.yml - job: displayName: 'Test' strategy: matrix: macOS - Java 8: OSName: 'macOS' OSVmImage: 'macOS-13' ProfileFlag: '-Djava8' JavaVersion: '1.8' macOS - Java LTS: OSName: 'macOS' OSVmImage: 'macOS-13' ProfileFlag: '-Djava-lts' JavaVersion: '1.11' pool: name: $(MACPOOL) vmImage: $(MACVMIMAGE) os: macOS steps: - template: /eng/templates/test-steps.ymlqpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/eng/000077500000000000000000000000001460332530100241455ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/eng/checkstyle/000077500000000000000000000000001460332530100263035ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/eng/checkstyle/checkstyle-suppressions.xml000066400000000000000000000006171460332530100337420ustar00rootroot00000000000000 qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/eng/checkstyle/checkstyle.xml000066400000000000000000000233121460332530100311640ustar00rootroot00000000000000 qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/eng/checkstyle/java.header000066400000000000000000000001401460332530100303710ustar00rootroot00000000000000// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/eng/settings.xml000066400000000000000000000007451460332530100265350ustar00rootroot00000000000000 repo-maven-apache-org-mirror https://repo-maven-apache-org.azurefd.net/maven2 central qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/eng/spotbugs/000077500000000000000000000000001460332530100260135ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/eng/spotbugs/spotbugs-excludes.xml000066400000000000000000000027131460332530100322200ustar00rootroot00000000000000 qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/eng/templates/000077500000000000000000000000001460332530100261435ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/eng/templates/1es-redirect.yml000066400000000000000000000023561460332530100311630ustar00rootroot00000000000000resources: repositories: - repository: 1ESPipelineTemplates type: git name: 1ESPipelineTemplates/1ESPipelineTemplates ref: refs/tags/release parameters: - name: stages type: stageList default: [] - name: UseOfficial type: boolean default: true extends: ${{ if and(parameters.UseOfficial, eq(variables['System.TeamProject'], 'internal')) }}: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates ${{ else }}: template: v1/1ES.Unofficial.PipelineTemplate.yml@1ESPipelineTemplates parameters: settings: skipBuildTagsForGitHubPullRequests: true sdl: sourceAnalysisPool: name: azsdk-pool-mms-win-2022-general image: azsdk-pool-mms-win-2022-1espt os: windows sourceRepositoriesToScan: exclude: - repository: azure-sdk-build-tools eslint: enabled: false justificationForDisabling: 'ESLint injected task has failures because it uses an old version of mkdirp. We should not fail for tools not controlled by the repo. See: https://dev.azure.com/azure-sdk/internal/_build/results?buildId=3499746' psscriptanalyzer: compiled: true break: true policy: M365 stages: ${{ parameters.stages }} qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/eng/templates/image.yml000066400000000000000000000011541460332530100277510ustar00rootroot00000000000000# Default pool image selection. Set as variable so we can override at pipeline level variables: - name: LINUXPOOL value: azsdk-pool-mms-ubuntu-2204-general - name: WINDOWSPOOL value: azsdk-pool-mms-win-2022-general - name: MACPOOL value: Azure Pipelines - name: LINUXVMIMAGE value: azsdk-pool-mms-ubuntu-2204-1espt - name: WINDOWSVMIMAGE value: azsdk-pool-mms-win-2022-1espt - name: MACVMIMAGE value: macos-13 # Values required for pool.os field in 1es pipeline templates - name: LINUXOS value: linux - name: WINDOWSOS value: windows - name: MACOS value: macOS qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/eng/templates/test-steps.yml000066400000000000000000000011501460332530100307760ustar00rootroot00000000000000# relies on variable settings set from calling previous build steps steps: - task: Maven@3 displayName: 'Run Tests' inputs: mavenPomFile: 'pom.xml' options: '$(DefaultOptions) $(ProfileFlag) --settings eng/settings.xml' mavenOptions: '-Xmx3072m $(LoggingOptions)' javaHomeOption: 'JDKVersion' jdkVersionOption: $(JavaVersion) jdkArchitectureOption: 'x64' publishJUnitResults: false goals: 'test' - task: PublishTestResults@2 condition: succeededOrFailed() inputs: mergeTestResults: true testRunTitle: '$(OSName) on Java $(JavaVersion)'qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/event-hubs.png000077500000000000000000000050541460332530100261710ustar00rootroot00000000000000PNG  IHDRæ$sRGBgAMA aPLTEe y<<} 궾K \tRNSOO pHYs\F\FCAIDATx^mA99]Z$8=L;wk7 `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@mr^qKL88888888888K5O׿o|=@6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @6 A!& 4l@ `BMh @ a&HžIENDB`qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/pom.xml000066400000000000000000000404541460332530100247200ustar00rootroot00000000000000 Extensions on Apache Proton-J library Extensions on Apache Proton-J library https://github.com/Azure/qpid-proton-j-extensions Microsoft Corporation http://microsoft.com 4.0.0 com.microsoft.azure qpid-proton-j-extensions 1.2.5 The MIT License (MIT) http://opensource.org/licenses/MIT repo microsoft Microsoft Corporation https://github.com/Azure/qpid-proton-j-extensions scm:git:https://github.com/Azure/qpid-proton-j-extensions.git HEAD GitHub https://github.com/Azure/qpid-proton-j-extensions/issues UTF-8 ${project.build.directory} 0.34.1 1.7.28 5.8.0-M1 4.13.1 3.0.0 3.1.2 3.8.1 3.1.2 3.1.1 3.1.2 3.9.1 3.0.1 4.2.0 3.0.0-M3 8.42 4.2.3 org.apache.maven.plugins maven-checkstyle-plugin ${maven-checkstyle-plugin.version} verify check com.github.spotbugs spotbugs-maven-plugin ${maven-spotbugs-plugin.version} verify check org.apache.maven.plugins maven-jar-plugin ${maven-jar-plugin.version} com.microsoft.azure.qpid.protonj.extensions ${packageOutputDirectory} org.apache.maven.plugins maven-source-plugin ${maven-source-plugin.version} attach-sources jar ${packageOutputDirectory} org.apache.maven.plugins maven-javadoc-plugin ${maven-javadoc-plugin.version} attach-javadocs jar org.apache.maven.plugins maven-compiler-plugin ${maven-compiler-plugin.version} true true -proc:none -Xlint:cast -Xlint:classfile -Xlint:deprecation -Xlint:dep-ann -Xlint:divzero -Xlint:empty -Xlint:fallthrough -Xlint:finally -Xlint:options -Xlint:overrides -Xlint:path -Xlint:rawtypes -Xlint:static -Xlint:try -Xlint:unchecked -Xlint:varargs org.apache.maven.plugins maven-checkstyle-plugin ${maven-checkstyle-plugin.version} com.puppycrawl.tools checkstyle ${checkstyle.version} eng/checkstyle/checkstyle.xml eng/checkstyle/checkstyle-suppressions.xml UTF-8 true true true true true org.apache.maven.plugins maven-javadoc-plugin ${maven-javadoc-plugin.version} 1.8 Extensions on Apache Proton-J Reference Documentation Extensions on Apache Proton-J Reference Documentation ${packageOutputDirectory} true true all https://docs.oracle.com/javase/8/docs/api/ false *.impl* org.apache.maven.plugins maven-surefire-plugin ${maven-surefire-plugin.version} alphabetical false 1 false com.github.spotbugs spotbugs-maven-plugin ${maven-spotbugs-plugin.version} com.github.spotbugs spotbugs ${spotbugs.version} max Low true eng/spotbugs/spotbugs-excludes.xml true true false org.apache.maven.plugins maven-site-plugin ${maven-site-plugin.version} org.apache.maven.plugins maven-project-info-reports-plugin ${maven-project-info-reports-plugin.version} org.apache.maven.plugins maven-javadoc-plugin ${maven-javadoc-plugin.version} com.github.spotbugs spotbugs-maven-plugin ${maven-spotbugs-plugin.version} target/site org.apache.qpid proton-j ${proton-j-version} org.slf4j slf4j-api ${slf4j-version} junit junit ${junit-version} test org.junit.jupiter junit-jupiter-params ${junit-params-version} test org.mockito mockito-core ${mockito-version} test org.slf4j slf4j-simple ${slf4j-version} test java8 [1.8,9) 8 8 org.apache.maven.plugins maven-compiler-plugin ${maven-compiler-plugin.version} 1.8 1.8 module-info.java org.apache.maven.plugins maven-javadoc-plugin ${maven-javadoc-plugin.version} module-info.java org.apache.maven.plugins maven-javadoc-plugin ${maven-javadoc-plugin.version} module-info.java java-lts true [11,) org.apache.maven.plugins maven-compiler-plugin ${maven-compiler-plugin.version} 11 -Xlint:-module -Xlint:-requires-transitive-automatic default-compile 11 base-compile compile 8 module-info.java org.apache.maven.plugins maven-surefire-plugin ${maven-surefire-plugin.version} qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/000077500000000000000000000000001460332530100241635ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/000077500000000000000000000000001460332530100251075ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/000077500000000000000000000000001460332530100260305ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/000077500000000000000000000000001460332530100266065ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/000077500000000000000000000000001460332530100306135ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/000077500000000000000000000000001460332530100317415ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/000077500000000000000000000000001460332530100332625ustar00rootroot00000000000000transport/000077500000000000000000000000001460332530100352375ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/protonproxy/000077500000000000000000000000001460332530100364205ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transportHttpStatusLine.java000066400000000000000000000062701460332530100422230ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxy// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy; import java.util.Locale; import java.util.Objects; /** * The first line in an HTTP 1.0/1.1 response. Consists of the HTTP protocol version, status code, and a reason phrase * for the HTTP response. * * @see RFC 2616 */ public final class HttpStatusLine { private final String httpVersion; private final int statusCode; private final String reason; /** * Creates a new instance of {@link HttpStatusLine}. * * @param protocolVersion The HTTP protocol version. For example, 1.0, 1.1. * @param statusCode A numeric status code for the HTTP response. * @param reason Textual phrase representing the HTTP status code. */ private HttpStatusLine(String protocolVersion, int statusCode, String reason) { this.httpVersion = Objects.requireNonNull(protocolVersion, "'httpVersion' cannot be null."); this.statusCode = statusCode; this.reason = Objects.requireNonNull(reason, "'reason' cannot be null."); } /** * Parses the provided {@code statusLine} into an HTTP status line. * * @param line Line to parse into an HTTP status line. * @return A new instance of {@link HttpStatusLine} representing the given {@code statusLine}. * @throws IllegalArgumentException if {@code line} is not the correct format of an HTTP status line. If it * does not have a protocol version, status code, or reason component. Or, if the HTTP protocol version * cannot be parsed. */ public static HttpStatusLine create(String line) { final String[] components = line.split(" ", 3); if (components.length != 3) { throw new IllegalArgumentException(String.format(Locale.ROOT, "HTTP status-line is invalid. Line: %s", line)); } final String[] protocol = components[0].split("/", 2); if (protocol.length != 2) { throw new IllegalArgumentException(String.format(Locale.ROOT, "Protocol is invalid, expected HTTP/{version}. Actual: %s", components[0])); } int statusCode; try { statusCode = Integer.parseInt(components[1]); } catch (NumberFormatException e) { throw new IllegalArgumentException(String.format(Locale.US, "HTTP Status code '%s' is not valid.", components[1]), e); } return new HttpStatusLine(protocol[1], statusCode, components[2]); } /** * Gets the HTTP protocol version. * * @return The HTTP protocol version. */ public String getProtocolVersion() { return this.httpVersion; } /** * Gets the HTTP status code. * * @return The HTTP status code. */ public int getStatusCode() { return this.statusCode; } /** * Gets the textual representation for the HTTP status code. * * @return The textual representation for the HTTP status code. */ public String getReason() { return this.reason; } } Proxy.java000066400000000000000000000020621460332530100404040ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxy// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy; import org.apache.qpid.proton.engine.Transport; import java.util.Map; /** * Represents a proxy. */ public interface Proxy { /** * States that the proxy can be in. */ enum ProxyState { PN_PROXY_NOT_STARTED, PN_PROXY_CONNECTING, PN_PROXY_CHALLENGE, PN_PROXY_CHALLENGE_RESPONDED, PN_PROXY_CONNECTED, PN_PROXY_FAILED } /** * Configures the AMQP broker {@code host} with the given proxy handler and transport. * * @param host AMQP broker. * @param headers Additional headers to add to the proxy request. * @param proxyHandler Handler for the proxy. * @param underlyingTransport Actual transport layer. */ void configure( String host, Map headers, ProxyHandler proxyHandler, Transport underlyingTransport); } ProxyAuthenticationType.java000066400000000000000000000010621460332530100441450ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxy// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy; /** * Supported methods of proxy authentication. */ public enum ProxyAuthenticationType { /** * Proxy requires no authentication. Service calls will fail if proxy demands authentication. */ NONE, /** * Authenticates against proxy with basic authentication scheme. */ BASIC, /** * Authenticates against proxy with digest access authentication. */ DIGEST, } ProxyChallengeProcessor.java000066400000000000000000000012241460332530100441060ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxy// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy; import java.util.Map; /** * Creates a set of headers to add to the HTTP request when responding to a proxy challenge. * * @see Proxy-Authenticate */ public interface ProxyChallengeProcessor { /** * Gets headers to add to the HTTP request when a proxy challenge is issued. * * @return Headers to add to the HTTP request when a proxy challenge is issued. */ Map getHeader(); } ProxyConfiguration.java000066400000000000000000000110071460332530100431330ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxy// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.PasswordAuthentication; import java.util.Arrays; import java.util.Objects; /** * Options for configuring the {@link Proxy}. * * @see Proxy */ public class ProxyConfiguration implements AutoCloseable { private static final Logger LOGGER = LoggerFactory.getLogger(ProxyConfiguration.class); private final java.net.Proxy proxyAddress; private final ProxyAuthenticationType authentication; private final PasswordAuthentication credentials; /** * Gets the system defaults for proxy configuration and authentication. */ public static final ProxyConfiguration SYSTEM_DEFAULTS = new ProxyConfiguration(); /** * Creates a proxy configuration that uses the system-wide proxy configuration and authenticator. */ private ProxyConfiguration() { this.authentication = null; this.credentials = null; this.proxyAddress = null; } /** * Creates a proxy configuration that uses the {@code proxyAddress} and authenticates with provided {@code * username}, {@code password} and {@code authentication}. * * @param authentication Authentication method to preemptively use with proxy. * @param proxyAddress Proxy to use. If {@code null} is passed in, then the system configured {@link * java.net.Proxy} is used. * @param username Optional. Username used to authenticate with proxy. If not specified, the system-wide * {@link java.net.Authenticator} is used to fetch credentials. * @param password Optional. Password used to authenticate with proxy. * @throws NullPointerException if {@code authentication} is {@code null}. * @throws IllegalArgumentException if {@code authentication} is {@link ProxyAuthenticationType#BASIC} or * {@link ProxyAuthenticationType#DIGEST} and {@code username} or {@code password} are {@code null}. */ public ProxyConfiguration(ProxyAuthenticationType authentication, java.net.Proxy proxyAddress, String username, String password) { Objects.requireNonNull(authentication); this.proxyAddress = proxyAddress; this.authentication = authentication; if (username != null && password != null) { this.credentials = new PasswordAuthentication(username, password.toCharArray()); } else { if (LOGGER.isInfoEnabled()) { LOGGER.info("username or password is null. Using system-wide authentication."); } this.credentials = null; } } /** * Gets the proxy address. * * @return The proxy address. Returns {@code null} if user creates proxy credentials with {@link * ProxyConfiguration#SYSTEM_DEFAULTS}. */ public java.net.Proxy proxyAddress() { return proxyAddress; } /** * Gets credentials to authenticate against proxy with. * * @return The credentials to authenticate against proxy with. Returns {@code null} if no credentials were set. This * occurs when user uses {@link ProxyConfiguration#SYSTEM_DEFAULTS}. */ public PasswordAuthentication credentials() { return credentials; } /** * Gets the proxy authentication type to use. * * @return The proxy authentication type to use. returns {@code null} if no authentication type was set. This occurs * when user uses {@link ProxyConfiguration#SYSTEM_DEFAULTS}. */ public ProxyAuthenticationType authentication() { return authentication; } /** * Gets whether the user has defined credentials. * * @return true if the user has defined the credentials to use, false otherwise. */ public boolean hasUserDefinedCredentials() { return credentials != null; } /** * Gets whether the proxy address has been configured. Used to determine whether to use system-defined or * user-defined proxy. * * @return true if the proxy url has been set, and false otherwise. */ public boolean isProxyAddressConfigured() { return proxyAddress != null && proxyAddress.address() != null; } @Override public void close() { // It is up to us to clear the password field when we are done using it. if (credentials != null) { Arrays.fill(credentials.getPassword(), '\0'); } } } ProxyHandler.java000066400000000000000000000021131460332530100416770ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxy// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy; import java.util.Map; /** * Creates and validates proxy requests and responses. */ public interface ProxyHandler { /** * Creates a CONNECT request to the provided {@code hostName} and adds {@code additionalHeaders} to the request. * * @param hostName Name of the host to connect to. * @param additionalHeaders Optional. Additional headers to add to the request. * @return A string representing the HTTP CONNECT request. */ String createProxyRequest(String hostName, Map additionalHeaders); /** * Verifies that {@code httpResponse} contains a successful CONNECT response. * * @param httpResponse HTTP response to validate for a successful CONNECT response. * @return {@code true} if the HTTP response is successful and correct, and {@code false} otherwise. * */ boolean validateProxyResponse(ProxyResponse httpResponse); } ProxyResponse.java000066400000000000000000000025351460332530100421300ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxy// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; /** * Represents an HTTP response from a proxy. */ public interface ProxyResponse { /** * Gets the headers for the HTTP response. * * @return The headers for the HTTP response. */ Map> getHeaders(); /** * Gets the HTTP status line. * * @return The HTTP status line. */ HttpStatusLine getStatus(); /** * Gets the HTTP response body. * * @return The HTTP response body. */ ByteBuffer getContents(); /** * Gets the HTTP response body as an error. * * @return If there is no HTTP response body, an empty string is returned. */ String getError(); /** * Gets whether or not the HTTP response is complete. An HTTP response is complete when the HTTP header and body are * received. * * @return {@code true} if the HTTP response is complete, and {@code false} otherwise. */ boolean isMissingContent(); /** * Adds contents to the body if it is missing content. * * @param contents Contents to add to the HTTP body. */ void addContent(ByteBuffer contents); } impl/000077500000000000000000000000001460332530100373615ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxyBasicProxyChallengeProcessorImpl.java000066400000000000000000000040351460332530100466360ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; import com.microsoft.azure.proton.transport.proxy.ProxyChallengeProcessor; import java.net.PasswordAuthentication; import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.Objects; import static java.nio.charset.StandardCharsets.UTF_8; /** * Implementation to support basic authentication for proxies. * * @see Proxy-Authenticate * @see Basic Authentication Scheme */ public class BasicProxyChallengeProcessorImpl implements ProxyChallengeProcessor { private final ProxyAuthenticator proxyAuthenticator; private final Map headers; private String host; BasicProxyChallengeProcessorImpl(String host, ProxyAuthenticator proxyAuthenticator) { Objects.requireNonNull(host); Objects.requireNonNull(proxyAuthenticator); this.host = host; headers = new HashMap<>(); this.proxyAuthenticator = proxyAuthenticator; } @Override public Map getHeader() { PasswordAuthentication passwordAuthentication = proxyAuthenticator.getPasswordAuthentication(Constants.BASIC_LOWERCASE, host); if (!ProxyAuthenticator.isPasswordAuthenticationHasValues(passwordAuthentication)) { return null; } final String proxyUserName = passwordAuthentication.getUserName(); final String proxyPassword = new String(passwordAuthentication.getPassword()); final String usernamePasswordPair = String.join(":", proxyUserName, proxyPassword); headers.put( Constants.PROXY_AUTHORIZATION, String.join(" ", Constants.BASIC, Base64.getEncoder().encodeToString(usernamePasswordPair.getBytes(UTF_8)))); return headers; } } Constants.java000066400000000000000000000020351460332530100422000ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; import java.util.Locale; /** * Package private constants. */ class Constants { static final String PROXY_AUTHENTICATE = "Proxy-Authenticate"; static final String PROXY_AUTHORIZATION = "Proxy-Authorization"; static final String DIGEST = "Digest"; static final String BASIC = "Basic"; static final String BASIC_LOWERCASE = Constants.BASIC.toLowerCase(Locale.ROOT); static final String DIGEST_LOWERCASE = Constants.DIGEST.toLowerCase(Locale.ROOT); static final String CONNECT = "CONNECT"; static final String PROXY_CONNECT_FAILED = "Proxy connect request failed with error: "; static final String PROXY_CONNECT_USER_ERROR = "User configuration error. Using non-matching proxy authentication."; static final int PROXY_HANDSHAKE_BUFFER_SIZE = 4 * 1024; // buffers used only for proxy-handshake static final String CONTENT_LENGTH = "Content-Length"; } DigestProxyChallengeProcessorImpl.java000066400000000000000000000163131460332530100470360ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; import com.microsoft.azure.proton.transport.proxy.ProxyChallengeProcessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.PasswordAuthentication; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Scanner; import java.util.concurrent.atomic.AtomicInteger; import static java.nio.charset.StandardCharsets.UTF_8; /** * Implementation to support digest authentication for proxies. * * @see Proxy-Authenticate * @see Authentication Schemes */ public class DigestProxyChallengeProcessorImpl implements ProxyChallengeProcessor { static final String DEFAULT_ALGORITHM = "MD5"; private static final String PROXY_AUTH_DIGEST = Constants.DIGEST; private static final char[] HEX_CODE = "0123456789ABCDEF".toCharArray(); private static final SecureRandom SECURE_RANDOM = new SecureRandom(); private final Logger logger = LoggerFactory.getLogger(DigestProxyChallengeProcessorImpl.class); private final AtomicInteger nonceCounter = new AtomicInteger(0); private final Map headers; private final ProxyAuthenticator proxyAuthenticator; private final String host; private final String challenge; DigestProxyChallengeProcessorImpl(String host, String challenge, ProxyAuthenticator authenticator) { Objects.requireNonNull(authenticator); this.host = host; this.challenge = challenge; headers = new HashMap<>(); proxyAuthenticator = authenticator; } @Override public Map getHeader() { final Scanner responseScanner = new Scanner(challenge); final Map challengeQuestionValues = new HashMap<>(); if (logger.isInfoEnabled()) { logger.info("Fetching header from:"); } while (responseScanner.hasNextLine()) { String line = responseScanner.nextLine(); if (logger.isInfoEnabled()) { logger.info(line); } if (line.contains(PROXY_AUTH_DIGEST)) { getChallengeQuestionHeaders(line, challengeQuestionValues); computeDigestAuthHeader(challengeQuestionValues, host, proxyAuthenticator.getPasswordAuthentication(Constants.DIGEST_LOWERCASE, host)); logger.info("Finished getting auth header."); break; } } if (logger.isInfoEnabled()) { logger.info("Headers added are:"); headers.forEach((key, value) -> { logger.info("{}: {}", key, value); }); } return headers; } private void getChallengeQuestionHeaders(String line, Map challengeQuestionValues) { final String context = line.substring(PROXY_AUTH_DIGEST.length()); final String[] headerValues = context.split(","); if (logger.isInfoEnabled()) { logger.info("Fetching challenge questions."); } for (String headerValue : headerValues) { if (headerValue.contains("=")) { String key = headerValue.substring(0, headerValue.indexOf("=")); String value = headerValue.substring(headerValue.indexOf("=") + 1); challengeQuestionValues.put(key.trim(), value.replaceAll("\"", "").trim()); } } if (logger.isInfoEnabled()) { logger.info("Challenge questions are: "); challengeQuestionValues.forEach((key, value) -> { logger.info("{}: {}", key, value); }); } } private void computeDigestAuthHeader(Map challengeQuestionValues, String uri, PasswordAuthentication passwordAuthentication) { if (logger.isInfoEnabled()) { logger.info("Computing password authentication..."); } if (!ProxyAuthenticator.isPasswordAuthenticationHasValues(passwordAuthentication)) { if (logger.isErrorEnabled()) { logger.error("Password authentication does not have values. Not computing authorization header."); } return; } final String proxyUserName = passwordAuthentication.getUserName(); final String proxyPassword = new String(passwordAuthentication.getPassword()); try { String digestValue; final String nonce = challengeQuestionValues.get("nonce"); final String realm = challengeQuestionValues.get("realm"); final String qop = challengeQuestionValues.get("qop"); final MessageDigest md5 = MessageDigest.getInstance(DEFAULT_ALGORITHM); final String a1 = printHexBinary(md5.digest(String.format("%s:%s:%s", proxyUserName, realm, proxyPassword).getBytes(UTF_8))); final String a2 = printHexBinary(md5.digest(String.format("%s:%s", Constants.CONNECT, uri).getBytes(UTF_8))); final byte[] cnonceBytes = new byte[16]; SECURE_RANDOM.nextBytes(cnonceBytes); final String cnonce = printHexBinary(cnonceBytes); String response; if (StringUtils.isNullOrEmpty(qop)) { response = printHexBinary(md5.digest(String.format("%s:%s:%s", a1, nonce, a2).getBytes(UTF_8))); digestValue = String.format("Digest username=\"%s\",realm=\"%s\",nonce=\"%s\",uri=\"%s\",cnonce=\"%s\",response=\"%s\"", proxyUserName, realm, nonce, uri, cnonce, response); } else { int nc = nonceCounter.incrementAndGet(); response = printHexBinary(md5.digest(String.format("%s:%s:%08X:%s:%s:%s", a1, nonce, nc, cnonce, qop, a2).getBytes(UTF_8))); digestValue = String.format( "Digest username=\"%s\",realm=\"%s\",nonce=\"%s\",uri=\"%s\",cnonce=\"%s\",nc=%08X,response=\"%s\",qop=\"%s\"", proxyUserName, realm, nonce, uri, cnonce, nc, response, qop); } headers.put(Constants.PROXY_AUTHORIZATION, digestValue); if (logger.isInfoEnabled()) { logger.info("Adding authorization header. {} '{}'", Constants.PROXY_AUTHORIZATION, digestValue); } } catch (NoSuchAlgorithmException ex) { if (logger.isErrorEnabled()) { logger.error("Error encountered when computing header.", ex); } throw new RuntimeException(ex); } } static String printHexBinary(byte[] data) { StringBuilder r = new StringBuilder(data.length * 2); for (byte b : data) { r.append(HEX_CODE[(b >> 4) & 0xF]); r.append(HEX_CODE[(b & 0xF)]); } return r.toString().toLowerCase(Locale.ROOT); } } ProxyAuthenticator.java000066400000000000000000000116001460332530100440760ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; import com.microsoft.azure.proton.transport.proxy.ProxyConfiguration; import java.net.Authenticator; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.PasswordAuthentication; import java.net.Proxy; import java.net.ProxySelector; import java.net.URI; import java.util.List; import java.util.Objects; /** * Responds to proxy challenge requests by providing authentication information. */ class ProxyAuthenticator { private static final String PROMPT = "Event Hubs client web socket proxy support"; private final ProxyConfiguration configuration; /** * Creates an authenticator that authenticates using system-configured authenticator and system-configured proxy * settings. */ ProxyAuthenticator() { this(ProxyConfiguration.SYSTEM_DEFAULTS); } /** * Creates an authenticator that responses to authentication requests with the provided configuration. * * @param configuration Proxy configuration to use for requests. * @throws NullPointerException if {@code configuration} is {@code null}. */ ProxyAuthenticator(ProxyConfiguration configuration) { Objects.requireNonNull(configuration); this.configuration = configuration; } /** * Gets the credentials to use for proxy authentication given the {@code scheme} and {@code host}. Finds credentials * to return in the following order *
    *
  1. If user specified username/password from {@link ProxyConfiguration}, return that.
  2. *
  3. If user specified proxy address, tries to fetch credentials using the system-wide authenticator.
  4. *
  5. Use system-wide proxy configuration and authenticator to fetch credentials.
  6. *
* * @param scheme The authentication scheme for the proxy. * @param host The proxy's URL that is requesting authentication. * @return The username and password to authenticate against proxy with. */ PasswordAuthentication getPasswordAuthentication(String scheme, String host) { if (configuration.hasUserDefinedCredentials()) { return configuration.credentials(); } // The user has specified the proxy address, so we'll use that address to try to fetch the system-wide // credentials for this. if (configuration.isProxyAddressConfigured()) { // We can cast this because Proxy ctor verifies that address is an instance of InetSocketAddress. InetSocketAddress address = (InetSocketAddress) configuration.proxyAddress().address(); return Authenticator.requestPasswordAuthentication( address.getHostName(), address.getAddress(), 0, null, PROMPT, scheme, null, Authenticator.RequestorType.PROXY); } // Otherwise, use the system-configured proxies and authenticator to fetch the credentials. ProxySelector proxySelector = ProxySelector.getDefault(); URI uri; List proxies = null; if (!StringUtils.isNullOrEmpty(host)) { uri = URI.create(host); proxies = proxySelector.select(uri); } InetAddress proxyAddr = null; java.net.Proxy.Type proxyType = null; if (isProxyAddressLegal(proxies)) { // will be only one element in the proxy list proxyAddr = ((InetSocketAddress) proxies.get(0).address()).getAddress(); proxyType = proxies.get(0).type(); } // It appears to be fine to pass in a null value for proxyAddr and proxyType (which maps to "scheme" argument in // the call to requestPasswordAuthentication). return Authenticator.requestPasswordAuthentication( "", proxyAddr, 0, proxyType == null ? "" : proxyType.name(), PROMPT, scheme, null, Authenticator.RequestorType.PROXY); } static boolean isPasswordAuthenticationHasValues(PasswordAuthentication passwordAuthentication) { if (passwordAuthentication == null) { return false; } final String username = passwordAuthentication.getUserName(); final char[] password = passwordAuthentication.getPassword(); return !StringUtils.isNullOrEmpty(username) && password != null && password.length > 0; } private static boolean isProxyAddressLegal(final List proxies) { return proxies != null && !proxies.isEmpty() && proxies.get(0).address() != null && proxies.get(0).address() instanceof InetSocketAddress; } } ProxyHandlerImpl.java000066400000000000000000000053071460332530100434720ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; import com.microsoft.azure.proton.transport.proxy.HttpStatusLine; import com.microsoft.azure.proton.transport.proxy.Proxy; import com.microsoft.azure.proton.transport.proxy.ProxyHandler; import com.microsoft.azure.proton.transport.proxy.ProxyResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; /** * Implementation class that handles connecting to the proxy. * * @see Proxy * @see ProxyHandler */ public class ProxyHandlerImpl implements ProxyHandler { /** * CONNECT request format string initiated by ProxyHandler. */ static final String CONNECT_REQUEST = "CONNECT %1$s HTTP/1.1%2$sHost: %1$s%2$sConnection: Keep-Alive%2$s"; static final String HEADER_FORMAT = "%s: %s"; static final String NEW_LINE = "\r\n"; private static final String CONNECTION_ESTABLISHED = "connection established"; private static final Set SUPPORTED_VERSIONS = Stream.of("1.1", "1.0").collect(Collectors.toSet()); private final Logger logger = LoggerFactory.getLogger(ProxyHandlerImpl.class); /** * {@inheritDoc} */ @Override public String createProxyRequest(String hostName, Map additionalHeaders) { final StringBuilder connectRequestBuilder = new StringBuilder(); connectRequestBuilder.append( String.format(Locale.ROOT, CONNECT_REQUEST, hostName, NEW_LINE)); if (additionalHeaders != null) { additionalHeaders.forEach((header, value) -> { connectRequestBuilder.append(String.format(HEADER_FORMAT, header, value)); connectRequestBuilder.append(NEW_LINE); }); } connectRequestBuilder.append(NEW_LINE); return connectRequestBuilder.toString(); } /** * {@inheritDoc} */ @Override public boolean validateProxyResponse(ProxyResponse response) { Objects.requireNonNull(response, "'response' cannot be null."); final HttpStatusLine status = response.getStatus(); if (status == null) { logger.error("Response does not contain a status line. {}", response); return false; } return status.getStatusCode() == 200 && SUPPORTED_VERSIONS.contains(status.getProtocolVersion()) && CONNECTION_ESTABLISHED.equalsIgnoreCase(status.getReason()); } } ProxyImpl.java000066400000000000000000000545071460332530100422020ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; import com.microsoft.azure.proton.transport.proxy.Proxy; import com.microsoft.azure.proton.transport.proxy.ProxyAuthenticationType; import com.microsoft.azure.proton.transport.proxy.ProxyChallengeProcessor; import com.microsoft.azure.proton.transport.proxy.ProxyConfiguration; import com.microsoft.azure.proton.transport.proxy.ProxyHandler; import com.microsoft.azure.proton.transport.proxy.ProxyResponse; import org.apache.qpid.proton.engine.Transport; import org.apache.qpid.proton.engine.TransportException; import org.apache.qpid.proton.engine.impl.TransportImpl; import org.apache.qpid.proton.engine.impl.TransportInput; import org.apache.qpid.proton.engine.impl.TransportLayer; import org.apache.qpid.proton.engine.impl.TransportOutput; import org.apache.qpid.proton.engine.impl.TransportWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import static com.microsoft.azure.proton.transport.proxy.ProxyAuthenticationType.BASIC; import static com.microsoft.azure.proton.transport.proxy.ProxyAuthenticationType.DIGEST; import static com.microsoft.azure.proton.transport.proxy.impl.Constants.PROXY_AUTHENTICATE; import static com.microsoft.azure.proton.transport.proxy.impl.Constants.PROXY_CONNECT_FAILED; import static com.microsoft.azure.proton.transport.proxy.impl.Constants.PROXY_CONNECT_USER_ERROR; import static com.microsoft.azure.proton.transport.proxy.impl.Constants.PROXY_HANDSHAKE_BUFFER_SIZE; import static org.apache.qpid.proton.engine.impl.ByteBufferUtils.newWriteableBuffer; /** * Implementation class that handles connecting to, the status of, and passing bytes through the web socket after the * proxy is created. * * @see Proxy * @see ProxyHandler */ public class ProxyImpl implements Proxy, TransportLayer { private static final Logger LOGGER = LoggerFactory.getLogger(ProxyImpl.class); private final ByteBuffer inputBuffer; private final ByteBuffer outputBuffer; private final ProxyConfiguration proxyConfiguration; private boolean tailClosed = false; private boolean headClosed = false; private String host = ""; private Map headers = null; private TransportImpl underlyingTransport; private ProxyHandler proxyHandler; private volatile boolean isProxyConfigured; private volatile ProxyState proxyState = ProxyState.PN_PROXY_NOT_STARTED; /** * Create proxy transport layer - which, after configuring using the {@link #configure(String, Map, ProxyHandler, * Transport)} API is ready for layering in qpid-proton-j transport layers, using {@link * org.apache.qpid.proton.engine.impl.TransportInternal#addTransportLayer(TransportLayer)} API. */ public ProxyImpl() { this(null); } /** * Create proxy transport layer - which, after configuring using the {@link #configure(String, Map, ProxyHandler, * Transport)} API is ready for layering in qpid-proton-j transport layers, using {@link * org.apache.qpid.proton.engine.impl.TransportInternal#addTransportLayer(TransportLayer)} API. * * @param configuration Proxy configuration to use. */ public ProxyImpl(ProxyConfiguration configuration) { inputBuffer = newWriteableBuffer(PROXY_HANDSHAKE_BUFFER_SIZE); outputBuffer = newWriteableBuffer(PROXY_HANDSHAKE_BUFFER_SIZE); isProxyConfigured = false; proxyConfiguration = configuration; } /** * Adds the proxy in the transport layer chain. * * @param input The input to the transport layer. * @param output The output from the transport layer. * * @return A transport layer containing the proxy. */ @Override public TransportWrapper wrap(TransportInput input, TransportOutput output) { return new ProxyTransportWrapper(input, output); } /** * Configures the AMQP broker {@code host} with the given proxy handler and transport. * * @param host AMQP broker. * @param headers Additional headers to add to the proxy request. * @param proxyHandler Handler for the proxy. * @param underlyingTransport Actual transport layer. */ @Override public void configure( String host, Map headers, ProxyHandler proxyHandler, Transport underlyingTransport) { this.host = host; this.headers = headers; this.proxyHandler = proxyHandler; this.underlyingTransport = (TransportImpl) underlyingTransport; isProxyConfigured = true; } /** * Gets headers for the proxy request. * * @return Headers for the proxy request. */ public Map getProxyRequestHeaders() { return this.headers; } protected ByteBuffer getInputBuffer() { return this.inputBuffer; } protected ByteBuffer getOutputBuffer() { return this.outputBuffer; } protected boolean getIsProxyConfigured() { return this.isProxyConfigured; } protected ProxyHandler getProxyHandler() { return this.proxyHandler; } protected Transport getUnderlyingTransport() { return this.underlyingTransport; } protected void writeProxyRequest() { outputBuffer.clear(); final String request = proxyHandler.createProxyRequest(host, headers); if (LOGGER.isInfoEnabled()) { LOGGER.info("Writing proxy request:{}{}", System.lineSeparator(), request); } //TODO (conniey): HTTP headers are encoded using StandardCharsets.ISO_8859_1. update proxyHandler.createProxyRequest to return bytes instead // of String because encoding is not UTF-16. https://stackoverflow.com/a/655948/4220757 // See https://datatracker.ietf.org/doc/html/rfc2616#section-3.7.1 outputBuffer.put(request.getBytes()); } protected boolean getIsHandshakeInProgress() { // if handshake is in progress // we do not engage the underlying transportInput/transportOutput. // Only when, ProxyState == Connected - then we can start engaging // next TransportLayers. // So, InProgress includes - proxyState = failed as well. // return true - from the point when proxyImpl.configure() is invoked to // proxyState transitions to Connected. // returns false - in all other cases return isProxyConfigured && proxyState != ProxyState.PN_PROXY_CONNECTED; } protected ProxyState getProxyState() { return this.proxyState; } private class ProxyTransportWrapper implements TransportWrapper { private final TransportInput underlyingInput; private final TransportOutput underlyingOutput; private final ByteBuffer head; // Represents a response from a CONNECT request. private final AtomicReference proxyResponse = new AtomicReference<>(); /** * Creates a transport wrapper that wraps the WebSocket transport input and output. */ ProxyTransportWrapper(TransportInput input, TransportOutput output) { underlyingInput = input; underlyingOutput = output; head = outputBuffer.asReadOnlyBuffer(); head.limit(0); } @Override public int capacity() { if (getIsHandshakeInProgress()) { if (tailClosed) { return Transport.END_OF_STREAM; } else { return inputBuffer.remaining(); } } else { return underlyingInput.capacity(); } } @Override public int position() { if (getIsHandshakeInProgress()) { if (tailClosed) { return Transport.END_OF_STREAM; } else { return inputBuffer.position(); } } else { return underlyingInput.position(); } } @Override public ByteBuffer tail() throws TransportException { if (getIsHandshakeInProgress()) { return inputBuffer; } else { return underlyingInput.tail(); } } @Override public void process() throws TransportException { if (!getIsHandshakeInProgress()) { underlyingInput.process(); return; } switch (proxyState) { case PN_PROXY_CONNECTING: inputBuffer.flip(); final ProxyResponse connectResponse = readProxyResponse(inputBuffer); if (connectResponse == null || connectResponse.isMissingContent()) { LOGGER.info("Request is missing content. Waiting for more bytes."); break; } //Clean up response to prepare for challenge proxyResponse.set(null); final boolean isSuccess = proxyHandler.validateProxyResponse(connectResponse); // When connecting to proxy, it does not challenge us for authentication. If the user has specified // a configuration, and it is not NONE, then we fail due to misconfiguration. if (isSuccess) { if (proxyConfiguration == null || proxyConfiguration.authentication() == ProxyAuthenticationType.NONE) { proxyState = ProxyState.PN_PROXY_CONNECTED; } else { if (LOGGER.isErrorEnabled()) { LOGGER.error("ProxyConfiguration mismatch. User configured: '{}', but authentication is not required", proxyConfiguration.authentication()); } closeTailProxyError(PROXY_CONNECT_USER_ERROR); } break; } final Map> headers = connectResponse.getHeaders(); final Set supportedTypes = getAuthenticationTypes(headers); // The proxy did not successfully connect, user has specified that they want a particular // authentication method, but it is not in list of supported authentication methods. if (proxyConfiguration != null && !supportedTypes.contains(proxyConfiguration.authentication())) { if (LOGGER.isErrorEnabled()) { LOGGER.error("Proxy authentication required. User configured: '{}', but supported proxy authentication methods are: {}", proxyConfiguration.authentication(), supportedTypes.stream().map(type -> type.toString()).collect(Collectors.joining(","))); } closeTailProxyError(PROXY_CONNECT_USER_ERROR + PROXY_CONNECT_FAILED + connectResponse); break; } final List challenges = headers.getOrDefault(PROXY_AUTHENTICATE, new ArrayList<>()); final ProxyChallengeProcessor processor = proxyConfiguration != null ? getChallengeProcessor(host, challenges, proxyConfiguration.authentication()) : getChallengeProcessor(host, challenges, supportedTypes); if (processor != null) { proxyState = ProxyState.PN_PROXY_CHALLENGE; ProxyImpl.this.headers = processor.getHeader(); } else { LOGGER.warn("Could not get ProxyChallengeProcessor for challenges."); closeTailProxyError(PROXY_CONNECT_FAILED + String.join(";", challenges)); } break; case PN_PROXY_CHALLENGE_RESPONDED: inputBuffer.flip(); final ProxyResponse challengeResponse = readProxyResponse(inputBuffer); if (challengeResponse == null || challengeResponse.isMissingContent()) { LOGGER.warn("Request is missing content. Waiting for more bytes."); break; } //Clean up proxyResponse.set(null); final boolean result = proxyHandler.validateProxyResponse(challengeResponse); if (result) { proxyState = ProxyState.PN_PROXY_CONNECTED; } else { closeTailProxyError(PROXY_CONNECT_FAILED + challengeResponse); } break; default: underlyingInput.process(); } } @Override public void close_tail() { tailClosed = true; if (getIsHandshakeInProgress()) { headClosed = true; } underlyingInput.close_tail(); } @Override public int pending() { if (!getIsHandshakeInProgress()) { return underlyingOutput.pending(); } switch (proxyState) { case PN_PROXY_NOT_STARTED: if (outputBuffer.position() == 0) { proxyState = ProxyState.PN_PROXY_CONNECTING; writeProxyRequest(); head.limit(outputBuffer.position()); if (headClosed) { proxyState = ProxyState.PN_PROXY_FAILED; return Transport.END_OF_STREAM; } else { return outputBuffer.position(); } } else { return outputBuffer.position(); } case PN_PROXY_CHALLENGE: if (outputBuffer.position() == 0) { proxyState = ProxyState.PN_PROXY_CHALLENGE_RESPONDED; writeProxyRequest(); head.limit(outputBuffer.position()); if (headClosed) { proxyState = ProxyState.PN_PROXY_FAILED; return Transport.END_OF_STREAM; } else { return outputBuffer.position(); } } else { return outputBuffer.position(); } case PN_PROXY_CHALLENGE_RESPONDED: case PN_PROXY_CONNECTING: if (headClosed && (outputBuffer.position() == 0)) { proxyState = ProxyState.PN_PROXY_FAILED; return Transport.END_OF_STREAM; } else { return outputBuffer.position(); } default: return Transport.END_OF_STREAM; } } /** * Gets the beginning of the output buffer. * * @return The beginning of the byte buffer. */ @Override public ByteBuffer head() { if (getIsHandshakeInProgress()) { switch (proxyState) { case PN_PROXY_CONNECTING: case PN_PROXY_CHALLENGE_RESPONDED: return head; default: return underlyingOutput.head(); } } else { return underlyingOutput.head(); } } /** * Removes the first number of bytes from the output buffer. * * @param bytes The number of bytes to remove from the output buffer. */ @Override public void pop(int bytes) { if (getIsHandshakeInProgress()) { switch (proxyState) { case PN_PROXY_CONNECTING: case PN_PROXY_CHALLENGE_RESPONDED: if (outputBuffer.position() != 0) { outputBuffer.flip(); outputBuffer.position(bytes); outputBuffer.compact(); head.position(0); head.limit(outputBuffer.position()); } else { underlyingOutput.pop(bytes); } break; default: underlyingOutput.pop(bytes); } } else { underlyingOutput.pop(bytes); } } /** * Closes the output transport. */ @Override public void close_head() { headClosed = true; underlyingOutput.close_head(); } /* * Gets the ProxyChallengeProcessor based on authentication types supported. Prefers DIGEST authentication if * supported over BASIC. Returns null if it cannot match any supported types. */ private ProxyChallengeProcessor getChallengeProcessor(String host, List challenges, Set authentication) { final ProxyAuthenticationType authType; if (authentication.contains(DIGEST)) { authType = DIGEST; } else if (authentication.contains(BASIC)) { authType = BASIC; } else { return null; } return getChallengeProcessor(host, challenges, authType); } private ProxyChallengeProcessor getChallengeProcessor(String host, List challenges, ProxyAuthenticationType authentication) { final ProxyAuthenticator authenticator = proxyConfiguration != null ? new ProxyAuthenticator(proxyConfiguration) : new ProxyAuthenticator(); switch (authentication) { case DIGEST: final Optional matching = challenges.stream() .filter(challenge -> challenge.toLowerCase(Locale.ROOT).startsWith(Constants.DIGEST_LOWERCASE)) .findFirst(); return matching.map(c -> new DigestProxyChallengeProcessorImpl(host, c, authenticator)) .orElse(null); case BASIC: return new BasicProxyChallengeProcessorImpl(host, authenticator); default: LOGGER.warn("Authentication type does not have a challenge processor: {}", authentication); return null; } } /** * Gets the supported authentication types based on the {@code headers}. * * @param headers HTTP proxy response headers from service call. * @return The supported proxy authentication methods. Or, an empty set if the value of {@code error} is {@code * null}, an empty string. Or, if it does not contain{@link Constants#PROXY_AUTHENTICATE} with * {@link Constants#BASIC_LOWERCASE} or {@link Constants#DIGEST_LOWERCASE}. */ private Set getAuthenticationTypes(Map> headers) { if (!headers.containsKey(PROXY_AUTHENTICATE)) { return Collections.emptySet(); } final Set supportedTypes = new HashSet<>(); final List authenticationTypes = headers.get(PROXY_AUTHENTICATE); for (String type : authenticationTypes) { final String lowercase = type.toLowerCase(Locale.ROOT); if (lowercase.startsWith(Constants.BASIC_LOWERCASE)) { supportedTypes.add(BASIC); } else if (lowercase.startsWith(Constants.DIGEST_LOWERCASE)) { supportedTypes.add(DIGEST); } else { LOGGER.warn("Did not understand this authentication type: {}", type); } } return supportedTypes; } private void closeTailProxyError(String errorMessage) { tailClosed = true; underlyingTransport.closed(new TransportException(errorMessage)); } /** * Given a byte buffer, reads a HTTP proxy response from it. * * @param buffer The buffer to read HTTP proxy response from. * @return The current HTTP proxy response. Or {@code null} if one could not be read from the buffer and there * is no current HTTP response. */ private ProxyResponse readProxyResponse(ByteBuffer buffer) { int size = buffer.remaining(); if (size <= 0) { LOGGER.warn("InputBuffer is empty. Not reading any contents from it. Returning current response."); return proxyResponse.get(); } ProxyResponse current = proxyResponse.get(); if (current == null) { proxyResponse.set(ProxyResponseImpl.create(buffer)); } else { current.addContent(buffer); } buffer.compact(); return proxyResponse.get(); } } } ProxyResponseImpl.java000066400000000000000000000163241460332530100437140ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; import com.microsoft.azure.proton.transport.proxy.HttpStatusLine; import com.microsoft.azure.proton.transport.proxy.ProxyResponse; import com.microsoft.azure.proton.transport.ws.WebSocket.WebSocketFrameReadState; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.AbstractMap; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import static com.microsoft.azure.proton.transport.proxy.impl.Constants.CONTENT_LENGTH; /** * Represents an HTTP response from a proxy. * * @see RFC2616 */ public final class ProxyResponseImpl implements ProxyResponse { private static final Logger LOGGER = LoggerFactory.getLogger(ProxyResponseImpl.class); private final HttpStatusLine status; private final Map> headers; private final ByteBuffer contents; private ProxyResponseImpl(HttpStatusLine status, Map> headers, ByteBuffer contents) { this.status = status; this.headers = headers; this.contents = contents; } /** * Create a proxy response from a given {@code buffer}. Assumes that the {@code buffer} has been flipped. * * @param buffer Buffer which could parse to a proxy response. * @return A new instance of {@link ProxyResponseImpl} representing the given buffer. * @throws IllegalArgumentException if {@code buffer} have no content to read. */ public static ProxyResponse create(ByteBuffer buffer) { // Because we've flipped the buffer, position = 0, and the limit = size of the content. int size = buffer.remaining(); if (size <= 0) { throw new IllegalArgumentException(String.format("Cannot create response with buffer have no content. " + "Limit: %s. Position: %s. Cap: %s", buffer.limit(), buffer.position(), buffer.capacity())); } final byte[] responseBytes = new byte[size]; buffer.get(responseBytes); final String response = new String(responseBytes, StandardCharsets.UTF_8); final String[] lines = response.split(StringUtils.NEW_LINE); final Map> headers = new HashMap<>(); WebSocketFrameReadState frameReadState = WebSocketFrameReadState.INIT_READ; HttpStatusLine statusLine = null; ByteBuffer contents = ByteBuffer.allocate(0); //Assume the full header message is in the first frame for (String line : lines) { switch (frameReadState) { case INIT_READ: statusLine = HttpStatusLine.create(line); frameReadState = WebSocketFrameReadState.CHUNK_READ; break; case CHUNK_READ: if (StringUtils.isNullOrEmpty(line)) { // Now that we're done reading all the headers, figure out the size of the HTTP body and // allocate an array of that size. int length = 0; if (headers.containsKey(CONTENT_LENGTH)) { final List contentLength = headers.get(CONTENT_LENGTH); length = Integer.parseInt(contentLength.get(0)); } boolean hasBody = length > 0; if (!hasBody) { LOGGER.info("There is no content in the response. Response: {}", response); return new ProxyResponseImpl(statusLine, headers, contents); } contents = ByteBuffer.allocate(length); frameReadState = WebSocketFrameReadState.HEADER_READ; } else { final Map.Entry header = parseHeader(line); final List value = headers.getOrDefault(header.getKey(), new ArrayList<>()); value.add(header.getValue()); headers.put(header.getKey(), value); } break; case HEADER_READ: if (contents.position() == 0) { frameReadState = WebSocketFrameReadState.CONTINUED_FRAME_READ; } contents.put(line.getBytes(StandardCharsets.UTF_8)); contents.mark(); break; case CONTINUED_FRAME_READ: contents.put(line.getBytes(StandardCharsets.UTF_8)); contents.mark(); break; default: LOGGER.error("Unknown state: {}. Response: {}", frameReadState, response); frameReadState = WebSocketFrameReadState.READ_ERROR; break; } } return new ProxyResponseImpl(statusLine, headers, contents); } private static Map.Entry parseHeader(String contents) { final String[] split = contents.split(":", 2); if (split.length != 2) { throw new IllegalStateException("Line is not a valid header. Contents: " + contents); } return new AbstractMap.SimpleEntry<>(split[0].trim(), split[1].trim()); } /** * {@inheritDoc} */ public HttpStatusLine getStatus() { return status; } /** * {@inheritDoc} */ public Map> getHeaders() { return headers; } /** * {@inheritDoc} */ public ByteBuffer getContents() { return contents.duplicate(); } /** * {@inheritDoc} */ public String getError() { final ByteBuffer readonly = contents.asReadOnlyBuffer(); readonly.flip(); return StandardCharsets.UTF_8.decode(readonly).toString(); } /** * Gets whether or not the HTTP response is complete. An HTTP response is complete when the HTTP header and body are * received. * * @return {@code true} if the HTTP response is complete, and {@code false} otherwise. */ public boolean isMissingContent() { return contents.hasRemaining(); } /** * Adds additional content to the HTTP response's body. Assumes that the {@code content} has been flipped. * * @param content Content to add to the body of the HTTP response. * @throws NullPointerException if {@code content} is {@code null}. * @throws IllegalArgumentException if {@code content} have no content to read. */ public void addContent(ByteBuffer content) { Objects.requireNonNull(content, "'content' cannot be null."); int size = content.remaining(); if (size <= 0) { throw new IllegalArgumentException("There was no content to add to current HTTP response."); } final byte[] responseBytes = new byte[content.remaining()]; content.get(responseBytes); this.contents.put(responseBytes); } } StringUtils.java000066400000000000000000000005601460332530100425140ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; /** * Utility classes for strings. */ class StringUtils { static final String NEW_LINE = "\r\n"; static boolean isNullOrEmpty(String string) { return string == null || string.isEmpty(); } } package-info.java000066400000000000000000000005121460332530100425460ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. /** * Package containg implementation for {@link com.microsoft.azure.proton.transport.proxy.ProxyHandler} and {@link * com.microsoft.azure.proton.transport.proxy.Proxy}. */ package com.microsoft.azure.proton.transport.proxy.impl; package-info.java000066400000000000000000000003351460332530100416100ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/proxy// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. /** * Package containing classes for connecting via an HTTP proxy. */ package com.microsoft.azure.proton.transport.proxy; ws/000077500000000000000000000000001460332530100356705ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transportWebSocket.java000066400000000000000000000072211460332530100404230ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/ws// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.ws; import java.nio.ByteBuffer; import java.util.Map; /** * Provides interface for WebSocket. */ public interface WebSocket { /** * States that the web socket can be in. */ enum WebSocketState { /** * WebSocket. */ PN_WS_NOT_STARTED, /** * Pending connection. */ PN_WS_CONNECTING, /** * Connected and messages flow. */ PN_WS_CONNECTED_FLOW, /** * Connected and ping-pong. */ PN_WS_CONNECTED_PONG, /** * Connected and received a close. */ PN_WS_CONNECTED_CLOSING, /** * Connection closed. */ PN_WS_CLOSED, /** * Connection failed. */ PN_WS_FAILED } /** * States for the web socket when reading from the underlying buffer. */ enum WebSocketFrameReadState { /** * The initial read. */ INIT_READ, /** * Reading chunks of bytes until a full header is read. */ CHUNK_READ, /** * Continue reading bytes until correct number of bytes are read. */ CONTINUED_FRAME_READ, /** * Full header has been read. */ HEADER_READ, /** * An error reading. */ READ_ERROR } /** * Configure WebSocket connection. * * @param host the hots name * @param path the resource path * @param query the query * @param port the port * @param protocol the base protocol * @param additionalHeaders the Map of additional headers * @param webSocketHandler the web socket handler */ void configure( String host, String path, String query, int port, String protocol, Map additionalHeaders, WebSocketHandler webSocketHandler); /** * Add WebSocket frame to send the given buffer. * * @param srcBuffer the source buffer * @param dstBuffer the destination buffer */ void wrapBuffer(ByteBuffer srcBuffer, ByteBuffer dstBuffer); /** * Remove WebSocket frame from the given buffer. * * @param buffer the buffer to unwrap * @return The payload of the given WebSocket frame. */ WebSocketHandler.WebsocketTuple unwrapBuffer(ByteBuffer buffer); /** * Access the handler for WebSocket functions. * * @return The WebSocket handler class. */ WebSocketHandler getWebSocketHandler(); /** * Access the current state of the layer. * * @return The state of the WebSocket layer. */ WebSocketState getState(); /** * Access if WebSocket enabled . * * @return True if WebSocket enabled otherwise false. */ Boolean getEnabled(); /** * Access the output buffer (read only). * * @return The current output buffer. */ ByteBuffer getOutputBuffer(); /** * Access the input buffer (read only). * * @return The current input buffer. */ ByteBuffer getInputBuffer(); /** * Access the ping buffer (read only). * * @return The ping input buffer. */ ByteBuffer getPingBuffer(); /** * Access the web socket input buffer (read only). * * @return The wsInputBuffer input buffer. */ ByteBuffer getWsInputBuffer(); } WebSocketHandler.java000066400000000000000000000077461460332530100417350ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/ws// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.ws; import java.nio.ByteBuffer; import java.util.Map; /** * Handles states for the web socket. */ public interface WebSocketHandler { /** * States when parsing a frame. */ enum WebSocketMessageType { WEB_SOCKET_MESSAGE_TYPE_UNKNOWN, WEB_SOCKET_MESSAGE_TYPE_CHUNK, WEB_SOCKET_MESSAGE_TYPE_HEADER_CHUNK, WEB_SOCKET_MESSAGE_TYPE_AMQP, WEB_SOCKET_MESSAGE_TYPE_PING, WEB_SOCKET_MESSAGE_TYPE_CLOSE, } /** * Creates an HTTP request to upgrade to use web sockets. * * @param hostName Name of the host. * @param webSocketPath Path for the websocket. * @param webSocketQuery Query for the web socket. * @param webSocketPort Port for web socket. * @param webSocketProtocol Protocol to use for web sockets. * @param additionalHeaders Any additional headers to add to the HTTP upgrade request. * @return Represents the HTTP request. */ String createUpgradeRequest( String hostName, String webSocketPath, String webSocketQuery, int webSocketPort, String webSocketProtocol, Map additionalHeaders); /** * Validates the response. * * @param buffer ByteBuffer to read from. * @return True if the response is valid, otherwise, false. */ Boolean validateUpgradeReply(ByteBuffer buffer); /** * Wraps the source buffer with additional contents from the web socket. * * @param srcBuffer Source buffer to wrap input. * @param dstBuffer Output buffer that bytes are written to. */ void wrapBuffer(ByteBuffer srcBuffer, ByteBuffer dstBuffer); /** * Unwraps the layer from the buffer. * * @param srcBuffer The source buffer. * @return The current chunk for the web socket when reading. */ WebsocketTuple unwrapBuffer(ByteBuffer srcBuffer); /** * Creates the pong for the "keep-alive", heart beat, network status probing when connecting in a web socket. * * @param srcBuffer The source buffer to read from. * @param dstBuffer The destination buffer with the pong. * @see Ping and pong */ void createPong(ByteBuffer srcBuffer, ByteBuffer dstBuffer); /** * Gets the size of the header. * * @param payloadSize Size of the payload. * @return The size of the header. */ int calculateHeaderSize(int payloadSize); /** * Represents the web socket message and its type. */ class WebsocketTuple { private long length; private WebSocketMessageType type; /** * Creates an instance with the given length and type. * * @param length Length of the segment. * @param type Type of the socket message. */ public WebsocketTuple(long length, WebSocketMessageType type) { this.length = length; this.type = type; } /** * Sets the length of the message. * * @param length The length of the message. */ public void setLength(long length) { this.length = length; } /** * Sets the message type. * * @param type The message type. */ public void setType(WebSocketMessageType type) { this.type = type; } /** * Gets the length of the message. * * @return The length of the message. */ public long getLength() { return this.length; } /** * Gets the type of the message. * * @return The type of the message. */ public WebSocketMessageType getType() { return this.type; } } } WebSocketHeader.java000066400000000000000000000103001460332530100415240ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/ws// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.ws; /** * Represents a web socket header. * * +---------------------------------------------------------------+ * 0 1 2 3 | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | * +-+-+-+-+-------+-+-------------+-------------------------------+ * |F|R|R|R| opcode|M| Payload len | Extended payload length | * |I|S|S|S| (4) |A| (7) | (16/64) | * |N|V|V|V| |S| | (if payload len==126/127) | * | |1|2|3| |K| | | * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + * | Extended payload length continued, if payload len == 127 | * + - - - - - - - - - - - - - - - +-------------------------------+ * | | Masking-key, if MASK set to 1 | * +-------------------------------+-------------------------------+ * | Masking-key (continued) | Payload Data | * +-------------------------------- - - - - - - - - - - - - - - - + * : Payload Data continued ... : * + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * | Payload Data continued ... | * +---------------------------------------------------------------+ * * @see RFC6455: Base Framing Protocol */ public interface WebSocketHeader { /** * Size of header if payload size is {@literal < 125} and there is no masking key. */ byte MIN_HEADER_LENGTH = 2; /** * Size of header if payload size is {@literal < 125} and there is a masking key. */ byte MIN_HEADER_LENGTH_MASKED = 6; /** * Size of the header if the payload size is represented in 7 + 16 bits and there is no masking key. */ byte MED_HEADER_LENGTH_NOMASK = 4; /** * Size of the header if the payload size is represented in 7 + 16 bits and there is a masking key. */ byte MED_HEADER_LENGTH_MASKED = 8; /** * Size of the header if the payload size is represented in 7 + 64 bits and there is no masking key. */ byte MAX_HEADER_LENGTH_NOMASK = 10; /** * Size of the header if the payload size is represented in 7 + 64 bits and there is a masking key. */ byte MAX_HEADER_LENGTH_MASKED = 14; // Masks /** * FIN denotes that this is the final fragment in a message. */ byte FINBIT_MASK = (byte) 0x80; /** * Denotes whether the "Payload data" is masked. If set to 1, a masking key is present in 'masking-key' and this * will be used to unmask the "Payload data". */ byte OPCODE_MASK = (byte) 0x0F; /** * Denotes a continuation frame. */ byte OPCODE_CONTINUATION = (byte) 0x00; /** * Denotes a binary frame. */ byte OPCODE_BINARY = (byte) 0x02; /** * Denotes a connection close. */ byte OPCODE_CLOSE = (byte) 0x08; /** * Denotes a ping. */ byte OPCODE_PING = (byte) 0x09; /** * Denotes a pong. */ byte OPCODE_PONG = (byte) 0x0A; /** * Mask to get value of {@link #OPCODE_MASK}. */ byte MASKBIT_MASK = (byte) 0x80; /** * Mask to get value of payload length. */ byte PAYLOAD_MASK = (byte) 0x7F; /** * Determines whether it is the final frame for the message. */ byte FINAL_OPCODE_BINARY = FINBIT_MASK | OPCODE_BINARY; /** * Maximum size (125) in bytes for the payload when using 7 bits to represent the size. */ byte PAYLOAD_SHORT_MAX = 0x7D; /** * Maximum size in bytes for the payload when using 7 + 16 bits to represent the size. */ int PAYLOAD_MEDIUM_MAX = 0xFFFF; /** * Maximum size in bytes for the payload when using 7 + 64 bits to represent the size. */ int PAYLOAD_LARGE_MAX = 0x7FFFFFFF; /** * Size is 126. */ byte PAYLOAD_EXTENDED_16 = 0x7E; /** * Size is 127. */ byte PAYLOAD_EXTENDED_64 = 0x7F; } impl/000077500000000000000000000000001460332530100366315ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/wsBase64.java000066400000000000000000000326331460332530100405270ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/ws/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.ws.impl; import java.nio.charset.StandardCharsets; /** * Class to encode to base 64. */ public final class Base64 { private static final byte BYTE_START_UPPERCASE = 'A'; private static final byte BYTE_END_UPPERCASE = 'Z'; private static final byte BYTE_START_LOWERCASE = 'a'; private static final byte BYTE_END_LOWERCASE = 'z'; private static final byte BYTE_START_NUMBER = '0'; private static final byte BYTE_END_NUMBER = '9'; private static final byte BYTE_PLUS = '+'; private static final byte BYTE_SLASH = '/'; private static final int BASE64_END_UPPERCASE = 26; private static final int BASE64_END_LOWERCASE = 52; private static final int BASE64_END_NUMBER = 62; private static final int BASE64_PLUS = 62; private static final int BASE64_SLASH = 63; private static final byte BASE64_PAD = '='; private static final int HALF_NIBBLE = 2; private static final int ONE_NIBBLE = 4; private static final int ONE_AND_HALF_NIBBLE = 6; private static final int ONE_BYTE = 8; private static final int TWO_BYTES = 16; private static final int THREE_BYTES = 24; private static final int ISOLATE_BYTE = 0xFF; private static final int ISOLATE_BASE64 = 0x3F; private static final int ISOLATE_LSB_BASE64 = 0x0F; private static final int ISOLATE_MSB_BASE64 = 0x03; private static final int BYTE_GROUP_SIZE = 3; private static final int BASE64_GROUP_SIZE = 4; private static final int[] BASE64D16_CONVERSION_TABLE = { ((int) 'A' + ((int) 'E' << ONE_BYTE) + ((int) 'I' << TWO_BYTES) + ((int) 'M' << THREE_BYTES)), ((int) 'Q' + ((int) 'U' << ONE_BYTE) + ((int) 'Y' << TWO_BYTES) + ((int) 'c' << THREE_BYTES)), ((int) 'g' + ((int) 'k' << ONE_BYTE) + ((int) 'o' << TWO_BYTES) + ((int) 's' << THREE_BYTES)), ((int) 'w' + ((int) '0' << ONE_BYTE) + ((int) '4' << TWO_BYTES) + ((int) '8' << THREE_BYTES)) }; private static final int BASE64D8_CONVERSION_TABLE = ((int) 'A' + ((int) 'Q' << ONE_BYTE) + ((int) 'g' << TWO_BYTES) + ((int) 'w' << THREE_BYTES)); private static byte extractBase64FromInteger(final int integerValue, final int bytePosition) { return (byte) ((integerValue >> (bytePosition << 3)) & ISOLATE_BYTE); } private static byte base64ToByte(final byte base64Value) { if (base64Value < BASE64_END_UPPERCASE) { return (byte) (BYTE_START_UPPERCASE + base64Value); } if (base64Value < BASE64_END_LOWERCASE) { return (byte) (BYTE_START_LOWERCASE + (base64Value - BASE64_END_UPPERCASE)); } if (base64Value < BASE64_END_NUMBER) { return (byte) (BYTE_START_NUMBER + (base64Value - BASE64_END_LOWERCASE)); } if (base64Value == BASE64_END_NUMBER) { return BYTE_PLUS; } return BYTE_SLASH; } private static byte base64d16ToByte(final byte base64d16Value) { return extractBase64FromInteger(BASE64D16_CONVERSION_TABLE[base64d16Value >> HALF_NIBBLE], (base64d16Value & ISOLATE_MSB_BASE64)); } private static byte base64d8ToByte(final byte base64d8Value) { return extractBase64FromInteger(BASE64D8_CONVERSION_TABLE, base64d8Value); } private static byte byteToBase64(final byte byteValue) throws IllegalArgumentException { if ((byteValue >= BYTE_START_UPPERCASE) && (byteValue <= BYTE_END_UPPERCASE)) { return (byte) (byteValue - BYTE_START_UPPERCASE); } if ((byteValue >= BYTE_START_LOWERCASE) && (byteValue <= BYTE_END_LOWERCASE)) { return (byte) ((BYTE_END_UPPERCASE - BYTE_START_UPPERCASE) + 1 + (byteValue - BYTE_START_LOWERCASE)); } if ((byteValue >= BYTE_START_NUMBER) && (byteValue <= BYTE_END_NUMBER)) { return (byte) ((BYTE_END_UPPERCASE - BYTE_START_UPPERCASE) + 1 + (BYTE_END_LOWERCASE - BYTE_START_LOWERCASE) + 1 + (byteValue - BYTE_START_NUMBER)); } if (byteValue == BYTE_PLUS) { return BASE64_PLUS; } if (byteValue == BYTE_SLASH) { return BASE64_SLASH; } throw new IllegalArgumentException("provided byte value out of base64 range"); } private static int numberOfValidBase64BytesWithoutPad(final byte[] bytesToEncode) throws IllegalArgumentException { int validLength = bytesToEncode.length; if (bytesToEncode[validLength - 1] == BASE64_PAD) { validLength--; } if (bytesToEncode[validLength - 1] == BASE64_PAD) { validLength--; } return validLength; } private static int base64EstimatedLength(final byte[] base64sToDecode) { int estimatedLength; if (base64sToDecode.length == 0) { return 0; } estimatedLength = base64sToDecode.length / BASE64_GROUP_SIZE * BYTE_GROUP_SIZE; if (base64sToDecode[base64sToDecode.length - 1] == BASE64_PAD) { if (base64sToDecode[base64sToDecode.length - 2] == BASE64_PAD) { estimatedLength--; } estimatedLength--; } return estimatedLength; } /** * Convert a array of base64 encoded byte in a array of bytes, returning the bytes * original values. * RFC 2045. * *

Base64 only uses 6 bits, so fits each set of 4 base64 in 3 bytes * Base64 | c1 | c2 | c3 | c4 | * |7 6 5 4 3 2 1 0:7 6 5 4 3 2 1 0:7 6 5 4 3 2 1 0| * Byte | b1 | b2 | b3 | * * @param base64Values is an array of base64 encoded values * @return an array of bytes with the original values * @throws IllegalArgumentException if the provided base64 values are null, or do not fits the required length */ public static byte[] decodeBase64Local(final byte[] base64Values) throws IllegalArgumentException { /* Codes_SRS_BASE64_21_002: [If the `base64Values` is null, the decodeBase64Local shall throw IllegalArgumentException.] */ if (base64Values == null) { throw new IllegalArgumentException("null or empty base64Values"); } /* Codes_SRS_BASE64_21_003: [If the `base64Values` is empty, the decodeBase64Local shall return a empty byte array.] */ if (base64Values.length == 0) { return new byte[0]; } /* Codes_SRS_BASE64_21_004: [If the `base64Values` length is not multiple of 4, the decodeBase64Local shall throw IllegalArgumentException.] */ if ((base64Values.length % BASE64_GROUP_SIZE) != 0) { throw new IllegalArgumentException("invalid base64Values length"); } /* Codes_SRS_BASE64_21_001: [The decodeBase64Local shall decode the provided `base64Values` in a byte array using the Base64 format define in the RFC2045.] */ int numberOfEncodedBytes = numberOfValidBase64BytesWithoutPad(base64Values); int indexOfFirstEncodedByte = 0; int decodedIndex = 0; byte[] decodedResult = new byte[base64EstimatedLength(base64Values)]; while (numberOfEncodedBytes >= BASE64_GROUP_SIZE) { byte c1 = byteToBase64(base64Values[indexOfFirstEncodedByte++]); byte c2 = byteToBase64(base64Values[indexOfFirstEncodedByte++]); byte c3 = byteToBase64(base64Values[indexOfFirstEncodedByte++]); byte c4 = byteToBase64(base64Values[indexOfFirstEncodedByte++]); decodedResult[decodedIndex++] = (byte) ((c1 << HALF_NIBBLE) | (c2 >> ONE_NIBBLE)); decodedResult[decodedIndex++] = (byte) ((c2 << ONE_NIBBLE) | (c3 >> HALF_NIBBLE)); decodedResult[decodedIndex++] = (byte) ((c3 << ONE_AND_HALF_NIBBLE) | c4); numberOfEncodedBytes -= BASE64_GROUP_SIZE; } if (numberOfEncodedBytes == 3) { byte c1 = byteToBase64(base64Values[indexOfFirstEncodedByte++]); byte c2 = byteToBase64(base64Values[indexOfFirstEncodedByte++]); byte c3 = byteToBase64(base64Values[indexOfFirstEncodedByte]); decodedResult[decodedIndex++] = (byte) ((c1 << HALF_NIBBLE) | (c2 >> ONE_NIBBLE)); decodedResult[decodedIndex] = (byte) ((c2 << ONE_NIBBLE) | (c3 >> HALF_NIBBLE)); } if (numberOfEncodedBytes == 2) { byte c1 = byteToBase64(base64Values[indexOfFirstEncodedByte++]); byte c2 = byteToBase64(base64Values[indexOfFirstEncodedByte]); decodedResult[decodedIndex] = (byte) ((c1 << HALF_NIBBLE) | (c2 >> ONE_NIBBLE)); } return decodedResult; } /** * Convert a array of bytes in a array of MIME Base64 values. * RFC 2045. * * @param dataValues is an array of bytes with the original values * @return an array of base64 encoded values * @throws IllegalArgumentException if the provided base64 values are null, or do not fits the required length */ public static byte[] encodeBase64Local(byte[] dataValues) throws IllegalArgumentException { /* Codes_SRS_BASE64_21_006: [If the `dataValues` is null, the encodeBase64Local shall throw IllegalArgumentException.] */ if (dataValues == null) { throw new IllegalArgumentException("null or empty dataValues"); } /* Codes_SRS_BASE64_21_007: [If the `dataValues` is empty, the encodeBase64Local shall return a empty byte array.] */ if (dataValues.length == 0) { return new byte[0]; } /* Codes_SRS_BASE64_21_005: [The encodeBase64Local shall encoded the provided `dataValues` in a byte array using the Base64 format define in the RFC2045.] */ return encodeBase64Internal(dataValues); } /** * Convert a array of bytes in a array of MIME Base64 values. * RFC 2045. * * @param dataValues is an array of bytes with the original values * @return a string with the base64 encoded values * @throws IllegalArgumentException if the provided base64 values are null, or do not fits the required length */ public static String encodeBase64StringLocal(byte[] dataValues) throws IllegalArgumentException { /* Codes_SRS_BASE64_21_009: [If the `dataValues` is null, the encodeBase64StringLocal shall throw IllegalArgumentException.] */ if (dataValues == null) { throw new IllegalArgumentException("null or empty dataValues"); } /* Codes_SRS_BASE64_21_010: [If the `dataValues` is empty, the encodeBase64StringLocal shall return a empty string.] */ if (dataValues.length == 0) { return ""; } /* Codes_SRS_BASE64_21_008: [The encodeBase64StringLocal shall encoded the provided `dataValues` in a string using the Base64 format define in the RFC2045.] */ return new String(encodeBase64Internal(dataValues), StandardCharsets.US_ASCII); } private static byte[] encodeBase64Internal(byte[] dataValues) throws IllegalArgumentException { int encodedLength = (((dataValues.length - 1) / BYTE_GROUP_SIZE) + 1) * BASE64_GROUP_SIZE; int destinationPosition = 0; int currentPosition = 0; byte[] encodedResult = new byte[encodedLength]; while ((dataValues.length - currentPosition) >= BYTE_GROUP_SIZE) { encodedResult[destinationPosition++] = base64ToByte((byte) ((dataValues[currentPosition] >> HALF_NIBBLE) & ISOLATE_BASE64)); encodedResult[destinationPosition++] = base64ToByte((byte) (((dataValues[currentPosition] << ONE_NIBBLE) & ISOLATE_BASE64) | ((dataValues[currentPosition + 1] >> ONE_NIBBLE) & ISOLATE_LSB_BASE64))); encodedResult[destinationPosition++] = base64ToByte((byte) (((dataValues[currentPosition + 1] << HALF_NIBBLE) & ISOLATE_BASE64) | ((dataValues[currentPosition + 2] >> ONE_AND_HALF_NIBBLE) & ISOLATE_MSB_BASE64))); encodedResult[destinationPosition++] = base64ToByte((byte) (dataValues[currentPosition + 2] & ISOLATE_BASE64)); currentPosition += BYTE_GROUP_SIZE; } if ((dataValues.length - currentPosition) == 2) { encodedResult[destinationPosition++] = base64ToByte((byte) ((dataValues[currentPosition] >> HALF_NIBBLE) & ISOLATE_BASE64)); encodedResult[destinationPosition++] = base64ToByte((byte) (((dataValues[currentPosition] << ONE_NIBBLE) & ISOLATE_BASE64) | ((dataValues[currentPosition + 1] >> ONE_NIBBLE) & ISOLATE_LSB_BASE64))); encodedResult[destinationPosition++] = base64d16ToByte((byte) (dataValues[currentPosition + 1] & ISOLATE_LSB_BASE64)); encodedResult[destinationPosition] = BASE64_PAD; } if ((dataValues.length - currentPosition) == 1) { encodedResult[destinationPosition++] = base64ToByte((byte) ((dataValues[currentPosition] >> HALF_NIBBLE) & ISOLATE_BASE64)); encodedResult[destinationPosition++] = base64d8ToByte((byte) (dataValues[currentPosition] & ISOLATE_MSB_BASE64)); encodedResult[destinationPosition++] = BASE64_PAD; encodedResult[destinationPosition] = BASE64_PAD; } return encodedResult; } } Utils.java000066400000000000000000000011261460332530100405740ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/ws/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.ws.impl; import java.security.SecureRandom; /** * Utility methods. */ final class Utils { private static final SecureRandom SECURE_RANDOM = new SecureRandom(); /** * Gets an instance of secure random. * * @return An instance of secure random. */ static SecureRandom getSecureRandom() { return SECURE_RANDOM; } /** * So an instance of class cannot be created. */ private Utils() { } } WebSocketHandlerImpl.java000066400000000000000000000241501460332530100435040ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/ws/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.ws.impl; import com.microsoft.azure.proton.transport.ws.WebSocketHandler; import com.microsoft.azure.proton.transport.ws.WebSocketHeader; import java.io.ByteArrayOutputStream; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.Map; /** * Implementation for {@link WebSocketHandler}. */ public class WebSocketHandlerImpl implements WebSocketHandler { private WebSocketUpgrade webSocketUpgrade = null; @Override public String createUpgradeRequest( String hostName, String webSocketPath, String webSocketQuery, int webSocketPort, String webSocketProtocol, Map additionalHeaders) { webSocketUpgrade = createWebSocketUpgrade(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders); return webSocketUpgrade.createUpgradeRequest(); } @Override public void createPong(ByteBuffer ping, ByteBuffer pong) { if ((ping == null) || (pong == null)) { throw new IllegalArgumentException("input parameter cannot be null"); } if (ping.capacity() > pong.capacity()) { throw new IllegalArgumentException("insufficient output buffer size"); } if (ping.remaining() > 0) { byte[] buffer = ping.array(); buffer[0] = WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_PONG; pong.clear(); pong.put(buffer); } else { pong.clear(); pong.limit(0); } } @Override public Boolean validateUpgradeReply(ByteBuffer buffer) { Boolean retVal = false; if (webSocketUpgrade != null) { int size = buffer.remaining(); if (size > 0) { byte[] data = new byte[buffer.remaining()]; buffer.get(data); retVal = webSocketUpgrade.validateUpgradeReply(data); if (retVal) { webSocketUpgrade = null; } } } return retVal; } @Override public void wrapBuffer(ByteBuffer srcBuffer, ByteBuffer dstBuffer) { if ((srcBuffer == null) || (dstBuffer == null)) { throw new IllegalArgumentException("input parameter is null"); } if (srcBuffer.remaining() > 0) { // We always send masked data // RFC: "client MUST mask all frames that it sends to the server" final byte[] maskingKey = createRandomMaskingKey(); // Get data length final int dataLength = srcBuffer.remaining(); // Auto growing buffer for the WS frame, initialized to minimum size ByteArrayOutputStream webSocketFrame = new ByteArrayOutputStream(WebSocketHeader.MIN_HEADER_LENGTH_MASKED + dataLength); // Create the first byte // We always send final WebSocket frame // We always send binary message (AMQP) byte firstByte = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); webSocketFrame.write(firstByte); // Create the second byte // RFC: "client MUST mask all frames that it sends to the server" byte secondByte = WebSocketHeader.MASKBIT_MASK; // RFC: The length of the "Payload data", in bytes: if 0-125, that is the payload length. if (dataLength <= WebSocketHeader.PAYLOAD_SHORT_MAX) { secondByte = (byte) (secondByte | dataLength); webSocketFrame.write(secondByte); } else if (dataLength <= WebSocketHeader.PAYLOAD_MEDIUM_MAX) { // RFC: If 126, the following 2 bytes interpreted as a 16-bit unsigned integer are the payload length // Create payload byte secondByte = (byte) (secondByte | WebSocketHeader.PAYLOAD_EXTENDED_16); webSocketFrame.write(secondByte); // Create extended length bytes webSocketFrame.write((byte) (dataLength >>> 8)); webSocketFrame.write((byte) (dataLength)); } else { // RFC: If 127, the following 8 bytes interpreted as a 64-bit unsigned integer // (the most significant bit MUST be 0) are the payload length. // No need for "else if" because if it is longer than what 8 byte length can hold... all bets are off anyway secondByte = (byte) (secondByte | WebSocketHeader.PAYLOAD_EXTENDED_64); webSocketFrame.write(secondByte); webSocketFrame.write((byte) (dataLength >>> 56)); webSocketFrame.write((byte) (dataLength >>> 48)); webSocketFrame.write((byte) (dataLength >>> 40)); webSocketFrame.write((byte) (dataLength >>> 32)); webSocketFrame.write((byte) (dataLength >>> 24)); webSocketFrame.write((byte) (dataLength >>> 16)); webSocketFrame.write((byte) (dataLength >>> 8)); webSocketFrame.write((byte) (dataLength)); } // Write mask webSocketFrame.write(maskingKey[0]); webSocketFrame.write(maskingKey[1]); webSocketFrame.write(maskingKey[2]); webSocketFrame.write(maskingKey[3]); // Write masked data for (int i = 0; i < dataLength; i++) { byte nextByte = srcBuffer.get(); nextByte ^= maskingKey[i % 4]; webSocketFrame.write(nextByte); } // Copy frame to destination buffer dstBuffer.clear(); if (dstBuffer.capacity() >= webSocketFrame.size()) { dstBuffer.put(webSocketFrame.toByteArray()); } else { throw new OutOfMemoryError("insufficient output buffer size"); } } else { dstBuffer.clear(); } } @Override public WebsocketTuple unwrapBuffer(ByteBuffer srcBuffer) { WebsocketTuple result = new WebsocketTuple(0, WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_UNKNOWN); if (srcBuffer == null) { throw new IllegalArgumentException("input parameter is null"); } WebSocketMessageType retVal = WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_UNKNOWN; if (srcBuffer.remaining() > WebSocketHeader.MIN_HEADER_LENGTH) { // Read the first byte byte firstByte = srcBuffer.get(); // Get and check the opcode byte opcode = (byte) (firstByte & WebSocketHeader.OPCODE_MASK); // Read the second byte byte secondByte = srcBuffer.get(); // The MASK bit is never used. // byte maskBit = (byte) (secondByte & WebSocketHeader.MASKBIT_MASK); byte payloadLength = (byte) (secondByte & WebSocketHeader.PAYLOAD_MASK); long finalPayloadLength = -1; // We want to be explicit about the WebSocket payload length because the RFC specifies these ranges. if (payloadLength <= WebSocketHeader.PAYLOAD_SHORT_MAX) { finalPayloadLength = payloadLength; } else if (payloadLength == WebSocketHeader.PAYLOAD_EXTENDED_16) { // Check if we have enough bytes to read try { //Apply mask to turn into unsigned value finalPayloadLength = srcBuffer.getShort() & 0xFFFF; } catch (BufferUnderflowException e) { retVal = WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_HEADER_CHUNK; } } else if (payloadLength == WebSocketHeader.PAYLOAD_EXTENDED_64) { //Check if we have enough bytes to read try { finalPayloadLength = srcBuffer.getLong(); } catch (BufferUnderflowException e) { retVal = WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_HEADER_CHUNK; } } if (retVal == WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_UNKNOWN) { if (opcode == WebSocketHeader.OPCODE_BINARY) { retVal = WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_AMQP; } else if (opcode == WebSocketHeader.OPCODE_PING) { retVal = WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_PING; } else if (opcode == WebSocketHeader.OPCODE_CLOSE) { retVal = WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_CLOSE; } else if (opcode == WebSocketHeader.OPCODE_CONTINUATION) { retVal = WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_CHUNK; } else { retVal = WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_UNKNOWN; } } result.setLength(finalPayloadLength); result.setType(retVal); } return result; } protected WebSocketUpgrade createWebSocketUpgrade( String hostName, String webSocketPath, String webSocketQuery, int webSocketPort, String webSocketProtocol, Map additionalHeaders) { return new WebSocketUpgrade(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders); } protected byte[] createRandomMaskingKey() { final byte[] maskingKey = new byte[4]; Utils.getSecureRandom().nextBytes(maskingKey); return maskingKey; } @Override public int calculateHeaderSize(int payloadSize) { int retVal = 0; if (payloadSize > 0) { if (payloadSize <= WebSocketHeader.PAYLOAD_SHORT_MAX) { retVal = WebSocketHeader.MIN_HEADER_LENGTH_MASKED; } else if (payloadSize <= WebSocketHeader.PAYLOAD_MEDIUM_MAX) { retVal = WebSocketHeader.MED_HEADER_LENGTH_MASKED; } else { retVal = WebSocketHeader.MAX_HEADER_LENGTH_MASKED; } } return retVal; } } WebSocketImpl.java000066400000000000000000000617211460332530100422130ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/ws/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.ws.impl; import com.microsoft.azure.proton.transport.ws.WebSocket; import com.microsoft.azure.proton.transport.ws.WebSocketHandler; import com.microsoft.azure.proton.transport.ws.WebSocketHeader; import org.apache.qpid.proton.engine.Transport; import org.apache.qpid.proton.engine.TransportException; import org.apache.qpid.proton.engine.impl.ByteBufferUtils; import org.apache.qpid.proton.engine.impl.PlainTransportWrapper; import org.apache.qpid.proton.engine.impl.TransportInput; import org.apache.qpid.proton.engine.impl.TransportLayer; import org.apache.qpid.proton.engine.impl.TransportOutput; import org.apache.qpid.proton.engine.impl.TransportWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.util.Map; import static com.microsoft.azure.proton.transport.ws.WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_HEADER_CHUNK; import static com.microsoft.azure.proton.transport.ws.WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_UNKNOWN; import static org.apache.qpid.proton.engine.impl.ByteBufferUtils.newWriteableBuffer; import static org.apache.qpid.proton.engine.impl.ByteBufferUtils.pourAll; /** * Implementation for {@link WebSocket}. */ public class WebSocketImpl implements WebSocket, TransportLayer { private static final Logger TRACE_LOGGER = LoggerFactory.getLogger(WebSocketImpl.class); private static final int MAX_FRAME_SIZE = (4 * 1024) + (16 * WebSocketHeader.MED_HEADER_LENGTH_MASKED); private boolean tailClosed = false; private final ByteBuffer inputBuffer; private boolean headClosed = false; private final ByteBuffer outputBuffer; private ByteBuffer pingBuffer; private ByteBuffer wsInputBuffer; private ByteBuffer tempBuffer; private int underlyingOutputSize = 0; private int webSocketHeaderSize = 0; private WebSocketHandler webSocketHandler; private WebSocketState webSocketState = WebSocketState.PN_WS_NOT_STARTED; private String host = ""; private String path = ""; private String query = ""; private int port = 0; private String protocol = ""; private Map additionalHeaders = null; protected Boolean isWebSocketEnabled; private WebSocketHandler.WebSocketMessageType lastType; private long lastLength; private long bytesRead = 0; private WebSocketFrameReadState frameReadState = WebSocketFrameReadState.INIT_READ; /** * Create WebSocket transport layer - which, after configuring using * the {@link #configure(String, String, String, int, String, Map, WebSocketHandler)} API * is ready for layering in qpid-proton-j transport layers, using * {@link org.apache.qpid.proton.engine.impl.TransportInternal#addTransportLayer(TransportLayer)} API. */ public WebSocketImpl() { this(MAX_FRAME_SIZE); } /** * Create WebSocket transport layer - which, after configuring using * the {@link #configure(String, String, String, int, String, Map, WebSocketHandler)} API * is ready for layering in qpid-proton-j transport layers, using * {@link org.apache.qpid.proton.engine.impl.TransportInternal#addTransportLayer(TransportLayer)} API. * @param customMaxFrameSize the maximum frame size that this layer will buffer for */ public WebSocketImpl(int customMaxFrameSize) { inputBuffer = newWriteableBuffer(customMaxFrameSize); outputBuffer = newWriteableBuffer(customMaxFrameSize); pingBuffer = newWriteableBuffer(customMaxFrameSize); wsInputBuffer = newWriteableBuffer(customMaxFrameSize); tempBuffer = newWriteableBuffer(customMaxFrameSize); lastType = WEB_SOCKET_MESSAGE_TYPE_UNKNOWN; lastLength = 0; isWebSocketEnabled = false; } @Override public TransportWrapper wrap(final TransportInput input, final TransportOutput output) { return new WebSocketSnifferTransportWrapper(input, output); } @Override public void configure( String host, String path, String query, int port, String protocol, Map additionalHeaders, WebSocketHandler webSocketHandler) { this.host = host; this.path = path; this.query = query; this.port = port; this.protocol = protocol; this.additionalHeaders = additionalHeaders; if (webSocketHandler != null) { this.webSocketHandler = webSocketHandler; } else { this.webSocketHandler = new WebSocketHandlerImpl(); } isWebSocketEnabled = true; } @Override public void wrapBuffer(ByteBuffer srcBuffer, ByteBuffer dstBuffer) { if (isWebSocketEnabled) { webSocketHandler.wrapBuffer(srcBuffer, dstBuffer); } else { dstBuffer.clear(); dstBuffer.put(srcBuffer); } } @Override public WebSocketHandler.WebsocketTuple unwrapBuffer(ByteBuffer buffer) { if (isWebSocketEnabled) { return webSocketHandler.unwrapBuffer(buffer); } else { return new WebSocketHandler.WebsocketTuple(0, WEB_SOCKET_MESSAGE_TYPE_UNKNOWN); } } @Override public WebSocketState getState() { return webSocketState; } @Override public ByteBuffer getOutputBuffer() { return outputBuffer; } @Override public ByteBuffer getInputBuffer() { return inputBuffer; } @Override public ByteBuffer getPingBuffer() { return pingBuffer; } @Override public ByteBuffer getWsInputBuffer() { return wsInputBuffer; } @Override public Boolean getEnabled() { return isWebSocketEnabled; } @Override public WebSocketHandler getWebSocketHandler() { return webSocketHandler; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append( "WebSocketImpl [isWebSocketEnabled=").append(isWebSocketEnabled) .append(", state=").append(webSocketState) .append(", protocol=").append(protocol) .append(", host=").append(host) .append(", path=").append(path) .append(", query=").append(query) .append(", port=").append(port); if ((additionalHeaders != null) && (!additionalHeaders.isEmpty())) { builder.append(", additionalHeaders="); for (Map.Entry entry : additionalHeaders.entrySet()) { builder.append(entry.getKey() + ":" + entry.getValue()).append(", "); } int lastIndex = builder.lastIndexOf(", "); builder.delete(lastIndex, lastIndex + 2); } builder.append("]"); return builder.toString(); } protected void writeUpgradeRequest() { outputBuffer.clear(); //TODO (conniey): HTTP headers are encoded using StandardCharsets.ISO_8859_1. update webSocketHandler.createProxyRequest to return bytes // instead of String because encoding is not UTF-16. https://stackoverflow.com/a/655948/4220757 // See https://datatracker.ietf.org/doc/html/rfc2616#section-3.7.1 String request = webSocketHandler.createUpgradeRequest(host, path, query, port, protocol, additionalHeaders); outputBuffer.put(request.getBytes()); } protected void writePong() { webSocketHandler.createPong(pingBuffer, outputBuffer); } protected void writeClose() { outputBuffer.clear(); pingBuffer.flip(); outputBuffer.put(pingBuffer); } private final class WebSocketTransportWrapper implements TransportWrapper { private final TransportInput underlyingInput; private final TransportOutput underlyingOutput; private final ByteBuffer head; private WebSocketTransportWrapper(TransportInput input, TransportOutput output) { underlyingInput = input; underlyingOutput = output; head = outputBuffer.asReadOnlyBuffer(); head.limit(0); } private void readInputBuffer() { ByteBufferUtils.pour(inputBuffer, tempBuffer); } private boolean sendToUnderlyingInput() { boolean readComplete = false; switch (lastType) { case WEB_SOCKET_MESSAGE_TYPE_UNKNOWN: case WEB_SOCKET_MESSAGE_TYPE_CHUNK: wsInputBuffer.position(wsInputBuffer.limit()); wsInputBuffer.limit(wsInputBuffer.capacity()); break; case WEB_SOCKET_MESSAGE_TYPE_AMQP: wsInputBuffer.flip(); int bytes2 = pourAll(wsInputBuffer, underlyingInput); if (bytes2 == Transport.END_OF_STREAM) { tailClosed = true; } //underlyingInput.process(); wsInputBuffer.compact(); wsInputBuffer.flip(); readComplete = true; break; case WEB_SOCKET_MESSAGE_TYPE_CLOSE: wsInputBuffer.flip(); pingBuffer.put(wsInputBuffer); webSocketState = WebSocketState.PN_WS_CONNECTED_CLOSING; wsInputBuffer.compact(); wsInputBuffer.flip(); readComplete = true; break; case WEB_SOCKET_MESSAGE_TYPE_PING: wsInputBuffer.flip(); pingBuffer.put(wsInputBuffer); webSocketState = WebSocketState.PN_WS_CONNECTED_PONG; wsInputBuffer.compact(); wsInputBuffer.flip(); readComplete = true; break; default: assert false : String.format("unexpected value for WebSocketFrameReadState: %s", lastType); } wsInputBuffer.position(wsInputBuffer.limit()); wsInputBuffer.limit(wsInputBuffer.capacity()); return readComplete; } private void processInput() throws TransportException { switch (webSocketState) { case PN_WS_CONNECTING: inputBuffer.mark(); if (webSocketHandler.validateUpgradeReply(inputBuffer)) { webSocketState = WebSocketState.PN_WS_CONNECTED_FLOW; } else { // Input data was incomplete. Reset buffer position and wait for another call after more data arrives. inputBuffer.reset(); TRACE_LOGGER.warn("Websocket connecting response incomplete"); } inputBuffer.compact(); break; case PN_WS_CONNECTED_FLOW: case PN_WS_CONNECTED_PONG: if (inputBuffer.remaining() > 0) { boolean readComplete = false; while (!readComplete) { switch (frameReadState) { //State 1: Init_Read case INIT_READ: //Reset the bytes read count bytesRead = 0; //Determine how much to grab from the input buffer and only take that readInputBuffer(); frameReadState = tempBuffer.position() < 2 ? WebSocketFrameReadState.CHUNK_READ : WebSocketFrameReadState.HEADER_READ; readComplete = frameReadState == WebSocketFrameReadState.CHUNK_READ; break; //State 2: Chunk_Read case CHUNK_READ: //Determine how much to grab from the input buffer and only take that readInputBuffer(); frameReadState = tempBuffer.position() < 2 ? frameReadState : WebSocketFrameReadState.HEADER_READ; readComplete = frameReadState == WebSocketFrameReadState.CHUNK_READ; break; //State 3: Header_Read case HEADER_READ: //Determine how much to grab from the input buffer and only take that readInputBuffer(); tempBuffer.flip(); WebSocketHandler.WebsocketTuple unwrapResult = unwrapBuffer(tempBuffer); lastType = unwrapResult.getType(); lastLength = unwrapResult.getLength(); frameReadState = lastType == WEB_SOCKET_MESSAGE_TYPE_HEADER_CHUNK ? WebSocketFrameReadState.CHUNK_READ : WebSocketFrameReadState.CONTINUED_FRAME_READ; readComplete = frameReadState == WebSocketFrameReadState.CHUNK_READ || tempBuffer.position() == tempBuffer.limit(); if (frameReadState == WebSocketFrameReadState.CONTINUED_FRAME_READ) { tempBuffer.compact(); } else { //Unflip the buffer to continue writing to it tempBuffer.position(tempBuffer.limit()); tempBuffer.limit(tempBuffer.capacity()); } break; //State 4: Continued_Frame_Read (Similar to Chunk_Read but reading until // we've read the number of bytes specified when unwrapping the buffer) case CONTINUED_FRAME_READ: //Determine how much to grab from the input buffer and only take that readInputBuffer(); tempBuffer.flip(); final byte[] data; if (tempBuffer.remaining() >= lastLength - bytesRead) { data = new byte[(int) (lastLength - bytesRead)]; tempBuffer.get(data, 0, (int) (lastLength - bytesRead)); wsInputBuffer.put(data); bytesRead += lastLength - bytesRead; } else { //Otherwise the remaining bytes is < the rest that we need data = new byte[tempBuffer.remaining()]; tempBuffer.get(data); wsInputBuffer.put(data); bytesRead += data.length; } //Send whatever we have sendToUnderlyingInput(); frameReadState = bytesRead == lastLength ? WebSocketFrameReadState.INIT_READ : WebSocketFrameReadState.CONTINUED_FRAME_READ; readComplete = tempBuffer.remaining() == 0; tempBuffer.compact(); break; //State 5: Read_Error case READ_ERROR: break; default: assert false : String.format("unexpected value for WebSocketFrameReadState: %s", frameReadState); } } } inputBuffer.compact(); break; case PN_WS_NOT_STARTED: case PN_WS_CLOSED: case PN_WS_FAILED: default: break; } } @Override public int capacity() { if (isWebSocketEnabled) { if (tailClosed) { return Transport.END_OF_STREAM; } else { return inputBuffer.remaining(); } } else { return underlyingInput.capacity(); } } @Override public int position() { if (isWebSocketEnabled) { if (tailClosed) { return Transport.END_OF_STREAM; } else { return inputBuffer.position(); } } else { return underlyingInput.position(); } } @Override public ByteBuffer tail() { if (isWebSocketEnabled) { return inputBuffer; } else { return underlyingInput.tail(); } } @Override public void process() throws TransportException { if (isWebSocketEnabled) { inputBuffer.flip(); switch (webSocketState) { case PN_WS_CONNECTING: case PN_WS_CONNECTED_FLOW: processInput(); break; case PN_WS_NOT_STARTED: case PN_WS_FAILED: default: underlyingInput.process(); } } else { underlyingInput.process(); } } @Override public void close_tail() { tailClosed = true; if (isWebSocketEnabled) { headClosed = true; underlyingInput.close_tail(); } else { underlyingInput.close_tail(); } } @Override public int pending() { if (isWebSocketEnabled) { switch (webSocketState) { case PN_WS_NOT_STARTED: if (outputBuffer.position() == 0) { webSocketState = WebSocketState.PN_WS_CONNECTING; writeUpgradeRequest(); head.limit(outputBuffer.position()); if (headClosed) { webSocketState = WebSocketState.PN_WS_FAILED; return Transport.END_OF_STREAM; } else { return outputBuffer.position(); } } else { return outputBuffer.position(); } case PN_WS_CONNECTING: if (headClosed && (outputBuffer.position() == 0)) { webSocketState = WebSocketState.PN_WS_FAILED; return Transport.END_OF_STREAM; } else { return outputBuffer.position(); } case PN_WS_CONNECTED_FLOW: underlyingOutputSize = underlyingOutput.pending(); if (underlyingOutputSize > 0) { webSocketHeaderSize = webSocketHandler.calculateHeaderSize(underlyingOutputSize); return underlyingOutputSize + webSocketHeaderSize; } else { return underlyingOutputSize; } case PN_WS_CONNECTED_PONG: webSocketState = WebSocketState.PN_WS_CONNECTED_FLOW; writePong(); head.limit(outputBuffer.position()); if (headClosed) { webSocketState = WebSocketState.PN_WS_FAILED; return Transport.END_OF_STREAM; } else { return outputBuffer.position(); } case PN_WS_CONNECTED_CLOSING: webSocketState = WebSocketState.PN_WS_CLOSED; writeClose(); head.limit(outputBuffer.position()); if (headClosed) { webSocketState = WebSocketState.PN_WS_FAILED; return Transport.END_OF_STREAM; } else { return outputBuffer.position(); } case PN_WS_FAILED: default: return Transport.END_OF_STREAM; } } else { return underlyingOutput.pending(); } } @Override public ByteBuffer head() { if (isWebSocketEnabled) { switch (webSocketState) { case PN_WS_CONNECTING: case PN_WS_CONNECTED_PONG: case PN_WS_CONNECTED_CLOSING: return head; case PN_WS_CONNECTED_FLOW: underlyingOutputSize = underlyingOutput.pending(); if (underlyingOutputSize > 0) { wrapBuffer(underlyingOutput.head(), outputBuffer); webSocketHeaderSize = outputBuffer.position() - underlyingOutputSize; head.limit(outputBuffer.position()); } return head; case PN_WS_NOT_STARTED: case PN_WS_CLOSED: case PN_WS_FAILED: default: return underlyingOutput.head(); } } else { return underlyingOutput.head(); } } @Override public void pop(int bytes) { if (isWebSocketEnabled) { switch (webSocketState) { case PN_WS_CONNECTING: if (outputBuffer.position() != 0) { outputBuffer.flip(); outputBuffer.position(bytes); outputBuffer.compact(); head.position(0); head.limit(outputBuffer.position()); } else { underlyingOutput.pop(bytes); } break; case PN_WS_CONNECTED_FLOW: case PN_WS_CONNECTED_PONG: case PN_WS_CONNECTED_CLOSING: if ((bytes >= webSocketHeaderSize) && (outputBuffer.position() != 0)) { outputBuffer.flip(); outputBuffer.position(bytes); outputBuffer.compact(); head.position(0); head.limit(outputBuffer.position()); underlyingOutput.pop(bytes - webSocketHeaderSize); webSocketHeaderSize = 0; } else if ((bytes > 0) && (bytes < webSocketHeaderSize)) { webSocketHeaderSize -= bytes; } else { underlyingOutput.pop(bytes); } break; case PN_WS_NOT_STARTED: case PN_WS_CLOSED: case PN_WS_FAILED: underlyingOutput.pop(bytes); break; default: assert false : String.format("unexpected value for WebSocketFrameReadState: %s", webSocketState); } } else { underlyingOutput.pop(bytes); } } @Override public void close_head() { underlyingOutput.close_head(); } } private final class WebSocketSnifferTransportWrapper extends WebSocketSniffer { private WebSocketSnifferTransportWrapper(TransportInput input, TransportOutput output) { super(new WebSocketTransportWrapper(input, output), new PlainTransportWrapper(output, input)); } protected boolean isDeterminationMade() { _selectedTransportWrapper = _wrapper1; return true; } } } WebSocketSniffer.java000066400000000000000000000026201460332530100426770ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/ws/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.ws.impl; import com.microsoft.azure.proton.transport.ws.WebSocketHeader; import org.apache.qpid.proton.engine.impl.HandshakeSniffingTransportWrapper; import org.apache.qpid.proton.engine.impl.TransportWrapper; /** * Determines which transport layer to read web socket bytes from. */ public class WebSocketSniffer extends HandshakeSniffingTransportWrapper { /** * Creates an instance. * * @param webSocket Web socket transport layer. * @param other The next transport layer. */ public WebSocketSniffer(TransportWrapper webSocket, TransportWrapper other) { super(webSocket, other); } protected TransportWrapper getSelectedTransportWrapper() { return _selectedTransportWrapper; } @Override protected int bufferSize() { return WebSocketHeader.MIN_HEADER_LENGTH_MASKED; } @Override protected void makeDetermination(byte[] bytes) { if (bytes.length < bufferSize()) { throw new IllegalArgumentException("insufficient bytes"); } if (bytes[0] != WebSocketHeader.FINAL_OPCODE_BINARY) { _selectedTransportWrapper = _wrapper2; return; } _selectedTransportWrapper = _wrapper1; } } WebSocketUpgrade.java000066400000000000000000000173771460332530100427110ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/ws/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.ws.impl; import java.nio.charset.StandardCharsets; import java.security.InvalidParameterException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Locale; import java.util.Map; import java.util.Scanner; /** * Represents a web socket upgrade request. */ public class WebSocketUpgrade { private static final String RFC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; private static final char QUESTION_MARK = '?'; private static final char SLASH = '/'; private final String query; private final String host; private final String path; private final String port; private final String protocol; private final Map additionalHeaders; private volatile String webSocketKey = ""; /** * Create {@link WebSocketUpgrade} instance, which can be used for websocket upgrade hand-shake with http server * as per RFC https://tools.ietf.org/html/rfc6455. * @param hostName host name to send the request to * @param webSocketPath path on the request url where WebSocketUpgrade will be sent to * @param webSocketQuery query on the request url where WebSocketUpgrade will be sent to * @param webSocketPort port on the request url where WebSocketUpgrade will be sent to * @param webSocketProtocol value for Sec-WebSocket-Protocol header on the WebSocketUpgrade request * @param additionalHeaders any additional headers to be part of the WebSocketUpgrade request */ public WebSocketUpgrade( String hostName, String webSocketPath, String webSocketQuery, int webSocketPort, String webSocketProtocol, Map additionalHeaders) { this.host = hostName; this.path = webSocketPath.isEmpty() || webSocketPath.charAt(0) == SLASH ? webSocketPath : SLASH + webSocketPath; this.query = webSocketQuery.isEmpty() || webSocketQuery.charAt(0) == QUESTION_MARK ? webSocketQuery : QUESTION_MARK + webSocketQuery; this.port = webSocketPort == 0 ? "" : String.valueOf(webSocketPort); this.protocol = webSocketProtocol; this.additionalHeaders = additionalHeaders; } /** * Utility function to create random, Base64 encoded key. */ private String createWebSocketKey() { byte[] key = new byte[16]; for (int i = 0; i < 16; i++) { key[i] = (byte) (int) (Utils.getSecureRandom().nextDouble() * 256); } return Base64.encodeBase64StringLocal(key).trim(); } /** * Create the Upgrade to websocket request as per the RFC https://tools.ietf.org/html/rfc6455. * @return http request to upgrade to websockets. */ public String createUpgradeRequest() { if (this.host.isEmpty()) { throw new InvalidParameterException("host header has no value"); } if (this.protocol.isEmpty()) { throw new InvalidParameterException("protocol header has no value"); } this.webSocketKey = createWebSocketKey(); final String endOfLine = "\r\n"; final StringBuilder stringBuilder = new StringBuilder() .append("GET https://") .append(this.host) .append(this.path) .append(this.query) .append(" HTTP/1.1").append(endOfLine) .append("Connection: Upgrade,Keep-Alive").append(endOfLine) .append("Upgrade: websocket").append(endOfLine) .append("Sec-WebSocket-Version: 13").append(endOfLine) .append("Sec-WebSocket-Key: ").append(this.webSocketKey).append(endOfLine) .append("Sec-WebSocket-Protocol: ").append(this.protocol).append(endOfLine) .append("Host: ").append(this.host).append(endOfLine); if (additionalHeaders != null) { for (Map.Entry entry : additionalHeaders.entrySet()) { stringBuilder.append(entry.getKey()).append(": ").append(entry.getValue()).append(endOfLine); } } stringBuilder.append(endOfLine); return stringBuilder.toString(); } /** * Validate the response received for 'upgrade to websockets' request from http server. * @param responseBytes bytes received from http server * @return value indicating if the websockets upgrade succeeded */ public Boolean validateUpgradeReply(byte[] responseBytes) { final String httpString = new String(responseBytes, StandardCharsets.UTF_8); boolean isStatusLineOk = false; boolean isUpgradeHeaderOk = false; boolean isConnectionHeaderOk = false; boolean isProtocolHeaderOk = false; boolean isAcceptHeaderOk = false; final Scanner scanner = new Scanner(httpString); while (scanner.hasNextLine()) { final String line = scanner.nextLine(); final String lowercase = line.toLowerCase(Locale.ROOT); if ((lowercase.contains("http/1.1")) && (line.contains("101")) && (lowercase.contains("switching protocols"))) { isStatusLineOk = true; continue; } if ((lowercase.contains("upgrade")) && (lowercase.contains("websocket"))) { isUpgradeHeaderOk = true; continue; } if ((lowercase.contains("connection")) && (lowercase.contains("upgrade"))) { isConnectionHeaderOk = true; continue; } if (lowercase.contains("sec-websocket-protocol") && (lowercase.contains(protocol.toLowerCase(Locale.ROOT)))) { isProtocolHeaderOk = true; continue; } if (lowercase.contains("sec-websocket-accept")) { MessageDigest messageDigest = null; try { messageDigest = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { // can't happen since SHA-1 is a known digest break; } final byte[] bytes = (this.webSocketKey + RFC_GUID).getBytes(StandardCharsets.ISO_8859_1); final String expectedKey = Base64.encodeBase64StringLocal(messageDigest.digest(bytes)).trim(); if (line.contains(expectedKey)) { isAcceptHeaderOk = true; } } } scanner.close(); return (isStatusLineOk) && (isUpgradeHeaderOk) && (isConnectionHeaderOk) && (isProtocolHeaderOk) && (isAcceptHeaderOk); } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("WebSocketUpgrade [host=") .append(host) .append(", path=") .append(path) .append(", port=") .append(port) .append(", protocol=") .append(protocol) .append(", webSocketKey=") .append(webSocketKey); if ((additionalHeaders != null) && (!additionalHeaders.isEmpty())) { builder.append(", additionalHeaders="); for (Map.Entry entry : additionalHeaders.entrySet()) { builder.append(entry.getKey()).append(":").append(entry.getValue()).append(", "); } final int lastIndex = builder.lastIndexOf(", "); builder.delete(lastIndex, lastIndex + 2); } builder.append("]"); return builder.toString(); } } package-info.java000066400000000000000000000005111460332530100420150ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/ws/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. /** * Package containg implementation for {@link com.microsoft.azure.proton.transport.ws.WebSocketHandler} and {@link * com.microsoft.azure.proton.transport.ws.WebSocket}. */ package com.microsoft.azure.proton.transport.ws.impl; package-info.java000066400000000000000000000003271460332530100410610ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/main/java/com/microsoft/azure/proton/transport/ws// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. /** * Package containing classes for connecting via WebSockets. */ package com.microsoft.azure.proton.transport.ws; qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/000077500000000000000000000000001460332530100251425ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/000077500000000000000000000000001460332530100260635ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/000077500000000000000000000000001460332530100266415ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/000077500000000000000000000000001460332530100306465ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/000077500000000000000000000000001460332530100317745ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/000077500000000000000000000000001460332530100333155ustar00rootroot00000000000000transport/000077500000000000000000000000001460332530100352725ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/protonproxy/000077500000000000000000000000001460332530100364535ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transportimpl/000077500000000000000000000000001460332530100374145ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transport/proxyBasicProxyChallengeProcessorImplTest.java000066400000000000000000000121151460332530100475270ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; import com.microsoft.azure.proton.transport.proxy.ProxyAuthenticationType; import com.microsoft.azure.proton.transport.proxy.ProxyConfiguration; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.net.Authenticator; import java.net.InetSocketAddress; import java.net.PasswordAuthentication; import java.net.Proxy; import java.net.ProxySelector; import java.net.SocketAddress; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; import java.util.List; import java.util.Map; public class BasicProxyChallengeProcessorImplTest { private static final String HOSTNAME = "127.0.0.1"; private static final int PORT = 3128; private static final String USERNAME = "basicuser"; private static final String PASSWORD = "basicpw"; @Before public void setup() { ProxySelector.setDefault(new ProxySelector() { @Override public List select(URI uri) { List proxies = new ArrayList<>(); proxies.add(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(HOSTNAME, PORT))); return proxies; } @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { System.out.format("PROXY CONNECTION FAILED: URI = %s, Socket Address = %s, IO Exception = %s%n", uri.toString(), sa.toString(), ioe.toString()); } }); Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { if (getRequestorType() == Authenticator.RequestorType.PROXY) { return new PasswordAuthentication(USERNAME, PASSWORD.toCharArray()); } return super.getPasswordAuthentication(); } }); } @Test public void testGetHeaderBasic() { final String host = ""; final String expected = String.join(" ", Constants.BASIC, "YmFzaWN1c2VyOmJhc2ljcHc="); final ProxyAuthenticator authenticator = new ProxyAuthenticator(); final BasicProxyChallengeProcessorImpl proxyChallengeProcessor = new BasicProxyChallengeProcessorImpl(host, authenticator); Map headers = proxyChallengeProcessor.getHeader(); Assert.assertEquals(expected, headers.get(Constants.PROXY_AUTHORIZATION)); } /** * Verifies that if user provides their own credentials, it will be used. */ @Test public void testWithProxyConfiguration() { final InetSocketAddress address = InetSocketAddress.createUnresolved("foo.proxy.com", 3138); final Proxy proxy = new Proxy(Proxy.Type.SOCKS, address); final String username = "test-username"; final String password = "test-password!!"; final byte[] encoded = String.join(":", username, password).getBytes(StandardCharsets.UTF_8); final String base64Encoded = Base64.getEncoder().encodeToString(encoded); final String expectedAuthValue = String.join(" ", Constants.BASIC, base64Encoded); final ProxyConfiguration configuration = new ProxyConfiguration(ProxyAuthenticationType.DIGEST, proxy, username, password); final ProxyAuthenticator authenticator = new ProxyAuthenticator(configuration); final BasicProxyChallengeProcessorImpl proxyChallengeProcessor = new BasicProxyChallengeProcessorImpl("something.com", authenticator); Map headers = proxyChallengeProcessor.getHeader(); Assert.assertEquals(expectedAuthValue, headers.get(Constants.PROXY_AUTHORIZATION)); } /** * Verifies that if we cannot obtain credentials from proxyAuthenticator, then we return null. */ @Test public void cannotObtainPasswordCredentialsWithValues() { Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { if (getRequestorType() == Authenticator.RequestorType.PROXY) { return null; } Assert.fail("Should always be type of ProxyRequest."); throw new RuntimeException("Should be of type ProxyRequest"); } }); final InetSocketAddress address = InetSocketAddress.createUnresolved("foo.proxy.com", 3138); final Proxy proxy = new Proxy(Proxy.Type.SOCKS, address); final ProxyConfiguration configuration = new ProxyConfiguration(ProxyAuthenticationType.BASIC, proxy, null, null); final ProxyAuthenticator authenticator = new ProxyAuthenticator(configuration); final BasicProxyChallengeProcessorImpl proxyChallengeProcessor = new BasicProxyChallengeProcessorImpl("something.foo.com", authenticator); Map headers = proxyChallengeProcessor.getHeader(); Assert.assertNull(headers); } } DigestProxyChallengeProcessorImplTest.java000066400000000000000000000251511460332530100477310ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; import com.microsoft.azure.proton.transport.proxy.ProxyAuthenticationType; import com.microsoft.azure.proton.transport.proxy.ProxyConfiguration; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import java.io.IOException; import java.net.Authenticator; import java.net.InetSocketAddress; import java.net.PasswordAuthentication; import java.net.Proxy; import java.net.ProxySelector; import java.net.SocketAddress; import java.net.URI; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; import static com.microsoft.azure.proton.transport.proxy.impl.DigestProxyChallengeProcessorImpl.printHexBinary; import static java.nio.charset.StandardCharsets.UTF_8; public class DigestProxyChallengeProcessorImplTest { private static final String NEW_LINE = "\r\n"; private static final String HOSTNAME = "127.0.0.1"; private static final int PORT = 3128; private static final String USERNAME = "my-username"; private static final String PASSWORD = "my-password"; private static MessageDigest md5; private ProxySelector originalProxy; @BeforeClass public static void init() throws NoSuchAlgorithmException { md5 = MessageDigest.getInstance(DigestProxyChallengeProcessorImpl.DEFAULT_ALGORITHM); } @Before public void setup() { originalProxy = ProxySelector.getDefault(); ProxySelector.setDefault(new ProxySelector() { @Override public List select(URI uri) { List proxies = new ArrayList<>(); proxies.add(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(HOSTNAME, PORT))); return proxies; } @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { System.out.format("PROXY CONNECTION FAILED: URI = %s, Socket Address = %s, IO Exception = %s\n", uri.toString(), sa.toString(), ioe.toString()); } }); Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { if (getRequestorType() == Authenticator.RequestorType.PROXY) { return new PasswordAuthentication(USERNAME, PASSWORD.toCharArray()); } return super.getPasswordAuthentication(); } }); } @After public void teardown() { ProxySelector.setDefault(originalProxy); } @Test public void testGetHeaderDigest() { // Arrange final String realm = "Squid proxy-caching web server"; final String nonce = "zWV5XAAAAAAgz1ACAAAAAE9hOwIAAAAA"; final String qop = "auth"; // a1 and a2 are hash values generated as an intermediate step in the Digest algorithm. final String a1 = "6ea8f6c05777aae5987035155da478bc"; final String a2 = "9b1f7b831464108253ea5a0fd0ebb3d9"; final String response = generateProxyChallenge(realm, nonce, qop); final DigestValidator validator = new DigestValidator(USERNAME, realm, nonce, "00000001", HOSTNAME, qop); // Act final DigestProxyChallengeProcessorImpl proxyChallengeProcessor = new DigestProxyChallengeProcessorImpl(HOSTNAME, response, new ProxyAuthenticator()); Map headers = proxyChallengeProcessor.getHeader(); // Assert String resp = headers.get(Constants.PROXY_AUTHORIZATION); validator.assertEquals(resp, a1, a2); } /** * Verify that when we explicitly pass in proxy configuration, that host and those credentials are used rather than * the system defined ones. */ @Test public void testGetHeaderDigestWithProxy() { // Arrange final String realm = "My Test Realm"; final String nonce = "A randomly generated nonce"; final String qop = "auth"; final String challenge = generateProxyChallenge(realm, nonce, qop); final String host = "foobar.myhost.com"; final InetSocketAddress address = InetSocketAddress.createUnresolved(host, 3138); final Proxy proxy = new Proxy(Proxy.Type.SOCKS, address); final String username = "my-different-username"; final String password = "my-different-password"; final ProxyConfiguration configuration = new ProxyConfiguration(ProxyAuthenticationType.DIGEST, proxy, username, password); final ProxyAuthenticator authenticator = new ProxyAuthenticator(configuration); // a1 and a2 are hash values generated as an intermediate step in the Digest algorithm. final String a1 = "d9365dc421b15c1fe23ac413788839c7"; final String a2 = "5c8c78994a9672c0b394d4615f3f8c23"; final DigestValidator validator = new DigestValidator(username, realm, nonce, "00000001", host, qop); // Act final DigestProxyChallengeProcessorImpl proxyChallengeProcessor = new DigestProxyChallengeProcessorImpl(host, challenge, authenticator); Map headers = proxyChallengeProcessor.getHeader(); // Assert String resp = headers.get(Constants.PROXY_AUTHORIZATION); validator.assertEquals(resp, a1, a2); } /** * Verifies that if we cannot obtain credentials from proxyAuthenticator, then we return null. */ @Test public void cannotObtainPasswordCredentialsWithValues() { Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { if (getRequestorType() == Authenticator.RequestorType.PROXY) { return null; } Assert.fail("Should always be type of ProxyRequest."); throw new RuntimeException("Should be of type ProxyRequest"); } }); // Arrange final String realm = "My Test Realm"; final String nonce = "A randomly generated nonce"; final String qop = "auth"; final String challenge = generateProxyChallenge(realm, nonce, qop); final String host = "foobar.myhost.com"; final InetSocketAddress address = InetSocketAddress.createUnresolved(host, 3138); final Proxy proxy = new Proxy(Proxy.Type.SOCKS, address); final ProxyConfiguration configuration = new ProxyConfiguration(ProxyAuthenticationType.DIGEST, proxy, null, null); final ProxyAuthenticator authenticator = new ProxyAuthenticator(configuration); final DigestProxyChallengeProcessorImpl proxyChallengeProcessor = new DigestProxyChallengeProcessorImpl(host, challenge, authenticator); // Act Map headers = proxyChallengeProcessor.getHeader(); // Assert Assert.assertTrue(headers.isEmpty()); } private static String generateProxyChallenge(String realm, String nonce, String qop) { final String digest = String.format("%s realm=\"%s\", nonce=\"%s\", qop=\"%s\", stale=false", Constants.DIGEST, realm, nonce, qop); final String basic = String.format("%s realm=\"%s\"", Constants.BASIC, realm); return String.join(NEW_LINE, "HTTP/1.1 407 Proxy Authentication Required", "X-Squid-Error: ERR_CACHE_ACCESS_DENIED 0", digest, basic) + NEW_LINE; } private class DigestValidator { private static final String USERNAME_KEY = "username"; private static final String NONCE_KEY = "nonce"; private static final String URI_KEY = "uri"; private static final String CNONCE_KEY = "cnonce"; private static final String RESPONSE_KEY = "response"; private static final String NC_KEY = "nc"; private static final String QOP_KEY = "qop"; private final Map expected = new HashMap<>(); DigestValidator(String username, String realm, String nonce, String nc, String uri, String qop) { expected.put(NC_KEY, nc); expected.put(USERNAME_KEY, username); expected.put("realm", realm); expected.put(NONCE_KEY, nonce); expected.put(URI_KEY, uri); expected.put(QOP_KEY, qop); expected.put(CNONCE_KEY, null); expected.put(RESPONSE_KEY, null); } void assertEquals(String response, String a1, String a2) { Assert.assertNotNull(response); Assert.assertTrue(response.startsWith(Constants.DIGEST)); String[] split = response.substring(Constants.DIGEST.length()).split(","); Map actual = Arrays.stream(split).map(p -> { String[] part = p.split("="); Assert.assertEquals(2, part.length); String key = part[0].trim(); String value = part[1].replace("\"", "").trim(); return new Pair(key, value); }).collect(Collectors.toMap(Pair::key, Pair::value)); Assert.assertEquals(expected.size(), actual.size()); expected.forEach((key, value) -> { // Skipping "cnonce" and "response" because they are randomly generated each time. Later on, we'll // check for the response's validity. if (key.equals(CNONCE_KEY) || key.equals(RESPONSE_KEY)) { return; } Assert.assertTrue(actual.containsKey(key)); Assert.assertEquals(value, actual.get(key)); }); String expectedRawResponse = String.join(":", a1, expected.get(NONCE_KEY), expected.get(NC_KEY), actual.get(CNONCE_KEY), expected.get(QOP_KEY), a2); String expectedResponse = printHexBinary(md5.digest(expectedRawResponse.getBytes(UTF_8))).toLowerCase(Locale.ROOT); Assert.assertEquals(expectedResponse, actual.get(RESPONSE_KEY)); } } private static class Pair { private final String key; private final String value; Pair(String key, String value) { this.key = key; this.value = value; } String key() { return this.key; } String value() { return this.value; } } } HttpStatusLineTest.java000066400000000000000000000025061460332530100440550ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; import com.microsoft.azure.proton.transport.proxy.HttpStatusLine; import org.junit.Assert; import org.junit.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; public class HttpStatusLineTest { /** * Verifies that it successfully parses a valid HTTP status line. */ @Test public void validStatusLine() { // Arrange final String line = "HTTP/1.1 200 Connection Established"; // Act final HttpStatusLine actual = HttpStatusLine.create(line); // Assert Assert.assertNotNull(actual); Assert.assertEquals(200, actual.getStatusCode()); Assert.assertEquals("1.1", actual.getProtocolVersion()); Assert.assertEquals("Connection Established", actual.getReason()); } /** * Verifies that status line length is invalid */ @ParameterizedTest @ValueSource(strings = {"HTTP/1.1 InvalidLength", "HTTP/1.1 Invalid Code", "HTTP/invalid protocol"}) public void invalidStatusLine(String line) { // Act & Assert Assert.assertThrows(IllegalArgumentException.class, () -> HttpStatusLine.create(line)); } } ProxyAuthenticatorTests.java000066400000000000000000000177451460332530100451740ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; import com.microsoft.azure.proton.transport.proxy.ProxyAuthenticationType; import com.microsoft.azure.proton.transport.proxy.ProxyConfiguration; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import java.net.Authenticator; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.PasswordAuthentication; import java.net.Proxy; import java.net.ProxySelector; import java.util.ArrayList; import java.util.List; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; public class ProxyAuthenticatorTests { private static final String USERNAME = "test-user"; private static final String PASSWORD = "test-password!"; private static final char[] PASSWORD_CHAR_ARRAY = PASSWORD.toCharArray(); private static final String PROXY_ADDRESS = "foo.proxy.com"; private ProxySelector originalProxySelector; private ProxySelector proxySelector; private TestAuthenticator authenticator; /** * Creates mocks of the proxy selector and authenticator and sets them as defaults. */ @Before public void setup() { originalProxySelector = ProxySelector.getDefault(); authenticator = new TestAuthenticator(USERNAME, PASSWORD); proxySelector = mock(ProxySelector.class, Mockito.CALLS_REAL_METHODS); ProxySelector.setDefault(proxySelector); Authenticator.setDefault(authenticator); } @After public void teardown() { ProxySelector.setDefault(originalProxySelector); } @Test(expected = NullPointerException.class) public void constructorThrows() { new ProxyAuthenticator(null); } @Test public void useSystemDefaults() { // Arrange final String scheme = "Digest"; final InetAddress loopbackAddress = InetAddress.getLoopbackAddress(); final InetSocketAddress socketAddress = new InetSocketAddress(loopbackAddress, 443); final ProxyAuthenticator proxyAuthenticator = new ProxyAuthenticator(); final Proxy proxy = new Proxy(Proxy.Type.HTTP, socketAddress); final List proxies = new ArrayList<>(); proxies.add(proxy); when(proxySelector.select(argThat(u -> u != null && u.getPath().equals(PROXY_ADDRESS)))) .thenReturn(proxies); // Act PasswordAuthentication authentication = proxyAuthenticator.getPasswordAuthentication(scheme, PROXY_ADDRESS); // Assert Assert.assertNotNull(authentication); Assert.assertEquals(USERNAME, authentication.getUserName()); Assert.assertArrayEquals(PASSWORD_CHAR_ARRAY, authentication.getPassword()); Assert.assertEquals(Proxy.Type.HTTP.name(), authenticator.requestingProtocol()); Assert.assertEquals(loopbackAddress, authenticator.requestingSite()); Assert.assertEquals(scheme, authenticator.requestingScheme()); Assert.assertEquals(Authenticator.RequestorType.PROXY, authenticator.requestorType()); } /** * Verifies that if user specifies the credentials that is fully populated, use the credentials. Regardless of the * proxy address in {@link ProxyAuthenticator#getPasswordAuthentication(String, String)}. */ @Test public void useProxyConfigurationWithCredentials() { // Arrange final String scheme = "Digest"; final String host = "foobar.myhost.com"; final InetSocketAddress address = InetSocketAddress.createUnresolved(host, 3138); final Proxy proxy = new Proxy(Proxy.Type.SOCKS, address); final ProxyConfiguration configuration = new ProxyConfiguration(ProxyAuthenticationType.BASIC, proxy, "my-username", "my-password"); final ProxyAuthenticator proxyAuthenticator = new ProxyAuthenticator(configuration); final List proxies = new ArrayList<>(); proxies.add(proxy); when(proxySelector.select(argThat(u -> u != null && u.getPath().equals(PROXY_ADDRESS)))) .thenReturn(proxies); // Act PasswordAuthentication authentication = proxyAuthenticator.getPasswordAuthentication(scheme, PROXY_ADDRESS); // Assert verifyZeroInteractions(proxySelector); Assert.assertNotNull(authentication); Assert.assertEquals(configuration.credentials().getUserName(), authentication.getUserName()); Assert.assertArrayEquals(configuration.credentials().getPassword(), authentication.getPassword()); // We never use the system-wide authenticator. Verify that. Assert.assertNull(authenticator.requestingScheme()); } /** * Verifies that if user specifies the credentials but no proxy address, we use the credentials. */ @Test public void useProxyConfigurationWithCredentialsNoAddress() { // Arrange final String scheme = "Digest"; final String host = "foobar.myhost.com"; final InetSocketAddress address = InetSocketAddress.createUnresolved(host, 3138); final Proxy proxy = new Proxy(Proxy.Type.SOCKS, address); final ProxyConfiguration configuration = new ProxyConfiguration(ProxyAuthenticationType.BASIC, null, "my-username", "my-password"); final ProxyAuthenticator proxyAuthenticator = new ProxyAuthenticator(configuration); final List proxies = new ArrayList<>(); proxies.add(proxy); when(proxySelector.select(argThat(u -> u != null && u.getPath().equals(PROXY_ADDRESS)))) .thenReturn(proxies); // Act PasswordAuthentication authentication = proxyAuthenticator.getPasswordAuthentication(scheme, PROXY_ADDRESS); // Assert verifyZeroInteractions(proxySelector); Assert.assertNotNull(authentication); Assert.assertEquals(configuration.credentials().getUserName(), authentication.getUserName()); Assert.assertArrayEquals(configuration.credentials().getPassword(), authentication.getPassword()); // We never use the system-wide authenticator. Verify that. Assert.assertNull(authenticator.requestingScheme()); } /** * Verifies that if user specifies the proxy address but not the credentials, use the system-wide authenticator to * figure out the credentials for that proxy address. */ @Test public void useProxyAddressWithSystemAuthentication() { // Arrange final String scheme = "Digest"; final String host = "my-proxy.myhost.com"; final InetSocketAddress address = InetSocketAddress.createUnresolved(host, 3138); final Proxy proxy = new Proxy(Proxy.Type.SOCKS, address); final ProxyConfiguration configuration = new ProxyConfiguration(ProxyAuthenticationType.BASIC, proxy, null, null); final ProxyAuthenticator proxyAuthenticator = new ProxyAuthenticator(configuration); final List proxies = new ArrayList<>(); proxies.add(proxy); when(proxySelector.select(argThat(u -> u != null && u.getPath().equals(PROXY_ADDRESS)))) .thenReturn(proxies); // Act PasswordAuthentication authentication = proxyAuthenticator.getPasswordAuthentication(scheme, PROXY_ADDRESS); // Assert Assert.assertNotNull(authentication); Assert.assertEquals(USERNAME, authentication.getUserName()); Assert.assertArrayEquals(PASSWORD_CHAR_ARRAY, authentication.getPassword()); Assert.assertEquals(host, authenticator.requestingHost()); Assert.assertNull(authenticator.requestingProtocol()); Assert.assertNull(authenticator.requestingSite()); Assert.assertEquals(scheme, authenticator.requestingScheme()); Assert.assertEquals(Authenticator.RequestorType.PROXY, authenticator.requestorType()); } } ProxyConfigurationTest.java000066400000000000000000000101641460332530100447720ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; import com.microsoft.azure.proton.transport.proxy.ProxyAuthenticationType; import com.microsoft.azure.proton.transport.proxy.ProxyConfiguration; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.theories.DataPoints; import org.junit.experimental.theories.FromDataPoints; import org.junit.experimental.theories.Theories; import org.junit.experimental.theories.Theory; import org.junit.runner.RunWith; import java.net.InetSocketAddress; import java.net.Proxy; @RunWith(value = Theories.class) public class ProxyConfigurationTest { private static final String USERNAME = "test-user"; private static final String PASSWORD = "test-password!"; private static final char[] PASSWORD_CHARS = PASSWORD.toCharArray(); private static final InetSocketAddress PROXY_ADDRESS = InetSocketAddress.createUnresolved("foo.proxy.com", 3138); private static final Proxy PROXY = new Proxy(Proxy.Type.HTTP, PROXY_ADDRESS); private static final ProxyAuthenticationType AUTHENTICATION_TYPE = ProxyAuthenticationType.BASIC; @DataPoints("userConfigurations") public static ProxyConfiguration[] userConfigurations() { return new ProxyConfiguration[]{ new ProxyConfiguration(AUTHENTICATION_TYPE, PROXY, null, PASSWORD), new ProxyConfiguration(AUTHENTICATION_TYPE, PROXY, USERNAME, null), new ProxyConfiguration(AUTHENTICATION_TYPE, PROXY, null, null), }; } @Test public void systemConfiguredConfiguration() { ProxyConfiguration configuration = ProxyConfiguration.SYSTEM_DEFAULTS; Assert.assertFalse(configuration.isProxyAddressConfigured()); Assert.assertFalse(configuration.hasUserDefinedCredentials()); Assert.assertNull(configuration.proxyAddress()); Assert.assertNull(configuration.credentials()); Assert.assertNull(configuration.authentication()); } @Test public void userDefinedConfiguration() { ProxyConfiguration configuration = new ProxyConfiguration(AUTHENTICATION_TYPE, PROXY, USERNAME, PASSWORD); Assert.assertTrue(configuration.isProxyAddressConfigured()); Assert.assertTrue(configuration.hasUserDefinedCredentials()); Assert.assertEquals(AUTHENTICATION_TYPE, configuration.authentication()); Assert.assertEquals(PROXY, configuration.proxyAddress()); Assert.assertEquals(USERNAME, configuration.credentials().getUserName()); Assert.assertArrayEquals(PASSWORD_CHARS, configuration.credentials().getPassword()); } /** * Verify that if the user has not provided a username or password, we cannot construct valid credentials from that. * * @param configuration Configuration to test out. */ @Theory public void userDefinedConfigurationMissingData(@FromDataPoints("userConfigurations") ProxyConfiguration configuration) { Assert.assertTrue(configuration.isProxyAddressConfigured()); Assert.assertFalse(configuration.hasUserDefinedCredentials()); Assert.assertNull(configuration.credentials()); Assert.assertEquals(AUTHENTICATION_TYPE, configuration.authentication()); Assert.assertEquals(PROXY, configuration.proxyAddress()); } /** * Verify that if the user has not provided a proxy address, we will use the system-wide configured proxy. */ @Test public void userDefinedConfigurationNoProxyAddress() { ProxyAuthenticationType type = ProxyAuthenticationType.DIGEST; ProxyConfiguration configuration = new ProxyConfiguration(type, null, USERNAME, PASSWORD); Assert.assertFalse(configuration.isProxyAddressConfigured()); Assert.assertTrue(configuration.hasUserDefinedCredentials()); Assert.assertEquals(type, configuration.authentication()); Assert.assertNotNull(configuration.credentials()); Assert.assertEquals(USERNAME, configuration.credentials().getUserName()); Assert.assertArrayEquals(PASSWORD_CHARS, configuration.credentials().getPassword()); } } ProxyHandlerImplTest.java000066400000000000000000000073051460332530100443650ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; import com.microsoft.azure.proton.transport.proxy.HttpStatusLine; import com.microsoft.azure.proton.transport.proxy.ProxyResponse; import org.junit.Assert; import org.junit.Test; import java.nio.ByteBuffer; import java.util.HashMap; import static com.microsoft.azure.proton.transport.proxy.impl.StringUtils.NEW_LINE; import static java.nio.charset.StandardCharsets.UTF_8; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class ProxyHandlerImplTest { @Test public void testCreateProxyRequest() { final String hostName = "testHostName"; final HashMap headers = new HashMap<>(); headers.put("header1", "headervalue1"); headers.put("header2", "headervalue2"); final ProxyHandlerImpl proxyHandler = new ProxyHandlerImpl(); final String actualProxyRequest = proxyHandler.createProxyRequest(hostName, headers); final String expectedProxyRequest = String.join("\r\n", "CONNECT testHostName HTTP/1.1", "Host: testHostName", "Connection: Keep-Alive", "header2: headervalue2", "header1: headervalue1", "\r\n"); Assert.assertEquals(expectedProxyRequest, actualProxyRequest); } @Test public void testValidateProxyResponseOnSuccess() { // Arrange final HttpStatusLine statusLine = HttpStatusLine.create("HTTP/1.1 200 Connection Established"); final ProxyResponse response = mock(ProxyResponse.class); when(response.isMissingContent()).thenReturn(false); when(response.getStatus()).thenReturn(statusLine); final ProxyHandlerImpl proxyHandler = new ProxyHandlerImpl(); // Act final boolean result = proxyHandler.validateProxyResponse(response); // Assert Assert.assertTrue(result); } @Test public void testValidateProxyResponseOnFailure() { // Arrange final HttpStatusLine statusLine = HttpStatusLine.create("HTTP/1.1 407 Proxy Auth Required"); final String contents = "[Fiddler] Proxy Authentication Required.
"; final ByteBuffer encoded = UTF_8.encode(contents); final ProxyResponse response = mock(ProxyResponse.class); when(response.isMissingContent()).thenReturn(false); when(response.getStatus()).thenReturn(statusLine); when(response.getContents()).thenReturn(encoded); when(response.getError()).thenReturn(contents); final ProxyHandlerImpl proxyHandler = new ProxyHandlerImpl(); // Act final boolean result = proxyHandler.validateProxyResponse(response); // Assert Assert.assertFalse(result); } @Test public void testValidateProxyResponseOnEmptyResponse() { final String emptyResponse = NEW_LINE + NEW_LINE; final ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.put(emptyResponse.getBytes(UTF_8)); buffer.flip(); final ProxyResponse response = mock(ProxyResponse.class); when(response.isMissingContent()).thenReturn(false); when(response.getStatus()).thenReturn(null); when(response.getContents()).thenReturn(buffer); when(response.getError()).thenReturn(emptyResponse); final ProxyHandlerImpl proxyHandler = new ProxyHandlerImpl(); // Act final boolean result = proxyHandler.validateProxyResponse(response); // Assert Assert.assertFalse(result); } } ProxyImplTest.java000066400000000000000000001406631460332530100430740ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; import com.microsoft.azure.proton.transport.proxy.Proxy; import com.microsoft.azure.proton.transport.proxy.ProxyAuthenticationType; import com.microsoft.azure.proton.transport.proxy.ProxyConfiguration; import com.microsoft.azure.proton.transport.proxy.ProxyHandler; import org.apache.qpid.proton.engine.Transport; import org.apache.qpid.proton.engine.TransportException; import org.apache.qpid.proton.engine.impl.TransportImpl; import org.apache.qpid.proton.engine.impl.TransportInput; import org.apache.qpid.proton.engine.impl.TransportOutput; import org.apache.qpid.proton.engine.impl.TransportWrapper; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.lang.reflect.Field; import java.net.Authenticator; import java.net.InetSocketAddress; import java.net.PasswordAuthentication; import java.net.ProxySelector; import java.net.SocketAddress; import java.net.URI; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import static com.microsoft.azure.proton.transport.proxy.impl.Constants.BASIC; import static com.microsoft.azure.proton.transport.proxy.impl.Constants.DIGEST; import static com.microsoft.azure.proton.transport.proxy.impl.Constants.PROXY_AUTHENTICATE; import static com.microsoft.azure.proton.transport.proxy.impl.Constants.PROXY_AUTHORIZATION; import static org.mockito.Mockito.any; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.isA; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; // \org\apache\qpid\proton\reactor\impl\IOHandler.java > connectionReadable and connectionWriteable // methods are the starting point which invokes all methods of TransportInput and TransportOutput // classes - to implement transport layering. // Goal of this class is to test - expected outcomes of proxy transport layer // when these methods are invoked, and how ProxyState state transitions plays along. public class ProxyImplTest { private static final InetSocketAddress PROXY_ADDRESS = InetSocketAddress.createUnresolved("my.host.name", 8888); private static final java.net.Proxy PROXY = new java.net.Proxy(java.net.Proxy.Type.HTTP, PROXY_ADDRESS); private static final int BUFFER_SIZE = Constants.PROXY_HANDSHAKE_BUFFER_SIZE; private static final String USERNAME = "test-user"; private static final String PASSWORD = "test-password!"; private static final String BASIC_HEADER = BASIC; private static final String DIGEST_HEADER = String.format("%s realm=\"%s\", nonce=\"A randomly set nonce.\", qop=\"auth\", stale=false", DIGEST, PROXY); private final Logger logger = LoggerFactory.getLogger(ProxyImplTest.class); private final Map headers = new HashMap<>(); private ProxySelector originalProxy; @Captor private ArgumentCaptor> additionalHeaders; private void initHeaders() { headers.put("header1", "value1"); headers.put("header2", "value2"); headers.put("header3", "value3"); } @Before public void setup() { MockitoAnnotations.initMocks(this); originalProxy = ProxySelector.getDefault(); ProxySelector.setDefault(new ProxySelector() { @Override public List select(URI uri) { List proxies = new ArrayList<>(); proxies.add(PROXY); return proxies; } @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { if (logger.isErrorEnabled()) { logger.error("PROXY CONNECTION FAILED: URI = {}, Socket Address = {}, IO Exception = {}", uri.toString(), sa.toString(), ioe.toString()); } } }); Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { if (getRequestorType() == Authenticator.RequestorType.PROXY) { return new PasswordAuthentication(USERNAME, PASSWORD.toCharArray()); } return super.getPasswordAuthentication(); } }); } @After public void teardown() { Mockito.framework().clearInlineMocks(); ProxySelector.setDefault(originalProxy); } @Test public void testConstructor() { ProxyImpl proxyImpl = new ProxyImpl(); Assert.assertEquals(BUFFER_SIZE, proxyImpl.getInputBuffer().capacity()); Assert.assertEquals(BUFFER_SIZE, proxyImpl.getOutputBuffer().capacity()); Assert.assertFalse(proxyImpl.getIsProxyConfigured()); } @Test public void testConstructorOverload() { final ProxyConfiguration configuration = new ProxyConfiguration(ProxyAuthenticationType.DIGEST, PROXY, USERNAME, PASSWORD); final ProxyImpl proxyImpl = new ProxyImpl(configuration); Assert.assertEquals(BUFFER_SIZE, proxyImpl.getInputBuffer().capacity()); Assert.assertEquals(BUFFER_SIZE, proxyImpl.getOutputBuffer().capacity()); Assert.assertFalse(proxyImpl.getIsProxyConfigured()); } @Test public void testConfigure() { ProxyImpl proxyImpl = new ProxyImpl(); ProxyHandlerImpl proxyHandler = mock(ProxyHandlerImpl.class); TransportImpl transport = mock(TransportImpl.class); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, proxyHandler, transport); Assert.assertTrue(proxyImpl.getIsProxyConfigured()); Assert.assertEquals(proxyHandler, proxyImpl.getProxyHandler()); Assert.assertEquals(transport, proxyImpl.getUnderlyingTransport()); Assert.assertEquals(headers, proxyImpl.getProxyRequestHeaders()); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_NOT_STARTED, proxyImpl.getProxyState()); } @Test public void testWriteProxyRequest() { initHeaders(); int expectedRequestLength = getConnectRequestLength(PROXY_ADDRESS.getHostName(), headers); ProxyHandlerImpl spyProxyHandler = spy(new ProxyHandlerImpl()); TransportImpl transport = mock(TransportImpl.class); ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, spyProxyHandler, transport); proxyImpl.writeProxyRequest(); verify(spyProxyHandler, times(1)).createProxyRequest(PROXY_ADDRESS.getHostName(), headers); ByteBuffer outputBuffer = proxyImpl.getOutputBuffer(); outputBuffer.flip(); Assert.assertEquals(expectedRequestLength, outputBuffer.remaining()); } @Test public void testProxyHandshakeStatesBeforeConfigure() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); Assert.assertFalse(proxyImpl.getIsHandshakeInProgress()); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_NOT_STARTED); Assert.assertFalse(proxyImpl.getIsHandshakeInProgress()); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTING); Assert.assertFalse(proxyImpl.getIsHandshakeInProgress()); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTED); Assert.assertFalse(proxyImpl.getIsHandshakeInProgress()); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_FAILED); Assert.assertFalse(proxyImpl.getIsHandshakeInProgress()); } @Test public void testProxyHandshakeStatesAfterConfigure() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); ProxyHandlerImpl proxyHandler = mock(ProxyHandlerImpl.class); TransportImpl transport = mock(TransportImpl.class); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, proxyHandler, transport); Assert.assertTrue(proxyImpl.getIsHandshakeInProgress()); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTING); Assert.assertTrue(proxyImpl.getIsHandshakeInProgress()); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTED); Assert.assertFalse(proxyImpl.getIsHandshakeInProgress()); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_FAILED); Assert.assertTrue(proxyImpl.getIsHandshakeInProgress()); } @Test public void testPendingWhenProxyStateIsNotStarted() { initHeaders(); int expectedRequestLength = getConnectRequestLength(PROXY_ADDRESS.getHostName(), headers); ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, new ProxyHandlerImpl(), mock(TransportImpl.class)); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_NOT_STARTED, proxyImpl.getProxyState()); TransportOutput mockOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mockOutput); int bytesCount = transportWrapper.pending(); Assert.assertEquals(expectedRequestLength, transportWrapper.head().remaining()); ByteBuffer outputBuffer = proxyImpl.getOutputBuffer(); outputBuffer.flip(); Assert.assertEquals(expectedRequestLength, transportWrapper.head().remaining()); Assert.assertEquals(expectedRequestLength, outputBuffer.remaining()); Assert.assertEquals(expectedRequestLength, bytesCount); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTING, proxyImpl.getProxyState()); verify(mockOutput, times(0)).pending(); } @Test public void testPendingWhenProxyStateIsNotStartedAndOutputBufferIsNotEmpty() { initHeaders(); ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, new ProxyHandlerImpl(), mock(TransportImpl.class)); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_NOT_STARTED, proxyImpl.getProxyState()); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mock(TransportOutput.class)); String message = "olddata"; ByteBuffer outputBuffer = proxyImpl.getOutputBuffer(); outputBuffer.put(message.getBytes()); int bytesCount = transportWrapper.pending(); outputBuffer.flip(); Assert.assertEquals(message.length(), outputBuffer.remaining()); Assert.assertEquals(message.length(), bytesCount); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_NOT_STARTED, proxyImpl.getProxyState()); } @Test public void testPendingWhenProxyStateIsConnecting() { initHeaders(); int expectedRequestLength = getConnectRequestLength(PROXY_ADDRESS.getHostName(), headers); ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, new ProxyHandlerImpl(), mock(TransportImpl.class)); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mock(TransportOutput.class)); transportWrapper.pending(); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTING, proxyImpl.getProxyState()); for (int i = 0; i < 10; i++) { transportWrapper.pending(); } Assert.assertEquals(expectedRequestLength, transportWrapper.head().remaining()); ByteBuffer outputBuffer = proxyImpl.getOutputBuffer(); outputBuffer.flip(); Assert.assertEquals(expectedRequestLength, outputBuffer.remaining()); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTING, proxyImpl.getProxyState()); } @Test public void testPendingWhenProxyStateIsConnected() throws Exception { initHeaders(); ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, new ProxyHandlerImpl(), mock(TransportImpl.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTED); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mock(TransportOutput.class)); transportWrapper.pending(); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTED, proxyImpl.getProxyState()); } @Test public void testPendingWhenProxyStateIsFailed() throws Exception { initHeaders(); ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, new ProxyHandlerImpl(), mock(TransportImpl.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_FAILED); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mock(TransportOutput.class)); Assert.assertEquals(-1, transportWrapper.pending()); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_FAILED, proxyImpl.getProxyState()); } @Test public void testProcessWhenProxyStateNotStarted() { initHeaders(); ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, new ProxyHandlerImpl(), mock(TransportImpl.class)); TransportInput mockInput = mock(TransportInput.class); TransportWrapper transportWrapper = proxyImpl.wrap(mockInput, mock(TransportOutput.class)); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_NOT_STARTED, proxyImpl.getProxyState()); transportWrapper.process(); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_NOT_STARTED, proxyImpl.getProxyState()); verify(mockInput, times(1)).process(); } @Test public void testProcessWhenProxyStateConnectingTransitionsToConnectedOnValidResponse() { ProxyImpl proxyImpl = new ProxyImpl(); ProxyHandler mockHandler = mock(ProxyHandler.class); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mockHandler, mock(TransportImpl.class)); TransportInput mockInput = mock(TransportInput.class); TransportWrapper transportWrapper = proxyImpl.wrap(mockInput, mock(TransportOutput.class)); when(mockHandler.createProxyRequest(any(), any())).thenReturn("proxy request"); when(mockHandler.validateProxyResponse(any())).thenReturn(true); final String[] statusLine = new String[]{"HTTP/1.1", "200", "Connection Established"}; String response = getProxyResponse(statusLine, new ArrayList<>()); setInputBuffer(proxyImpl, response); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_NOT_STARTED, proxyImpl.getProxyState()); transportWrapper.pending(); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTING, proxyImpl.getProxyState()); transportWrapper.process(); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTED, proxyImpl.getProxyState()); } @Test public void testProcessProxyStateConnectingFailureLeadsToUnderlyingTransportClosed() { ProxyImpl proxyImpl = new ProxyImpl(); ProxyHandler mockHandler = mock(ProxyHandler.class); TransportImpl mockTransport = mock(TransportImpl.class); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mockHandler, mockTransport); TransportInput mockInput = mock(TransportInput.class); TransportWrapper transportWrapper = proxyImpl.wrap(mockInput, mock(TransportOutput.class)); when(mockHandler.createProxyRequest(any(), any())).thenReturn("proxy request"); final String[] statusLine = new String[]{"HTTP/1.1", "500", "Internal Server Error"}; final List authentications = new ArrayList<>(); String response = getProxyResponse(statusLine, authentications); setInputBuffer(proxyImpl, response); when(mockHandler.validateProxyResponse(any())).thenReturn(false); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_NOT_STARTED, proxyImpl.getProxyState()); transportWrapper.pending(); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTING, proxyImpl.getProxyState()); transportWrapper.process(); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTING, proxyImpl.getProxyState()); verify(mockTransport, times(1)).closed(isA(TransportException.class)); } @Test public void testProcessProxyStateIsConnected() throws Exception { initHeaders(); ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTED); TransportInput mockInput = mock(TransportInput.class); TransportWrapper transportWrapper = proxyImpl.wrap(mockInput, mock(TransportOutput.class)); transportWrapper.process(); verify(mockInput, times(1)).process(); } @Test public void testProcessProxyStateIsFailed() throws Exception { initHeaders(); ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_FAILED); TransportInput mockInput = mock(TransportInput.class); TransportWrapper transportWrapper = proxyImpl.wrap(mockInput, mock(TransportOutput.class)); transportWrapper.process(); verify(mockInput, times(1)).process(); } @Test public void testPopProxyStateIsNotStarted() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_NOT_STARTED); TransportOutput mockOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mockOutput); transportWrapper.pop(20); verify(mockOutput, times(1)).pop(20); } @Test public void testPopProxyStateConnecting() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTING); ByteBuffer outputBuffer = proxyImpl.getOutputBuffer(); byte[] outputBufferData = "test pop moves position".getBytes(); outputBuffer.put(outputBufferData); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mock(TransportOutput.class)); Assert.assertEquals(outputBufferData.length, outputBuffer.position()); transportWrapper.pop(5); Assert.assertEquals(outputBufferData.length - 5, outputBuffer.position()); Assert.assertEquals(BUFFER_SIZE - outputBufferData.length + 5, outputBuffer.remaining()); ByteBuffer head = transportWrapper.head(); Assert.assertEquals(0, head.position()); Assert.assertEquals(outputBufferData.length - 5, head.remaining()); } @Test public void testPopProxyStateIsConnected() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTED); TransportOutput mockOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mockOutput); transportWrapper.pop(20); verify(mockOutput, times(1)).pop(20); } @Test public void testPopProxyStateIsFailed() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_FAILED); TransportOutput mockOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mockOutput); transportWrapper.pop(20); verify(mockOutput, times(1)).pop(20); } @Test public void testTailReturnsCurrentInputBufferExceptProxyStateIsConnected() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportInput mockInput = mock(TransportInput.class); TransportWrapper transportWrapper = proxyImpl.wrap(mockInput, mock(TransportOutput.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_NOT_STARTED); Assert.assertSame(proxyImpl.getInputBuffer(), transportWrapper.tail()); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTING); Assert.assertSame(proxyImpl.getInputBuffer(), transportWrapper.tail()); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_FAILED); Assert.assertSame(proxyImpl.getInputBuffer(), transportWrapper.tail()); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTED); Assert.assertNotSame(proxyImpl.getInputBuffer(), transportWrapper.tail()); verify(mockInput, times(1)).tail(); } @Test public void testHeadDelegatesToUnderlyingOutputWhenProxyStateIsConnected() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportOutput mockOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mockOutput); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTED); transportWrapper.head(); verify(mockOutput, times(1)).head(); } @Test public void testPositionWhenProxyStateIsNotStarted() { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mock(TransportOutput.class)); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_NOT_STARTED, proxyImpl.getProxyState()); Assert.assertEquals(0, transportWrapper.position()); } @Test public void testPositionWhenProxyStateIsNotStartedAndTailClosed() { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mock(TransportOutput.class)); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_NOT_STARTED, proxyImpl.getProxyState()); transportWrapper.close_tail(); Assert.assertEquals(Transport.END_OF_STREAM, transportWrapper.capacity()); } @Test public void testPositionWhenProxyStateIsConnecting() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mock(TransportOutput.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTING); Assert.assertEquals(0, transportWrapper.position()); } @Test public void testPositionWhenProxyStateIsConnectingAndTailClosed() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mock(TransportOutput.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTING); transportWrapper.close_tail(); Assert.assertEquals(Transport.END_OF_STREAM, transportWrapper.position()); } @Test public void testPositionWhenProxyStateIsConnected() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportInput mockInput = mock(TransportInput.class); TransportWrapper transportWrapper = proxyImpl.wrap(mockInput, mock(TransportOutput.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTED); transportWrapper.position(); verify(mockInput, times(1)).position(); } @Test public void testPositionWhenProxyStateIsConnectedAndTailClosed() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportInput mockInput = mock(TransportInput.class); TransportWrapper transportWrapper = proxyImpl.wrap(mockInput, mock(TransportOutput.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTED); transportWrapper.close_tail(); transportWrapper.position(); verify(mockInput, times(1)).position(); } @Test public void testPositionWhenProxyStateIsFailed() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mock(TransportOutput.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_FAILED); Assert.assertEquals(0, transportWrapper.position()); } @Test public void testPositionWhenProxyStateIsFailedAndTailClosed() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mock(TransportOutput.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_FAILED); transportWrapper.close_tail(); Assert.assertEquals(Transport.END_OF_STREAM, transportWrapper.position()); } @Test public void testCapacityWhenProxyStateIsNotStarted() { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mock(TransportOutput.class)); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_NOT_STARTED, proxyImpl.getProxyState()); Assert.assertEquals(BUFFER_SIZE, transportWrapper.capacity()); } @Test public void testCapacityWhenProxyStateIsNotStartedAndTailClosed() { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mock(TransportOutput.class)); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_NOT_STARTED, proxyImpl.getProxyState()); transportWrapper.close_tail(); Assert.assertEquals(Transport.END_OF_STREAM, transportWrapper.capacity()); } @Test public void testCapacityWhenProxyStateIsConnecting() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mock(TransportOutput.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTING); Assert.assertEquals(BUFFER_SIZE, transportWrapper.capacity()); } @Test public void testCapacityWhenProxyStateIsConnectingAndTailClosed() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mock(TransportOutput.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTING); transportWrapper.close_tail(); Assert.assertEquals(Transport.END_OF_STREAM, transportWrapper.capacity()); } @Test public void testCapacityWhenProxyStateIsConnected() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportInput mockInput = mock(TransportInput.class); TransportWrapper transportWrapper = proxyImpl.wrap(mockInput, mock(TransportOutput.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTED); transportWrapper.capacity(); verify(mockInput, times(1)).capacity(); } @Test public void testCapacityWhenProxyStateIsConnectedAndTailClosed() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportInput mockInput = mock(TransportInput.class); TransportWrapper transportWrapper = proxyImpl.wrap(mockInput, mock(TransportOutput.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_CONNECTED); transportWrapper.close_tail(); transportWrapper.capacity(); verify(mockInput, times(1)).capacity(); } @Test public void testCapacityWhenProxyStateIsFailed() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mock(TransportOutput.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_FAILED); Assert.assertEquals(BUFFER_SIZE, transportWrapper.capacity()); } @Test public void testCapacityWhenProxyStateIsFailedAndTailClosed() throws Exception { ProxyImpl proxyImpl = new ProxyImpl(); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, mock(ProxyHandler.class), mock(TransportImpl.class)); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), mock(TransportOutput.class)); setProxyState(proxyImpl, Proxy.ProxyState.PN_PROXY_FAILED); transportWrapper.close_tail(); Assert.assertEquals(Transport.END_OF_STREAM, transportWrapper.capacity()); } /** * Verifies that if we explicitly set ProxyAuthenticationType.NONE and the proxy asks for verification then we fail. * This also covers the case where the proxy configuration suggests one auth method, but it is not supported in the * proxy challenge. */ @Test public void authenticationTypeNoneClosesTail() { // Arrange ProxyConfiguration configuration = new ProxyConfiguration(ProxyAuthenticationType.NONE, PROXY, USERNAME, PASSWORD); ProxyImpl proxyImpl = new ProxyImpl(configuration); ProxyHandler handler = mock(ProxyHandler.class); TransportImpl underlyingTransport = mock(TransportImpl.class); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, handler, underlyingTransport); TransportInput input = mock(TransportInput.class); TransportWrapper transportWrapper = proxyImpl.wrap(input, mock(TransportOutput.class)); when(handler.createProxyRequest(any(), any())).thenReturn("proxy request"); when(handler.validateProxyResponse(any())).thenReturn(false); final String[] statusLine = new String[]{"HTTP/1.1", "407", "Proxy Authentication Required"}; final List authentications = new ArrayList<>(); authentications.add(BASIC_HEADER); final String response = getProxyResponse(statusLine, authentications); setInputBuffer(proxyImpl, response); // Act and Assert Assert.assertEquals(Proxy.ProxyState.PN_PROXY_NOT_STARTED, proxyImpl.getProxyState()); transportWrapper.pending(); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTING, proxyImpl.getProxyState()); transportWrapper.process(); verify(underlyingTransport, times(1)).closed(isA(TransportException.class)); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTING, proxyImpl.getProxyState()); } /** * Verifies that if we configure proxy authentication type but the proxy does not ask for verification then we fail. */ @Test public void authenticationNoAuthMismatchClosesTail() { // Arrange ProxyConfiguration configuration = new ProxyConfiguration(ProxyAuthenticationType.BASIC, PROXY, USERNAME, PASSWORD); ProxyImpl proxyImpl = new ProxyImpl(configuration); ProxyHandler handler = mock(ProxyHandler.class); TransportImpl underlyingTransport = mock(TransportImpl.class); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, handler, underlyingTransport); TransportInput input = mock(TransportInput.class); TransportWrapper transportWrapper = proxyImpl.wrap(input, mock(TransportOutput.class)); when(handler.createProxyRequest(any(), any())).thenReturn("proxy request"); when(handler.validateProxyResponse(any())).thenReturn(true); final String[] statusLine = new String[]{"HTTP/1.1", "200", "Connection Established"}; String response = getProxyResponse(statusLine, new ArrayList<>()); setInputBuffer(proxyImpl, response); // Act and Assert Assert.assertEquals(Proxy.ProxyState.PN_PROXY_NOT_STARTED, proxyImpl.getProxyState()); transportWrapper.pending(); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTING, proxyImpl.getProxyState()); transportWrapper.process(); verify(underlyingTransport, times(1)).closed(isA(TransportException.class)); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTING, proxyImpl.getProxyState()); } /** * Verifies that we can pass in a proxy configuration and connect to the proxy when the challenge contains the * configured auth method. */ @Test public void authenticationWithProxyConfiguration() { // Arrange ProxyConfiguration configuration = new ProxyConfiguration(ProxyAuthenticationType.BASIC, PROXY, USERNAME, PASSWORD); ProxyImpl proxyImpl = new ProxyImpl(configuration); ProxyHandler handler = mock(ProxyHandler.class); TransportImpl underlyingTransport = mock(TransportImpl.class); TransportOutput output = mock(TransportOutput.class); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, handler, underlyingTransport); TransportWrapper transportWrapper = proxyImpl.wrap(mock(TransportInput.class), output); when(handler.createProxyRequest(any(), any())).thenReturn("proxy request", "proxy request2"); when(handler.validateProxyResponse(any())).thenReturn(false, true); String[] statusLine = new String[]{"HTTP/1.1", "407", "Proxy Authentication Required"}; List authentications = new ArrayList<>(); authentications.add(BASIC_HEADER); authentications.add(DIGEST_HEADER); String response = getProxyResponse(statusLine, authentications); setInputBuffer(proxyImpl, response); // Act and Assert Assert.assertEquals(Proxy.ProxyState.PN_PROXY_NOT_STARTED, proxyImpl.getProxyState()); transportWrapper.pending(); Assert.assertTrue(proxyImpl.getIsHandshakeInProgress()); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTING, proxyImpl.getProxyState()); transportWrapper.process(); // At this point, we've gotten the correct challenger and set the header we want to respond with. We want to // zero out the output buffer so that it'll write the headers when getting the request from the proxy handler. Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CHALLENGE, proxyImpl.getProxyState()); clearOutputBuffer(proxyImpl); transportWrapper.pending(); statusLine = new String[]{"HTTP/1.1", "200", "Connection Established"}; response = getProxyResponse(statusLine, new ArrayList<>()); setInputBuffer(proxyImpl, response); Assert.assertTrue(proxyImpl.getIsHandshakeInProgress()); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CHALLENGE_RESPONDED, proxyImpl.getProxyState()); transportWrapper.process(); Assert.assertFalse(proxyImpl.getIsHandshakeInProgress()); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTED, proxyImpl.getProxyState()); verify(handler, times(2)).createProxyRequest( argThat(string -> string != null && string.equals(PROXY_ADDRESS.getHostName())), additionalHeaders.capture()); final Optional> matching = additionalHeaders.getAllValues() .stream() .filter(map -> map.containsKey(PROXY_AUTHORIZATION) && map.get(PROXY_AUTHORIZATION).trim().startsWith(BASIC)) .findFirst(); Assert.assertTrue(matching.isPresent()); } /** * Verifies that when we use the system defaults and both are offered, then we will use the DIGEST. */ @Test public void authenticationWithSystemDefaults() { // Arrange ProxyImpl proxyImpl = new ProxyImpl(); ProxyHandler handler = mock(ProxyHandler.class); TransportImpl underlyingTransport = mock(TransportImpl.class); TransportOutput output = mock(TransportOutput.class); TransportInput transportInput = mock(TransportInput.class); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, handler, underlyingTransport); TransportWrapper transportWrapper = proxyImpl.wrap(transportInput, output); when(handler.createProxyRequest(any(), any())).thenReturn("proxy request", "proxy request2"); when(handler.validateProxyResponse(any())).thenReturn(false, true); String[] statusLine = new String[]{"HTTP/1.1", "407", "Proxy Authentication Required"}; List authentications = new ArrayList<>(); authentications.add(BASIC_HEADER); authentications.add(DIGEST_HEADER); String response = getProxyResponse(statusLine, authentications); setInputBuffer(proxyImpl, response); // Act and Assert Assert.assertEquals(Proxy.ProxyState.PN_PROXY_NOT_STARTED, proxyImpl.getProxyState()); transportWrapper.pending(); Assert.assertTrue(proxyImpl.getIsHandshakeInProgress()); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTING, proxyImpl.getProxyState()); transportWrapper.process(); // At this point, we've gotten the correct challenger and set the header we want to respond with. We want to // zero out the output buffer so that it'll write the headers when getting the request from the proxy handler. Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CHALLENGE, proxyImpl.getProxyState()); clearOutputBuffer(proxyImpl); transportWrapper.pending(); statusLine = new String[]{"HTTP/1.1", "200", "Connection Established"}; response = getProxyResponse(statusLine, new ArrayList<>()); setInputBuffer(proxyImpl, response); Assert.assertTrue(proxyImpl.getIsHandshakeInProgress()); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CHALLENGE_RESPONDED, proxyImpl.getProxyState()); transportWrapper.process(); Assert.assertFalse(proxyImpl.getIsHandshakeInProgress()); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTED, proxyImpl.getProxyState()); verify(handler, times(2)).createProxyRequest( argThat(string -> string != null && string.equals(PROXY_ADDRESS.getHostName())), additionalHeaders.capture()); final Optional> matching = additionalHeaders.getAllValues() .stream() .filter(map -> map.containsKey(PROXY_AUTHORIZATION) && map.get(PROXY_AUTHORIZATION).trim().startsWith(DIGEST)) .findFirst(); Assert.assertTrue(matching.isPresent()); } /** * Verifies that when proxy authentication response are transfer in multiple frames. */ @Test public void authenticationResponseWithMultipleFrames() { // Arrange ProxyImpl proxyImpl = new ProxyImpl(); ProxyHandler handler = mock(ProxyHandler.class); TransportImpl underlyingTransport = mock(TransportImpl.class); TransportOutput output = mock(TransportOutput.class); TransportInput transportInput = mock(TransportInput.class); proxyImpl.configure(PROXY_ADDRESS.getHostName(), headers, handler, underlyingTransport); TransportWrapper transportWrapper = proxyImpl.wrap(transportInput, output); when(handler.createProxyRequest(any(), any())).thenReturn("proxy request", "proxy request2"); when(handler.validateProxyResponse(any())).thenReturn(false, true); String[] statusLine = new String[]{"HTTP/1.1", "407", "Proxy Authentication Required"}; List authentications = new ArrayList<>(); authentications.add(BASIC_HEADER); authentications.add(DIGEST_HEADER); //Create a body which over buffer size so that it could be cut into multiple frames //Here body is (buffer size * 2) bytes, and consider header size, //it will create 3 frames for proxy response String body = new String(new char[BUFFER_SIZE]).replace('\0', 't'); List responses = getProxyResponseFrames(statusLine, authentications, body); // Act and Assert Assert.assertEquals(3, responses.size()); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_NOT_STARTED, proxyImpl.getProxyState()); transportWrapper.pending(); for (String response : responses) { setInputBuffer(proxyImpl, response); Assert.assertTrue(proxyImpl.getIsHandshakeInProgress()); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTING, proxyImpl.getProxyState()); transportWrapper.process(); } // At this point, we've gotten the correct challenger and set the header we want to respond with. We want to // zero out the output buffer so that it'll write the headers when getting the request from the proxy handler. Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CHALLENGE, proxyImpl.getProxyState()); clearOutputBuffer(proxyImpl); transportWrapper.pending(); statusLine = new String[]{"HTTP/1.1", "200", "Connection Established"}; String response = getProxyResponse(statusLine, new ArrayList<>()); setInputBuffer(proxyImpl, response); Assert.assertTrue(proxyImpl.getIsHandshakeInProgress()); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CHALLENGE_RESPONDED, proxyImpl.getProxyState()); transportWrapper.process(); Assert.assertFalse(proxyImpl.getIsHandshakeInProgress()); Assert.assertEquals(Proxy.ProxyState.PN_PROXY_CONNECTED, proxyImpl.getProxyState()); verify(handler, times(2)).createProxyRequest( argThat(string -> string != null && string.equals(PROXY_ADDRESS.getHostName())), additionalHeaders.capture()); final Optional> matching = additionalHeaders.getAllValues() .stream() .filter(map -> map.containsKey(PROXY_AUTHORIZATION) && map.get(PROXY_AUTHORIZATION).trim().startsWith(DIGEST)) .findFirst(); Assert.assertTrue(matching.isPresent()); } private static int getConnectRequestLength(String host, Map headers) { StringBuilder builder = new StringBuilder(String.format(Locale.ROOT, ProxyHandlerImpl.CONNECT_REQUEST, host, ProxyHandlerImpl.NEW_LINE)); if (headers != null) { headers.forEach((key, value) -> { builder.append(String.format(Locale.ROOT, ProxyHandlerImpl.HEADER_FORMAT, key, value)); builder.append(ProxyHandlerImpl.NEW_LINE); }); } builder.append(ProxyHandlerImpl.NEW_LINE); return builder.toString().getBytes(StandardCharsets.UTF_8).length; } private void setProxyState(ProxyImpl proxyImpl, Proxy.ProxyState proxyState) throws NoSuchFieldException, IllegalAccessException { Field proxyStateField = ProxyImpl.class.getDeclaredField("proxyState"); proxyStateField.setAccessible(true); proxyStateField.set(proxyImpl, proxyState); Assert.assertEquals(proxyState, proxyImpl.getProxyState()); } private void clearOutputBuffer(ProxyImpl proxyImpl) { // Clears the "ProxyImpl.outputBuffer" field. final String outputBufferName = "outputBuffer"; Field outputBuffer; ByteBuffer buffer; try { outputBuffer = ProxyImpl.class.getDeclaredField(outputBufferName); outputBuffer.setAccessible(true); buffer = (ByteBuffer) outputBuffer.get(proxyImpl); buffer.clear(); } catch (NoSuchFieldException e) { if (logger.isErrorEnabled()) { logger.error("Could not locate field '{}' on ProxyImpl class. Exception: {}", outputBufferName, e); } } catch (IllegalAccessException e) { if (logger.isErrorEnabled()) { logger.error("Could not fetch byte buffer from object.", e); } } } private void setInputBuffer(ProxyImpl proxyImpl, String value) { final String inputBufferName = "inputBuffer"; try { Field inputBuffer = ProxyImpl.class.getDeclaredField(inputBufferName); inputBuffer.setAccessible(true); ByteBuffer buffer = (ByteBuffer) inputBuffer.get(proxyImpl); buffer.put(value.getBytes()); } catch (NoSuchFieldException e) { if (logger.isErrorEnabled()) { logger.error("Could not locate field '{}' on ProxyImpl class. Exception: {}", inputBufferName, e); } } catch (IllegalAccessException e) { if (logger.isErrorEnabled()) { logger.error("Could not fetch byte buffer from object.", e); } } } private String getProxyResponse(String[] statusLine, List authentications) { final Map> headers = new HashMap<>(); if (!authentications.isEmpty()) { headers.put(PROXY_AUTHENTICATE, authentications); } return TestUtils.createProxyResponse(statusLine, headers); } private List getProxyResponseFrames(String[] statusLine, List authentications, String body) { final Map> headers = new HashMap<>(); if (!authentications.isEmpty()) { headers.put(PROXY_AUTHENTICATE, authentications); } String response = TestUtils.createProxyResponse(statusLine, headers, body); //Split response into frames base on buffer in characters size int characters = BUFFER_SIZE / 2; List frames = new ArrayList<>(); for (int i = 0; i < response.length(); i += characters) { frames.add(response.substring(i, Math.min(i + characters, response.length()))); } return frames; } } ProxyResponseImplTest.java000066400000000000000000000103761460332530100446100ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; import com.microsoft.azure.proton.transport.proxy.HttpStatusLine; import com.microsoft.azure.proton.transport.proxy.ProxyResponse; import org.junit.Assert; import org.junit.Test; import java.nio.ByteBuffer; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.microsoft.azure.proton.transport.proxy.impl.StringUtils.NEW_LINE; public class ProxyResponseImplTest { /** * Verifies that it successfully parses a valid HTTP response. */ @Test public void validResponse() { // Arrange final String[] statusLine = new String[]{"HTTP/1.1", "200", "Connection Established"}; final Map> headers = new HashMap<>(); headers.put("FiddlerGateway", Collections.singletonList("Direct")); headers.put("StartTime", Collections.singletonList("13:08:21.574")); headers.put("Connection", Collections.singletonList("close")); final String response = TestUtils.createProxyResponse(statusLine, headers); final ByteBuffer contents = TestUtils.ENCODING.encode(response); // Act final ProxyResponse actual = ProxyResponseImpl.create(contents); // Assert Assert.assertNotNull(actual); final HttpStatusLine status = actual.getStatus(); Assert.assertEquals("1.1", status.getProtocolVersion()); Assert.assertEquals(statusLine[2], status.getReason()); Assert.assertEquals(Integer.parseInt(statusLine[1]), status.getStatusCode()); Assert.assertFalse(actual.isMissingContent()); final Map> actualHeaders = actual.getHeaders(); Assert.assertEquals(headers.size(), actualHeaders.size()); headers.forEach((key, value) -> { final List actualValue = actualHeaders.get(key); Assert.assertTrue(actualHeaders.containsKey(key)); Assert.assertNotNull(actualValue); Assert.assertEquals(1, actualValue.size()); Assert.assertEquals(value.get(0), actualValue.get(0)); }); } /** * Verifies that an exception is thrown when buffer is empty */ @Test public void invalidBuffer() { // Arrange final ByteBuffer contents = ByteBuffer.allocate(0); // Act & Assert Assert.assertThrows(IllegalArgumentException.class, () -> ProxyResponseImpl.create(contents)); } /** * Verifies that an exception is thrown when the header is invalid. */ @Test public void invalidHeader() { // Arrange final String[] statusLine = new String[]{"HTTP/1.1", "abc", "Connection Established"}; final Map> headers = new HashMap<>(); headers.put("FiddlerGateway", Collections.singletonList("Direct")); headers.put("StartTime", Collections.singletonList("13:08:21.574")); headers.put("Connection", Collections.singletonList("close")); final String response = TestUtils.createProxyResponse(statusLine, headers); final ByteBuffer contents = TestUtils.ENCODING.encode(response); // Act & Assert IllegalArgumentException thrown = Assert.assertThrows(IllegalArgumentException.class, () -> ProxyResponseImpl.create(contents)); Assert.assertEquals(NumberFormatException.class, thrown.getCause().getClass()); } /** * Verifies that we can parse an empty response. */ @Test public void emptyResponse() { // Arrange final String emptyResponse = NEW_LINE + NEW_LINE; final ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.put(emptyResponse.getBytes(TestUtils.ENCODING)); buffer.flip(); // Act final ProxyResponse response = ProxyResponseImpl.create(buffer); // Assert Assert.assertNotNull(response); Assert.assertNull(response.getStatus()); Assert.assertFalse(response.isMissingContent()); Assert.assertNotNull(response.getHeaders()); Assert.assertEquals(0, response.getHeaders().size()); Assert.assertEquals(0, response.getContents().position()); } } TestAuthenticator.java000066400000000000000000000032331460332530100437320ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; import java.net.Authenticator; import java.net.InetAddress; import java.net.PasswordAuthentication; import java.net.URL; import java.util.Arrays; /** * Test authenticator we can use to test what fields are set when calling * {@link Authenticator#requestPasswordAuthentication(String, InetAddress, int, String, String, String)}. */ class TestAuthenticator extends Authenticator implements AutoCloseable { private final PasswordAuthentication passwordAuthentication; TestAuthenticator(String username, String password) { passwordAuthentication = new PasswordAuthentication(username, password.toCharArray()); } String requestingHost() { return getRequestingHost(); } InetAddress requestingSite() { return getRequestingSite(); } int requestingPort() { return getRequestingPort(); } String requestingProtocol() { return getRequestingProtocol(); } String requestingPrompt() { return getRequestingPrompt(); } String requestingScheme() { return getRequestingScheme(); } URL requestingURL() { return getRequestingURL(); } RequestorType requestorType() { return getRequestorType(); } @Override protected PasswordAuthentication getPasswordAuthentication() { return passwordAuthentication; } @Override public void close() { if (passwordAuthentication != null) { Arrays.fill(passwordAuthentication.getPassword(), '\0'); } } } TestUtils.java000066400000000000000000000057241460332530100422270ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transport/proxy/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.proxy.impl; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import java.util.Map; import static com.microsoft.azure.proton.transport.proxy.impl.StringUtils.NEW_LINE; final class TestUtils { /** * Encoding for the HTTP proxy response. */ static final Charset ENCODING = StandardCharsets.UTF_8; private static final String CONTENT_TYPE = "Content-Type"; private static final String CONTENT_TYPE_TEXT = "text/plain"; private static final String CONTENT_LENGTH = "Content-Length"; private static final String HEADER_FORMAT = "%s: %s" + NEW_LINE; private TestUtils() { } /** * Creates a proxy HTTP response and returns it as a string. * * @param statusLine HTTP status line to create the proxy response with. * @param headers A set of headers to add to the proxy response. * @return A string representing the contents of the HTTP response. */ static String createProxyResponse(String[] statusLine, Map> headers) { return createProxyResponse(statusLine, headers, null); } /** * Creates a proxy HTTP response and returns it as a string. If there is content, {@link #CONTENT_LENGTH} and * {@link #CONTENT_TYPE} headers are added to the {@code headers} parameter. * * @param statusLine HTTP status line to create the proxy response with. * @param headers A set of headers to add to the proxy response. * @param body Optional HTTP content body. * @return A string representing the contents of the HTTP response. */ static String createProxyResponse(String[] statusLine, Map> headers, String body) { final ByteBuffer encoded; if (body != null) { //Add empty line to end body body += NEW_LINE; encoded = ENCODING.encode(body); final int size = encoded.remaining(); headers.put(CONTENT_TYPE, Collections.singletonList(CONTENT_TYPE_TEXT)); headers.put(CONTENT_LENGTH, Collections.singletonList(Integer.toString(size))); } final StringBuilder formattedHeaders = headers.entrySet() .stream() .collect(StringBuilder::new, (builder, entry) -> entry.getValue() .forEach(value -> builder.append(String.format(HEADER_FORMAT, entry.getKey(), value))), StringBuilder::append); String response = String.join(NEW_LINE, String.join(" ", statusLine), formattedHeaders.toString(), NEW_LINE); // The empty new line that ends the HTTP headers. if (body != null) { response += body; } return response; } } ws/000077500000000000000000000000001460332530100357235ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transportimpl/000077500000000000000000000000001460332530100366645ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transport/wsBase64Test.java000066400000000000000000000272421460332530100414220ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transport/ws/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.ws.impl; import org.junit.Test; import java.io.UnsupportedEncodingException; import static org.junit.Assert.assertEquals; /** * Unit tests for Base64 * 100% methods, 98% lines covered */ public class Base64Test { /** Tests_SRS_BASE64_21_001: [The decodeBase64Local shall decode the provided `base64Values` in a byte array * using the Base64 format define in the RFC2045.] */ @Test public void decodeBase64WithMultipleOf4CharactersSuccess() throws UnsupportedEncodingException, IllegalArgumentException { // arrange String base64ToDecode = "VGhpcyBpcyBhIHZhbGlkIHRlc3Q+Pj4+Pz8/PyhhQmNEZUZnSGlKS0xtbm9QcVJzdHVWV1h5eikwMTIzNDU2Nzg5"; String expectedTextResult = "This is a valid test>>>>????(aBcDeFgHiJKLmnoPqRstuVWXyz)0123456789"; // act byte[] result = Base64.decodeBase64Local(base64ToDecode.getBytes()); // assert assertEquals(expectedTextResult, new String(result)); } /** Tests_SRS_BASE64_21_001: [The decodeBase64Local shall decode the provided `base64Values` in a byte array * using the Base64 format define in the RFC2045.] */ @Test public void decodeBase64WithMultipleOf4Minus1CharactersSuccess() throws UnsupportedEncodingException, IllegalArgumentException { // arrange String base64ToDecode = "VGhpcyBpcyBhIHZhbGlkIHRlc3QgKGFCY0RlRmdIaUpLTG1ub1BxUnN0dVZXWHl6KS0wMTIzNDU2Nzg="; String expectedTextResult = "This is a valid test (aBcDeFgHiJKLmnoPqRstuVWXyz)-012345678"; // act byte[] result = Base64.decodeBase64Local(base64ToDecode.getBytes()); // assert assertEquals(expectedTextResult, new String(result)); } /** Tests_SRS_BASE64_21_001: [The decodeBase64Local shall decode the provided `base64Values` in a byte array * using the Base64 format define in the RFC2045.] */ @Test public void decodeBase64WithMultipleOf4Minus2CharactersSuccess() throws UnsupportedEncodingException, IllegalArgumentException { // arrange String base64ToDecode = "VGhpcyBpcyBhIHZhbGlkIHRlc3QgKGFCY0RlRmdIaUpLTG1ub1BxUnN0dVZXWHl6KS0wMTIzNDU2Nw=="; String expectedTextResult = "This is a valid test (aBcDeFgHiJKLmnoPqRstuVWXyz)-01234567"; // act byte[] result = Base64.decodeBase64Local(base64ToDecode.getBytes()); // assert assertEquals(expectedTextResult, new String(result)); } /* Tests_SRS_BASE64_21_002: [If the `base64Values` is null, the decodeBase64Local shall throw IllegalArgumentException.] */ @Test(expected = IllegalArgumentException.class) public void decodeBase64ThrowsOnNullByteArray() throws UnsupportedEncodingException, IllegalArgumentException { // arrange byte[] base64ToDecode = null; // act Base64.decodeBase64Local(base64ToDecode); } /* Tests_SRS_BASE64_21_003: [If the `base64Values` is empty, the decodeBase64Local shall return a empty byte array.] */ @Test public void decodeBase64WithEmptyByteArraySuccess() throws UnsupportedEncodingException, IllegalArgumentException { // arrange String base64ToDecode = ""; String expectedTextResult = ""; // act byte[] result = Base64.decodeBase64Local(base64ToDecode.getBytes()); // assert assertEquals(expectedTextResult, new String(result)); } /* Tests_SRS_BASE64_21_004: [If the `base64Values` length is not multiple of 4, the decodeBase64Local shall throw IllegalArgumentException.] */ @Test(expected = IllegalArgumentException.class) public void decodeBase64ThrowsOnInvalidByteArrayLength() throws UnsupportedEncodingException, IllegalArgumentException { // arrange String base64ToDecode = "VGhpcyBpcyBhIHZhbGlkIHRlc3QgKGFCY0RlRmdIaUpLTG1ub1BxUnN0dVZXWHl6KS0wMTIzNDU2Nw"; // act Base64.decodeBase64Local(base64ToDecode.getBytes()); } /* Tests_SRS_BASE64_21_004: [If the `base64Values` length is not multiple of 4, the decodeBase64Local shall throw IllegalArgumentException.] */ @Test(expected = IllegalArgumentException.class) public void decodeBase64ThrowsOnLostPad() throws UnsupportedEncodingException, IllegalArgumentException { // arrange String base64ToDecode = "VGhpcyBpcyBhIHZhbGlkIHRlc3QgKGFCY0RlRmdIaUpLTG1ub1BxUnN0dVZXWHl6KS0wMTIzNDU2Nw="; // act Base64.decodeBase64Local(base64ToDecode.getBytes()); } /* Tests_SRS_BASE64_21_004: [If the `base64Values` length is not multiple of 4, the decodeBase64Local shall throw IllegalArgumentException.] */ @Test(expected = IllegalArgumentException.class) public void decodeBase64ThrowsOnExtraPad() throws UnsupportedEncodingException, IllegalArgumentException { // arrange String base64ToDecode = "VGhpcyBpcyBhIHZhbGlkIHRlc3QgKGFCY0RlRmdIaUpLTG1ub1BxUnN0dVZXWHl6KS0wMTIzNDU2N==="; // act Base64.decodeBase64Local(base64ToDecode.getBytes()); } /* Tests_SRS_BASE64_21_004: [If the `base64Values` length is not multiple of 4, the decodeBase64Local shall throw IllegalArgumentException.] */ @Test(expected = IllegalArgumentException.class) public void decodeBase64ThrowsOnInvalidPad() throws UnsupportedEncodingException, IllegalArgumentException { // arrange String base64ToDecode = "VGhpcyBpcyBhIHZhbGlkIHRlc3QgKGFCY0RlRmdIaUpLTG1ub1BxUnN0dVZXWHl6KS0wMTIzNDU2Nw=*"; // act Base64.decodeBase64Local(base64ToDecode.getBytes()); } /** Tests_SRS_BASE64_21_005: [The encodeBase64Local shall encoded the provided `dataValues` in a byte array * using the Base64 format define in the RFC2045.] */ @Test public void encodeBase64WithMultipleOf4CharactersSuccess() throws UnsupportedEncodingException, IllegalArgumentException { // arrange String textToEncode = "This is a valid test>>>>????(aBcDeFgHiJKLmnoPqRstuVWXyz)0123456789"; String expectedBase64Result = "VGhpcyBpcyBhIHZhbGlkIHRlc3Q+Pj4+Pz8/PyhhQmNEZUZnSGlKS0xtbm9QcVJzdHVWV1h5eikwMTIzNDU2Nzg5"; // act byte[] result = Base64.encodeBase64Local(textToEncode.getBytes()); // assert assertEquals(expectedBase64Result, new String(result)); } /** Tests_SRS_BASE64_21_005: [The encodeBase64Local shall encoded the provided `dataValues` in a byte array * using the Base64 format define in the RFC2045.] */ @Test public void encodeBase64MultipleOf4Minus1CharactersSuccess() throws UnsupportedEncodingException, IllegalArgumentException { // arrange String textToEncode = "This is a valid test (aBcDeFgHiJKLmnoPqRstuVWXyz)-012345678"; String expectedBase64Result = "VGhpcyBpcyBhIHZhbGlkIHRlc3QgKGFCY0RlRmdIaUpLTG1ub1BxUnN0dVZXWHl6KS0wMTIzNDU2Nzg="; // act byte[] result = Base64.encodeBase64Local(textToEncode.getBytes()); // assert assertEquals(expectedBase64Result, new String(result)); } /** Tests_SRS_BASE64_21_005: [The encodeBase64Local shall encoded the provided `dataValues` in a byte array * using the Base64 format define in the RFC2045.] */ @Test public void encodeBase64MultipleOf4Minus2CharactersSuccess() throws UnsupportedEncodingException, IllegalArgumentException { // arrange String textToEncode = "This is a valid test (aBcDeFgHiJKLmnoPqRstuVWXyz)-01234567"; String expectedBase64Result = "VGhpcyBpcyBhIHZhbGlkIHRlc3QgKGFCY0RlRmdIaUpLTG1ub1BxUnN0dVZXWHl6KS0wMTIzNDU2Nw=="; // act byte[] result = Base64.encodeBase64Local(textToEncode.getBytes()); // assert assertEquals(expectedBase64Result, new String(result)); } /* Tests_SRS_BASE64_21_006: [If the `dataValues` is null, the encodeBase64Local shall throw IllegalArgumentException.] */ @Test(expected = IllegalArgumentException.class) public void encodeBase64ThrowsOnNullDataValues() throws UnsupportedEncodingException, IllegalArgumentException { // arrange byte[] textToEncode = null; // act Base64.encodeBase64Local(textToEncode); } /* Tests_SRS_BASE64_21_007: [If the `dataValues` is empty, the encodeBase64Local shall return a empty byte array.] */ @Test public void encodeBase64EmptyCharactersSuccess() throws UnsupportedEncodingException, IllegalArgumentException { // arrange String textToEncode = ""; String expectedBase64Result = ""; // act byte[] result = Base64.encodeBase64Local(textToEncode.getBytes()); // assert assertEquals(expectedBase64Result, new String(result)); } /** Tests_SRS_BASE64_21_008: [The encodeBase64StringLocal shall encoded the provided `dataValues` in a string * using the Base64 format define in the RFC2045.] */ @Test public void encodeBase64StringMultipleOf4CharactersSuccess() throws UnsupportedEncodingException, IllegalArgumentException { // arrange String textToEncode = "This is a valid test>>>>????(aBcDeFgHiJKLmnoPqRstuVWXyz)0123456789"; String expectedBase64Result = "VGhpcyBpcyBhIHZhbGlkIHRlc3Q+Pj4+Pz8/PyhhQmNEZUZnSGlKS0xtbm9QcVJzdHVWV1h5eikwMTIzNDU2Nzg5"; // act String result = Base64.encodeBase64StringLocal(textToEncode.getBytes()); // assert assertEquals(expectedBase64Result, result); } /** Tests_SRS_BASE64_21_008: [The encodeBase64StringLocal shall encoded the provided `dataValues` in a string * using the Base64 format define in the RFC2045.] */ @Test public void encodeBase64StringMultipleOf4Minus1CharactersSuccess() throws UnsupportedEncodingException, IllegalArgumentException { // arrange String textToEncode = "This is a valid test (aBcDeFgHiJKLmnoPqRstuVWXyz)-012345678"; String expectedBase64Result = "VGhpcyBpcyBhIHZhbGlkIHRlc3QgKGFCY0RlRmdIaUpLTG1ub1BxUnN0dVZXWHl6KS0wMTIzNDU2Nzg="; // act String result = Base64.encodeBase64StringLocal(textToEncode.getBytes()); // assert assertEquals(expectedBase64Result, result); } /** Tests_SRS_BASE64_21_008: [The encodeBase64StringLocal shall encoded the provided `dataValues` in a string * using the Base64 format define in the RFC2045.] */ @Test public void encodeBase64StringMultipleOf4Minus2CharactersSuccess() throws UnsupportedEncodingException, IllegalArgumentException { // arrange String textToEncode = "This is a valid test (aBcDeFgHiJKLmnoPqRstuVWXyz)-01234567"; String expectedBase64Result = "VGhpcyBpcyBhIHZhbGlkIHRlc3QgKGFCY0RlRmdIaUpLTG1ub1BxUnN0dVZXWHl6KS0wMTIzNDU2Nw=="; // act String result = Base64.encodeBase64StringLocal(textToEncode.getBytes()); // assert assertEquals(expectedBase64Result, result); } /* Tests_SRS_BASE64_21_009: [If the `dataValues` is null, the encodeBase64StringLocal shall throw IllegalArgumentException.] */ @Test(expected = IllegalArgumentException.class) public void encodeBase64StringThrowsOnNullDataValues() throws UnsupportedEncodingException, IllegalArgumentException { // arrange byte[] textToEncode = null; // act Base64.encodeBase64StringLocal(textToEncode); } /* Tests_SRS_BASE64_21_010: [If the `dataValues` is empty, the encodeBase64StringLocal shall return a empty string.] */ @Test public void encodeBase64StringEmptyCharactersSuccess() throws UnsupportedEncodingException, IllegalArgumentException { // arrange String textToEncode = ""; String expectedBase64Result = ""; // act String result = Base64.encodeBase64StringLocal(textToEncode.getBytes()); // assert assertEquals(expectedBase64Result, result); } } WebSocketHandlerImplTest.java000066400000000000000000001461311460332530100444030ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transport/ws/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.ws.impl; import com.microsoft.azure.proton.transport.ws.WebSocketHandler; import com.microsoft.azure.proton.transport.ws.WebSocketHeader; import org.junit.Test; import java.nio.ByteBuffer; import java.security.SecureRandom; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Scanner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; public class WebSocketHandlerImplTest { private static final SecureRandom SECURE_RANDOM = new SecureRandom(); @Test public void testCreateUpgradeRequest() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String queryKey = "?iothub-no-client-cert="; String queryValue = "true"; String webSocketProtocol = "subprotocol_name"; Map additionalHeaders = new HashMap(); additionalHeaders.put("header1", "content1"); additionalHeaders.put("header2", "content2"); additionalHeaders.put("header3", "content3"); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); String actual = webSocketHandler .createUpgradeRequest(hostName, webSocketPath, queryKey + queryValue, webSocketPort, webSocketProtocol, additionalHeaders); Boolean isLineCountOk = false; Boolean isStatusLineOk = false; Boolean isUpgradeHeaderOk = false; Boolean isConnectionHeaderOk = false; Boolean isWebSocketVersionHeaderOk = false; Boolean isWebSocketKeyHeaderOk = false; Boolean isWebSocketProtocolHeaderOk = false; Boolean isHostHeaderOk = false; Boolean isAdditonalHeader1Ok = false; Boolean isAdditonalHeader2Ok = false; Boolean isAdditonalHeader3Ok = false; Scanner scanner = new Scanner(actual); int lineCount = 0; while (scanner.hasNextLine()) { lineCount++; String line = scanner.nextLine(); if (line.equals("GET https://" + hostName + "/" + webSocketPath + queryKey + queryValue + " HTTP/1.1")) { isStatusLineOk = true; continue; } if (line.equals("Connection: Upgrade,Keep-Alive")) { isConnectionHeaderOk = true; continue; } if (line.equals("Upgrade: websocket")) { isUpgradeHeaderOk = true; continue; } if (line.equals("Sec-WebSocket-Version: 13")) { isWebSocketVersionHeaderOk = true; continue; } if (line.startsWith("Sec-WebSocket-Key: ")) { String keyBase64 = line.substring(19); if (keyBase64.length() == 24) { byte[] decoded = Base64.decodeBase64Local(keyBase64.getBytes()); if (decoded.length == 16) { isWebSocketKeyHeaderOk = true; } } continue; } if (line.equals("Sec-WebSocket-Protocol: " + webSocketProtocol)) { isWebSocketProtocolHeaderOk = true; continue; } if (line.equals("Host: host_XXX")) { isHostHeaderOk = true; continue; } if (line.equals("header1: content1")) { isAdditonalHeader1Ok = true; continue; } if (line.equals("header2: content2")) { isAdditonalHeader2Ok = true; continue; } if (line.equals("header3: content3")) { isAdditonalHeader3Ok = true; continue; } } if (lineCount == 11) { isLineCountOk = true; } assertTrue(isLineCountOk); assertTrue(isStatusLineOk); assertTrue(isUpgradeHeaderOk); assertTrue(isConnectionHeaderOk); assertTrue(isWebSocketVersionHeaderOk); assertTrue(isWebSocketKeyHeaderOk); assertTrue(isWebSocketProtocolHeaderOk); assertTrue(isHostHeaderOk); assertTrue(isAdditonalHeader1Ok); assertTrue(isAdditonalHeader2Ok); assertTrue(isAdditonalHeader3Ok); } @Test public void testCreateUpgradeRequestVerifySubsequentCall() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; Map additionalHeaders = new HashMap(); additionalHeaders.put("header1", "content1"); additionalHeaders.put("header2", "content2"); additionalHeaders.put("header3", "content3"); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); WebSocketUpgrade mockWebSocketUpgrade = mock(WebSocketUpgrade.class); doReturn(mockWebSocketUpgrade).when(spyWebSocketHandler) .createWebSocketUpgrade(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, additionalHeaders); spyWebSocketHandler.createUpgradeRequest(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, additionalHeaders); verify(spyWebSocketHandler, times(1)) .createWebSocketUpgrade(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, additionalHeaders); verify(mockWebSocketUpgrade, times(1)).createUpgradeRequest(); } @Test public void testCreatePong() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); ByteBuffer ping = ByteBuffer.allocate(10); ByteBuffer pong = ByteBuffer.allocate(10); byte[] buffer = new byte[10]; buffer[0] = WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_PING; ping.put(buffer); ping.flip(); webSocketHandler.createPong(ping, pong); int actual = pong.array()[0]; int expected = WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_PONG; assertEquals(actual, expected); } @Test(expected = IllegalArgumentException.class) public void testCreatePongPingNull() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); ByteBuffer pong = ByteBuffer.allocate(10); webSocketHandler.createPong(null, pong); } @Test(expected = IllegalArgumentException.class) public void testCreatePongPongNull() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); ByteBuffer ping = ByteBuffer.allocate(10); webSocketHandler.createPong(ping, null); } @Test(expected = IllegalArgumentException.class) public void testCreatePongPongCapacityInsufficient() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); ByteBuffer ping = ByteBuffer.allocate(10); ByteBuffer pong = ByteBuffer.allocate(9); webSocketHandler.createPong(ping, pong); } @Test public void testCreatePongPingNoRemaining() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); ByteBuffer ping = ByteBuffer.allocate(10); ByteBuffer pong = ByteBuffer.allocate(10); ping.flip(); webSocketHandler.createPong(ping, pong); assertEquals(0, pong.limit()); assertEquals(0, pong.position()); } @Test public void testValidateUpgradeReply() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; final Map additionalHeaders = new HashMap(); additionalHeaders.put("header1", "content1"); additionalHeaders.put("header2", "content2"); additionalHeaders.put("header3", "content3"); ByteBuffer buffer = ByteBuffer.allocate(10); byte[] data = new byte[buffer.remaining()]; WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); WebSocketUpgrade mockWebSocketUpgrade = mock(WebSocketUpgrade.class); doReturn(mockWebSocketUpgrade).when(spyWebSocketHandler) .createWebSocketUpgrade(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, additionalHeaders); doReturn(true).when(mockWebSocketUpgrade).validateUpgradeReply(data); spyWebSocketHandler.createUpgradeRequest(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, additionalHeaders); assertTrue(spyWebSocketHandler.validateUpgradeReply(buffer)); assertFalse(mockWebSocketUpgrade == null); verify(mockWebSocketUpgrade, times(1)).validateUpgradeReply(data); } @Test public void testValidateUpgradeReplyNoRemaining() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; Map additionalHeaders = new HashMap(); additionalHeaders.put("header1", "content1"); additionalHeaders.put("header2", "content2"); additionalHeaders.put("header3", "content3"); ByteBuffer buffer = ByteBuffer.allocate(10); byte[] data = new byte[buffer.remaining()]; buffer.limit(0); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); WebSocketUpgrade mockWebSocketUpgrade = mock(WebSocketUpgrade.class); doReturn(mockWebSocketUpgrade).when(spyWebSocketHandler) .createWebSocketUpgrade(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, additionalHeaders); doReturn(true).when(mockWebSocketUpgrade).validateUpgradeReply(data); spyWebSocketHandler.createUpgradeRequest(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, additionalHeaders); assertFalse(spyWebSocketHandler.validateUpgradeReply(buffer)); verify(mockWebSocketUpgrade, times(0)).validateUpgradeReply(data); } @Test public void testValidateUpgradeReplyWebsocketupgradeNull() { ByteBuffer buffer = ByteBuffer.allocate(10); byte[] data = new byte[buffer.remaining()]; WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); WebSocketUpgrade mockWebSocketUpgrade = mock(WebSocketUpgrade.class); assertFalse(spyWebSocketHandler.validateUpgradeReply(buffer)); verify(mockWebSocketUpgrade, times(0)).validateUpgradeReply(data); } @Test public void testWrapBufferShortPayload() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int payloadLength = 100; int messageLength = payloadLength + WebSocketHeader.MIN_HEADER_LENGTH_MASKED; byte[] maskingKey = new byte[]{0x01, 0x02, 0x03, 0x04}; byte[] data = new byte[payloadLength]; SECURE_RANDOM.nextBytes(data); ByteBuffer srcBuffer = ByteBuffer.allocate(payloadLength); ByteBuffer dstBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); int expectedHeaderSize = WebSocketHeader.MIN_HEADER_LENGTH_MASKED; byte[] expected = new byte[messageLength]; expected[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); expected[1] = (byte) (WebSocketHeader.MASKBIT_MASK | payloadLength); expected[2] = maskingKey[0]; expected[3] = maskingKey[1]; expected[4] = maskingKey[2]; expected[5] = maskingKey[3]; for (int i = 0; i < srcBuffer.limit(); i++) { byte nextByte = srcBuffer.get(); nextByte ^= maskingKey[i % 4]; expected[i + WebSocketHeader.MIN_HEADER_LENGTH_MASKED] = nextByte; } srcBuffer.flip(); doReturn(maskingKey).when(spyWebSocketHandler).createRandomMaskingKey(); spyWebSocketHandler.wrapBuffer(srcBuffer, dstBuffer); dstBuffer.flip(); byte[] actual = dstBuffer.array(); assertEquals("invalid content length", srcBuffer.limit() + expectedHeaderSize, dstBuffer.limit()); assertEquals("first byte mismatch", expected[0], actual[0]); assertEquals("second byte mismatch", expected[1], actual[1]); assertEquals("masking key mismatch 1", maskingKey[0], actual[2]); assertEquals("masking key mismatch 2", maskingKey[1], actual[3]); assertEquals("masking key mismatch 3", maskingKey[2], actual[4]); assertEquals("masking key mismatch 4", maskingKey[3], actual[5]); assertTrue(Arrays.equals(expected, actual)); } @Test public void testWrapBufferShortPayloadMin() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int payloadLength = 1; int messageLength = payloadLength + WebSocketHeader.MIN_HEADER_LENGTH_MASKED; byte[] maskingKey = new byte[]{0x01, 0x02, 0x03, 0x04}; byte[] data = new byte[payloadLength]; SECURE_RANDOM.nextBytes(data); ByteBuffer srcBuffer = ByteBuffer.allocate(payloadLength); ByteBuffer dstBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); int expectedHeaderSize = WebSocketHeader.MIN_HEADER_LENGTH_MASKED; byte[] expected = new byte[messageLength]; expected[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); expected[1] = (byte) (WebSocketHeader.MASKBIT_MASK | payloadLength); expected[2] = maskingKey[0]; expected[3] = maskingKey[1]; expected[4] = maskingKey[2]; expected[5] = maskingKey[3]; for (int i = 0; i < srcBuffer.limit(); i++) { byte nextByte = srcBuffer.get(); nextByte ^= maskingKey[i % 4]; expected[i + WebSocketHeader.MIN_HEADER_LENGTH_MASKED] = nextByte; } srcBuffer.flip(); doReturn(maskingKey).when(spyWebSocketHandler).createRandomMaskingKey(); spyWebSocketHandler.wrapBuffer(srcBuffer, dstBuffer); dstBuffer.flip(); byte[] actual = dstBuffer.array(); assertEquals("invalid content length", srcBuffer.limit() + expectedHeaderSize, dstBuffer.limit()); assertEquals("first byte mismatch", expected[0], actual[0]); assertEquals("second byte mismatch", expected[1], actual[1]); assertEquals("masking key mismatch 1", maskingKey[0], actual[2]); assertEquals("masking key mismatch 2", maskingKey[1], actual[3]); assertEquals("masking key mismatch 3", maskingKey[2], actual[4]); assertEquals("masking key mismatch 4", maskingKey[3], actual[5]); assertTrue(Arrays.equals(expected, actual)); } @Test public void testWrapBufferShortPayloadMax() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int payloadLength = WebSocketHeader.PAYLOAD_SHORT_MAX; int messageLength = payloadLength + WebSocketHeader.MIN_HEADER_LENGTH_MASKED; byte[] maskingKey = new byte[]{0x01, 0x02, 0x03, 0x04}; byte[] data = new byte[payloadLength]; SECURE_RANDOM.nextBytes(data); ByteBuffer srcBuffer = ByteBuffer.allocate(payloadLength); ByteBuffer dstBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); int expectedHeaderSize = WebSocketHeader.MIN_HEADER_LENGTH_MASKED; byte[] expected = new byte[messageLength]; expected[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); expected[1] = (byte) (WebSocketHeader.MASKBIT_MASK | payloadLength); expected[2] = maskingKey[0]; expected[3] = maskingKey[1]; expected[4] = maskingKey[2]; expected[5] = maskingKey[3]; for (int i = 0; i < srcBuffer.limit(); i++) { byte nextByte = srcBuffer.get(); nextByte ^= maskingKey[i % 4]; expected[i + WebSocketHeader.MIN_HEADER_LENGTH_MASKED] = nextByte; } srcBuffer.flip(); doReturn(maskingKey).when(spyWebSocketHandler).createRandomMaskingKey(); spyWebSocketHandler.wrapBuffer(srcBuffer, dstBuffer); dstBuffer.flip(); byte[] actual = dstBuffer.array(); assertEquals("invalid content length", srcBuffer.limit() + expectedHeaderSize, dstBuffer.limit()); assertEquals("first byte mismatch", expected[0], actual[0]); assertEquals("second byte mismatch", expected[1], actual[1]); assertEquals("masking key mismatch 1", maskingKey[0], actual[2]); assertEquals("masking key mismatch 2", maskingKey[1], actual[3]); assertEquals("masking key mismatch 3", maskingKey[2], actual[4]); assertEquals("masking key mismatch 4", maskingKey[3], actual[5]); assertTrue(Arrays.equals(expected, actual)); } @Test public void testWrapBufferMediumPayload() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int payloadLength = WebSocketHeader.PAYLOAD_SHORT_MAX + 100; int messageLength = payloadLength + WebSocketHeader.MED_HEADER_LENGTH_MASKED; byte[] maskingKey = new byte[]{0x01, 0x02, 0x03, 0x04}; byte[] data = new byte[payloadLength]; SECURE_RANDOM.nextBytes(data); ByteBuffer srcBuffer = ByteBuffer.allocate(payloadLength); ByteBuffer dstBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); int expectedHeaderSize = WebSocketHeader.MED_HEADER_LENGTH_MASKED; byte[] expected = new byte[messageLength]; expected[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); expected[1] = (byte) (WebSocketHeader.MASKBIT_MASK | WebSocketHeader.PAYLOAD_EXTENDED_16); expected[2] = (byte) (payloadLength >> 8); expected[3] = (byte) (payloadLength); expected[4] = maskingKey[0]; expected[5] = maskingKey[1]; expected[6] = maskingKey[2]; expected[7] = maskingKey[3]; for (int i = 0; i < srcBuffer.limit(); i++) { byte nextByte = srcBuffer.get(); nextByte ^= maskingKey[i % 4]; expected[i + WebSocketHeader.MED_HEADER_LENGTH_MASKED] = nextByte; } srcBuffer.flip(); doReturn(maskingKey).when(spyWebSocketHandler).createRandomMaskingKey(); spyWebSocketHandler.wrapBuffer(srcBuffer, dstBuffer); dstBuffer.flip(); byte[] actual = dstBuffer.array(); assertEquals("invalid content length", srcBuffer.limit() + expectedHeaderSize, dstBuffer.limit()); assertEquals("first byte mismatch", expected[0], actual[0]); assertEquals("second byte mismatch", expected[1], actual[1]); assertEquals("payload length byte mismatch 1", expected[2], actual[2]); assertEquals("payload length byte mismatch 2", expected[3], actual[3]); assertEquals("masking key mismatch 1", maskingKey[0], actual[4]); assertEquals("masking key mismatch 2", maskingKey[1], actual[5]); assertEquals("masking key mismatch 3", maskingKey[2], actual[6]); assertEquals("masking key mismatch 4", maskingKey[3], actual[7]); assertTrue(Arrays.equals(expected, actual)); } @Test public void testWrapBufferMediumPayloadMin() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int payloadLength = WebSocketHeader.PAYLOAD_SHORT_MAX + 1; int messageLength = payloadLength + WebSocketHeader.MED_HEADER_LENGTH_MASKED; byte[] maskingKey = new byte[]{0x01, 0x02, 0x03, 0x04}; byte[] data = new byte[payloadLength]; SECURE_RANDOM.nextBytes(data); ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); ByteBuffer dstBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); int expectedHeaderSize = WebSocketHeader.MED_HEADER_LENGTH_MASKED; byte[] expected = new byte[dstBuffer.capacity()]; expected[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); expected[1] = (byte) (WebSocketHeader.MASKBIT_MASK | WebSocketHeader.PAYLOAD_EXTENDED_16); expected[2] = (byte) (payloadLength >> 8); expected[3] = (byte) (payloadLength); expected[4] = maskingKey[0]; expected[5] = maskingKey[1]; expected[6] = maskingKey[2]; expected[7] = maskingKey[3]; for (int i = 0; i < srcBuffer.limit(); i++) { byte nextByte = srcBuffer.get(); nextByte ^= maskingKey[i % 4]; expected[i + WebSocketHeader.MED_HEADER_LENGTH_MASKED] = nextByte; } srcBuffer.flip(); doReturn(maskingKey).when(spyWebSocketHandler).createRandomMaskingKey(); spyWebSocketHandler.wrapBuffer(srcBuffer, dstBuffer); dstBuffer.flip(); byte[] actual = dstBuffer.array(); assertEquals("invalid content length", srcBuffer.limit() + expectedHeaderSize, dstBuffer.limit()); assertEquals("first byte mismatch", expected[0], actual[0]); assertEquals("second byte mismatch", expected[1], actual[1]); assertEquals("payload length byte mismatch 1", expected[2], actual[2]); assertEquals("payload length byte mismatch 2", expected[3], actual[3]); assertEquals("masking key mismatch 1", maskingKey[0], actual[4]); assertEquals("masking key mismatch 2", maskingKey[1], actual[5]); assertEquals("masking key mismatch 3", maskingKey[2], actual[6]); assertEquals("masking key mismatch 4", maskingKey[3], actual[7]); assertTrue(Arrays.equals(expected, actual)); } @Test public void testWrapBufferMediumPayloadMax() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int payloadLength = WebSocketHeader.PAYLOAD_MEDIUM_MAX; int messageLength = payloadLength + WebSocketHeader.MED_HEADER_LENGTH_MASKED; byte[] maskingKey = new byte[]{0x01, 0x02, 0x03, 0x04}; byte[] data = new byte[payloadLength]; SECURE_RANDOM.nextBytes(data); ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); ByteBuffer dstBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); int expectedHeaderSize = WebSocketHeader.MED_HEADER_LENGTH_MASKED; byte[] expected = new byte[dstBuffer.capacity()]; expected[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); expected[1] = (byte) (WebSocketHeader.MASKBIT_MASK | WebSocketHeader.PAYLOAD_EXTENDED_16); expected[2] = (byte) (payloadLength >>> 8); expected[3] = (byte) (payloadLength); expected[4] = maskingKey[0]; expected[5] = maskingKey[1]; expected[6] = maskingKey[2]; expected[7] = maskingKey[3]; for (int i = 0; i < srcBuffer.limit(); i++) { byte nextByte = srcBuffer.get(); nextByte ^= maskingKey[i % 4]; expected[i + WebSocketHeader.MED_HEADER_LENGTH_MASKED] = nextByte; } srcBuffer.flip(); doReturn(maskingKey).when(spyWebSocketHandler).createRandomMaskingKey(); spyWebSocketHandler.wrapBuffer(srcBuffer, dstBuffer); dstBuffer.flip(); byte[] actual = dstBuffer.array(); assertEquals("invalid content length", srcBuffer.limit() + expectedHeaderSize, dstBuffer.limit()); assertEquals("first byte mismatch", expected[0], actual[0]); assertEquals("second byte mismatch", expected[1], actual[1]); assertEquals("payload length byte mismatch 1", expected[2], actual[2]); assertEquals("payload length byte mismatch 2", expected[3], actual[3]); assertEquals("masking key mismatch 1", maskingKey[0], actual[4]); assertEquals("masking key mismatch 2", maskingKey[1], actual[5]); assertEquals("masking key mismatch 3", maskingKey[2], actual[6]); assertEquals("masking key mismatch 4", maskingKey[3], actual[7]); assertTrue(Arrays.equals(expected, actual)); } @Test public void testWrapBufferLargePayload() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int payloadLength = WebSocketHeader.PAYLOAD_MEDIUM_MAX + 100; int messageLength = payloadLength + WebSocketHeader.MAX_HEADER_LENGTH_MASKED; byte[] maskingKey = new byte[]{0x01, 0x02, 0x03, 0x04}; byte[] data = new byte[payloadLength]; SECURE_RANDOM.nextBytes(data); ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); ByteBuffer dstBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); int expectedHeaderSize = WebSocketHeader.MAX_HEADER_LENGTH_MASKED; byte[] expected = new byte[dstBuffer.capacity()]; expected[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); expected[1] = (byte) (WebSocketHeader.MASKBIT_MASK | WebSocketHeader.PAYLOAD_EXTENDED_64); expected[2] = (byte) (payloadLength >>> 56); expected[3] = (byte) (payloadLength >>> 48); expected[4] = (byte) (payloadLength >>> 40); expected[5] = (byte) (payloadLength >>> 32); expected[6] = (byte) (payloadLength >>> 24); expected[7] = (byte) (payloadLength >>> 16); expected[8] = (byte) (payloadLength >>> 8); expected[9] = (byte) (payloadLength); expected[10] = maskingKey[0]; expected[11] = maskingKey[1]; expected[12] = maskingKey[2]; expected[13] = maskingKey[3]; for (int i = 0; i < srcBuffer.limit(); i++) { byte nextByte = srcBuffer.get(); nextByte ^= maskingKey[i % 4]; expected[i + WebSocketHeader.MAX_HEADER_LENGTH_MASKED] = nextByte; } srcBuffer.flip(); doReturn(maskingKey).when(spyWebSocketHandler).createRandomMaskingKey(); spyWebSocketHandler.wrapBuffer(srcBuffer, dstBuffer); dstBuffer.flip(); byte[] actual = dstBuffer.array(); assertEquals("invalid content length", srcBuffer.limit() + expectedHeaderSize, dstBuffer.limit()); assertEquals("first byte mismatch", expected[0], actual[0]); assertEquals("second byte mismatch", expected[1], actual[1]); assertEquals("payload length byte mismatch 1", expected[2], actual[2]); assertEquals("payload length byte mismatch 2", expected[3], actual[3]); assertEquals("payload length byte mismatch 3", expected[4], actual[4]); assertEquals("payload length byte mismatch 4", expected[5], actual[5]); assertEquals("payload length byte mismatch 5", expected[6], actual[6]); assertEquals("payload length byte mismatch 6", expected[7], actual[7]); assertEquals("payload length byte mismatch 7", expected[8], actual[8]); assertEquals("payload length byte mismatch 8", expected[9], actual[9]); assertEquals("masking key mismatch 1", maskingKey[0], actual[10]); assertEquals("masking key mismatch 2", maskingKey[1], actual[11]); assertEquals("masking key mismatch 3", maskingKey[2], actual[12]); assertEquals("masking key mismatch 4", maskingKey[3], actual[13]); assertTrue(Arrays.equals(expected, actual)); } @Test public void testWrapBufferLargePayloadMin() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int payloadLength = WebSocketHeader.PAYLOAD_MEDIUM_MAX + 1; int messageLength = payloadLength + WebSocketHeader.MAX_HEADER_LENGTH_MASKED; byte[] maskingKey = new byte[]{0x01, 0x02, 0x03, 0x04}; byte[] data = new byte[payloadLength]; SECURE_RANDOM.nextBytes(data); ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); ByteBuffer dstBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); int expectedHeaderSize = WebSocketHeader.MAX_HEADER_LENGTH_MASKED; byte[] expected = new byte[dstBuffer.capacity()]; expected[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); expected[1] = (byte) (WebSocketHeader.MASKBIT_MASK | WebSocketHeader.PAYLOAD_EXTENDED_64); expected[2] = (byte) (payloadLength >>> 56); expected[3] = (byte) (payloadLength >>> 48); expected[4] = (byte) (payloadLength >>> 40); expected[5] = (byte) (payloadLength >>> 32); expected[6] = (byte) (payloadLength >>> 24); expected[7] = (byte) (payloadLength >>> 16); expected[8] = (byte) (payloadLength >>> 8); expected[9] = (byte) (payloadLength); expected[10] = maskingKey[0]; expected[11] = maskingKey[1]; expected[12] = maskingKey[2]; expected[13] = maskingKey[3]; for (int i = 0; i < srcBuffer.limit(); i++) { byte nextByte = srcBuffer.get(); nextByte ^= maskingKey[i % 4]; expected[i + WebSocketHeader.MAX_HEADER_LENGTH_MASKED] = nextByte; } srcBuffer.flip(); doReturn(maskingKey).when(spyWebSocketHandler).createRandomMaskingKey(); spyWebSocketHandler.wrapBuffer(srcBuffer, dstBuffer); dstBuffer.flip(); byte[] actual = dstBuffer.array(); assertEquals("invalid content length", srcBuffer.limit() + expectedHeaderSize, dstBuffer.limit()); assertEquals("first byte mismatch", expected[0], actual[0]); assertEquals("second byte mismatch", expected[1], actual[1]); assertEquals("payload length byte mismatch 1", expected[2], actual[2]); assertEquals("payload length byte mismatch 2", expected[3], actual[3]); assertEquals("payload length byte mismatch 3", expected[4], actual[4]); assertEquals("payload length byte mismatch 4", expected[5], actual[5]); assertEquals("payload length byte mismatch 5", expected[6], actual[6]); assertEquals("payload length byte mismatch 6", expected[7], actual[7]); assertEquals("payload length byte mismatch 7", expected[8], actual[8]); assertEquals("payload length byte mismatch 8", expected[9], actual[9]); assertEquals("masking key mismatch 1", maskingKey[0], actual[10]); assertEquals("masking key mismatch 2", maskingKey[1], actual[11]); assertEquals("masking key mismatch 3", maskingKey[2], actual[12]); assertEquals("masking key mismatch 4", maskingKey[3], actual[13]); assertTrue(Arrays.equals(expected, actual)); } // @Test // public void testWrapBuffer_large_payload_max() // { // WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); // WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); // // int payloadLength = WebSocketHeader.PAYLOAD_LARGE_MAX / 16; // Limited by memory // int messageLength = payloadLength + WebSocketHeader.MAX_HEADER_LENGTH_MASKED; // // byte[] maskingKey = new byte[]{0x01, 0x02, 0x03, 0x04}; // // byte[] data = new byte[payloadLength]; // SECURE_RANDOM.nextBytes(data); // // ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); // ByteBuffer dstBuffer = ByteBuffer.allocate(messageLength); // srcBuffer.put(data); // srcBuffer.flip(); // // int expectedHeaderSize = WebSocketHeader.MAX_HEADER_LENGTH_MASKED; // byte[] expected = new byte[dstBuffer.capacity()]; // expected[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); // expected[1] = (byte) (WebSocketHeader.MASKBIT_MASK | WebSocketHeader.PAYLOAD_EXTENDED_64); // // expected[2] = (byte) (payloadLength >>> 56); // expected[3] = (byte) (payloadLength >>> 48); // expected[4] = (byte) (payloadLength >>> 40); // expected[5] = (byte) (payloadLength >>> 32); // expected[6] = (byte) (payloadLength >>> 24); // expected[7] = (byte) (payloadLength >>> 16); // expected[8] = (byte) (payloadLength >>> 8); // expected[9] = (byte) (payloadLength); // // expected[10] = maskingKey[0]; // expected[11] = maskingKey[1]; // expected[12] = maskingKey[2]; // expected[13] = maskingKey[3]; // // for (int i = 0; i < srcBuffer.limit(); i++) // { // byte nextByte = srcBuffer.get(); // nextByte ^= maskingKey[i % 4]; // expected[i + WebSocketHeader.MAX_HEADER_LENGTH_MASKED] = nextByte; // } // srcBuffer.flip(); // // doReturn(maskingKey).when(spyWebSocketHandler).createRandomMaskingKey(); // // spyWebSocketHandler.wrapBuffer(srcBuffer, dstBuffer); // dstBuffer.flip(); // // byte[] actual = dstBuffer.array(); // // assertEquals("invalid content length", srcBuffer.limit() + expectedHeaderSize, dstBuffer.limit()); // // assertEquals("first byte mismatch", expected[0], actual[0]); // assertEquals("second byte mismatch", expected[1], actual[1]); // // assertEquals("payload length byte mismatch 1", expected[2], actual[2]); // assertEquals("payload length byte mismatch 2", expected[3], actual[3]); // assertEquals("payload length byte mismatch 3", expected[4], actual[4]); // assertEquals("payload length byte mismatch 4", expected[5], actual[5]); // assertEquals("payload length byte mismatch 5", expected[6], actual[6]); // assertEquals("payload length byte mismatch 6", expected[7], actual[7]); // assertEquals("payload length byte mismatch 7", expected[8], actual[8]); // assertEquals("payload length byte mismatch 8", expected[9], actual[9]); // // assertEquals("masking key mismatch 1", maskingKey[0], actual[10]); // assertEquals("masking key mismatch 2", maskingKey[1], actual[11]); // assertEquals("masking key mismatch 3", maskingKey[2], actual[12]); // assertEquals("masking key mismatch 4", maskingKey[3], actual[13]); // // assertTrue(Arrays.equals(expected, actual)); // } @Test(expected = IllegalArgumentException.class) public void testWrapBufferSrcBufferNull() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int messageLength = 10; ByteBuffer srcBuffer = null; ByteBuffer dstBuffer = ByteBuffer.allocate(messageLength + WebSocketHeader.MAX_HEADER_LENGTH_MASKED); spyWebSocketHandler.wrapBuffer(srcBuffer, dstBuffer); } @Test(expected = IllegalArgumentException.class) public void testWrapBufferDstBufferNull() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int messageLength = 10; ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength + WebSocketHeader.MAX_HEADER_LENGTH_MASKED); ByteBuffer dstBuffer = null; spyWebSocketHandler.wrapBuffer(srcBuffer, dstBuffer); } @Test(expected = OutOfMemoryError.class) public void testWrapBufferDstBufferSmall() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int messageLength = 10; ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); ByteBuffer dstBuffer = ByteBuffer.allocate(messageLength); spyWebSocketHandler.wrapBuffer(srcBuffer, dstBuffer); } @Test public void testWrapBufferSrcBufferEmpty() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int messageLength = 10; ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); srcBuffer.flip(); ByteBuffer dstBuffer = ByteBuffer.allocate(messageLength); spyWebSocketHandler.wrapBuffer(srcBuffer, dstBuffer); } @Test public void testUnwrapBufferOpcodePing() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int payloadLength = 100; int messageLength = payloadLength + WebSocketHeader.MIN_HEADER_LENGTH; byte[] data = new byte[messageLength]; SECURE_RANDOM.nextBytes(data); data[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_PING); data[1] = (byte) (payloadLength); ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); assertEquals(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_PING, spyWebSocketHandler.unwrapBuffer(srcBuffer).getType()); byte[] expected = Arrays.copyOfRange(data, WebSocketHeader.MIN_HEADER_LENGTH, messageLength); byte[] actual = new byte[srcBuffer.remaining()]; srcBuffer.get(actual); assertTrue(Arrays.equals(expected, actual)); } @Test public void testUnwrapBufferOpcodeClose() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int payloadLength = 100; int messageLength = payloadLength + WebSocketHeader.MIN_HEADER_LENGTH; byte[] data = new byte[messageLength]; SECURE_RANDOM.nextBytes(data); data[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_CLOSE); data[1] = (byte) (payloadLength); ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); assertEquals(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_CLOSE, spyWebSocketHandler.unwrapBuffer(srcBuffer).getType()); byte[] expected = Arrays.copyOfRange(data, WebSocketHeader.MIN_HEADER_LENGTH, messageLength); byte[] actual = new byte[srcBuffer.remaining()]; srcBuffer.get(actual); assertTrue(Arrays.equals(expected, actual)); } @Test public void testUnwrapBufferShortMessage() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int payloadLength = 100; int messageLength = payloadLength + WebSocketHeader.MIN_HEADER_LENGTH; byte[] data = new byte[messageLength]; SECURE_RANDOM.nextBytes(data); data[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); data[1] = (byte) (payloadLength); ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); assertEquals(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_AMQP, spyWebSocketHandler.unwrapBuffer(srcBuffer).getType()); byte[] expected = Arrays.copyOfRange(data, WebSocketHeader.MIN_HEADER_LENGTH, messageLength); byte[] actual = new byte[srcBuffer.remaining()]; srcBuffer.get(actual); assertTrue(Arrays.equals(expected, actual)); } @Test public void testUnwrapBufferShortMessageMin() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int payloadLength = 1; int messageLength = payloadLength + WebSocketHeader.MIN_HEADER_LENGTH; byte[] data = new byte[messageLength]; SECURE_RANDOM.nextBytes(data); data[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); data[1] = (byte) (payloadLength); ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); assertEquals(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_AMQP, spyWebSocketHandler.unwrapBuffer(srcBuffer).getType()); byte[] expected = Arrays.copyOfRange(data, 2, messageLength); byte[] actual = new byte[srcBuffer.remaining()]; srcBuffer.get(actual); assertTrue(Arrays.equals(expected, actual)); } @Test public void testUnwrapBufferShortMessageMax() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int payloadLength = WebSocketHeader.PAYLOAD_SHORT_MAX; int messageLength = payloadLength + WebSocketHeader.MIN_HEADER_LENGTH; byte[] data = new byte[messageLength]; SECURE_RANDOM.nextBytes(data); data[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); data[1] = (byte) (payloadLength); ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); assertEquals(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_AMQP, spyWebSocketHandler.unwrapBuffer(srcBuffer).getType()); byte[] expected = Arrays.copyOfRange(data, 2, messageLength); byte[] actual = new byte[srcBuffer.remaining()]; srcBuffer.get(actual); assertTrue(Arrays.equals(expected, actual)); } @Test public void testUnwrapBufferMediumMessage() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int payloadLength = WebSocketHeader.PAYLOAD_SHORT_MAX + 100; int messageLength = payloadLength + WebSocketHeader.MED_HEADER_LENGTH_NOMASK; byte[] data = new byte[messageLength]; SECURE_RANDOM.nextBytes(data); data[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); data[1] = WebSocketHeader.PAYLOAD_EXTENDED_16; data[2] = (byte) (payloadLength >>> 8); data[3] = (byte) (payloadLength); ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); assertEquals(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_AMQP, spyWebSocketHandler.unwrapBuffer(srcBuffer).getType()); byte[] expected = Arrays.copyOfRange(data, WebSocketHeader.MED_HEADER_LENGTH_NOMASK, messageLength); byte[] actual = new byte[srcBuffer.remaining()]; srcBuffer.get(actual); assertTrue(Arrays.equals(expected, actual)); } @Test public void testUnwrapBufferMediumMessageMin() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int payloadLength = WebSocketHeader.PAYLOAD_SHORT_MAX + 1; int messageLength = payloadLength + WebSocketHeader.MED_HEADER_LENGTH_NOMASK; byte[] data = new byte[messageLength]; SECURE_RANDOM.nextBytes(data); data[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); data[1] = WebSocketHeader.PAYLOAD_EXTENDED_16; data[2] = (byte) (payloadLength >>> 8); data[3] = (byte) (payloadLength); ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); assertEquals(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_AMQP, spyWebSocketHandler.unwrapBuffer(srcBuffer).getType()); byte[] expected = Arrays.copyOfRange(data, WebSocketHeader.MED_HEADER_LENGTH_NOMASK, messageLength); byte[] actual = new byte[srcBuffer.remaining()]; srcBuffer.get(actual); assertTrue(Arrays.equals(expected, actual)); } @Test public void testUnwrapBufferMediumMessageMax() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int payloadLength = WebSocketHeader.PAYLOAD_MEDIUM_MAX; int messageLength = payloadLength + WebSocketHeader.MED_HEADER_LENGTH_NOMASK; byte[] data = new byte[messageLength]; SECURE_RANDOM.nextBytes(data); data[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); data[1] = WebSocketHeader.PAYLOAD_EXTENDED_16; data[2] = (byte) (payloadLength >>> 8); data[3] = (byte) (payloadLength); ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); assertEquals(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_AMQP, spyWebSocketHandler.unwrapBuffer(srcBuffer).getType()); byte[] expected = Arrays.copyOfRange(data, WebSocketHeader.MED_HEADER_LENGTH_NOMASK, messageLength); byte[] actual = new byte[srcBuffer.remaining()]; srcBuffer.get(actual); assertTrue(Arrays.equals(expected, actual)); } @Test public void testUnwrapBufferLargeMessage() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int payloadLength = WebSocketHeader.PAYLOAD_MEDIUM_MAX + 100; int messageLength = payloadLength + WebSocketHeader.MAX_HEADER_LENGTH_NOMASK; byte[] data = new byte[messageLength]; SECURE_RANDOM.nextBytes(data); data[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); data[1] = WebSocketHeader.PAYLOAD_EXTENDED_64; data[2] = (byte) (payloadLength >>> 56); data[3] = (byte) (payloadLength >>> 48); data[4] = (byte) (payloadLength >>> 40); data[5] = (byte) (payloadLength >>> 32); data[6] = (byte) (payloadLength >>> 24); data[7] = (byte) (payloadLength >>> 16); data[8] = (byte) (payloadLength >>> 8); data[9] = (byte) (payloadLength); ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); assertEquals(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_AMQP, spyWebSocketHandler.unwrapBuffer(srcBuffer).getType()); byte[] expected = Arrays.copyOfRange(data, WebSocketHeader.MAX_HEADER_LENGTH_NOMASK, messageLength); byte[] actual = new byte[srcBuffer.remaining()]; srcBuffer.get(actual); assertTrue(Arrays.equals(expected, actual)); } @Test public void testUnwrapBufferInvalidOpcode() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); byte[] data = { 0b00001111, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; ByteBuffer srcBuffer = ByteBuffer.wrap(data); assertEquals(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_UNKNOWN, spyWebSocketHandler.unwrapBuffer(srcBuffer).getType()); } @Test public void testUnwrapBufferSrcBufferEmpty() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int messageLength = 10; ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); srcBuffer.flip(); assertEquals(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_UNKNOWN, spyWebSocketHandler.unwrapBuffer(srcBuffer).getType()); } @Test public void testUnwrapBufferSrcBufferMediumInvalidLength() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int messageLength = WebSocketHeader.MIN_HEADER_LENGTH + 1; byte[] data = new byte[messageLength]; SECURE_RANDOM.nextBytes(data); data[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); data[1] = WebSocketHeader.PAYLOAD_EXTENDED_16; data[2] = 0; ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); assertEquals(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_HEADER_CHUNK, spyWebSocketHandler.unwrapBuffer(srcBuffer).getType()); } @Test public void testUnwrapBufferSrcBufferLargeInvalidLength() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); int messageLength = WebSocketHeader.MED_HEADER_LENGTH_NOMASK + 1; byte[] data = new byte[messageLength]; SECURE_RANDOM.nextBytes(data); data[0] = (byte) (WebSocketHeader.FINBIT_MASK | WebSocketHeader.OPCODE_BINARY); data[1] = WebSocketHeader.PAYLOAD_EXTENDED_64; data[2] = 0; ByteBuffer srcBuffer = ByteBuffer.allocate(messageLength); srcBuffer.put(data); srcBuffer.flip(); assertEquals(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_HEADER_CHUNK, spyWebSocketHandler.unwrapBuffer(srcBuffer).getType()); } @Test(expected = IllegalArgumentException.class) public void testUnwrapBufferSrcBufferNull() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); ByteBuffer srcBuffer = null; spyWebSocketHandler.unwrapBuffer(srcBuffer); } @Test public void testCalculateHeaderSizeZeroPayload() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); assertEquals(webSocketHandler.calculateHeaderSize(0), 0); } @Test public void testCalculateHeaderSizeSmallPayload() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); assertEquals(webSocketHandler.calculateHeaderSize( WebSocketHeader.PAYLOAD_SHORT_MAX), WebSocketHeader.MIN_HEADER_LENGTH_MASKED); } @Test public void testCalculateHeaderSizeMediumPayload() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); assertEquals(webSocketHandler.calculateHeaderSize( WebSocketHeader.PAYLOAD_MEDIUM_MAX), WebSocketHeader.MED_HEADER_LENGTH_MASKED); } @Test public void testCalculateHeaderSizeLargePayload() { WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); assertEquals(webSocketHandler.calculateHeaderSize( WebSocketHeader.PAYLOAD_LARGE_MAX), WebSocketHeader.MAX_HEADER_LENGTH_MASKED); } } WebSocketImplTest.java000066400000000000000000002621451460332530100431110ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transport/ws/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.ws.impl; import com.microsoft.azure.proton.transport.ws.WebSocket; import com.microsoft.azure.proton.transport.ws.WebSocketHandler; import com.microsoft.azure.proton.transport.ws.WebSocketHeader; import org.apache.qpid.proton.engine.Transport; import org.apache.qpid.proton.engine.TransportException; import org.apache.qpid.proton.engine.impl.ByteBufferUtils; import org.apache.qpid.proton.engine.impl.TransportInput; import org.apache.qpid.proton.engine.impl.TransportOutput; import org.apache.qpid.proton.engine.impl.TransportWrapper; import org.junit.Assert; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class WebSocketImplTest { private static final int ALLOCATED_WEB_SOCKET_BUFFER_SIZE = (4 * 1024) + (16 * WebSocketHeader.MED_HEADER_LENGTH_MASKED); private static final int LENGTH_OF_UPGRADE_REQUEST = 284; private String hostName = "host_XXX"; private String webSocketPath = "path1/path2"; private String webSocketQuery = ""; private int webSocketPort = 1234567890; private String webSocketProtocol = "subprotocol_name"; private Map additionalHeaders = new HashMap(); private void init() { additionalHeaders.put("header1", "content1"); additionalHeaders.put("header2", "content2"); additionalHeaders.put("header3", "content3"); } @Test public void testConstructor() { init(); WebSocketImpl webSocketImpl = new WebSocketImpl(); ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); ByteBuffer pingBuffer = webSocketImpl.getPingBuffer(); assertNotNull(inputBuffer); assertNotNull(outputBuffer); assertNotNull(pingBuffer); assertEquals(inputBuffer.capacity(), ALLOCATED_WEB_SOCKET_BUFFER_SIZE); assertEquals(outputBuffer.capacity(), ALLOCATED_WEB_SOCKET_BUFFER_SIZE); assertEquals(pingBuffer.capacity(), ALLOCATED_WEB_SOCKET_BUFFER_SIZE); assertFalse(webSocketImpl.getEnabled()); } @Test public void testConstructorWithCustomBufferSize() { init(); int customBufferSize = 10; WebSocketImpl webSocketImpl = new WebSocketImpl(customBufferSize); ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); ByteBuffer pingBuffer = webSocketImpl.getPingBuffer(); assertNotNull(inputBuffer); assertNotNull(outputBuffer); assertNotNull(pingBuffer); assertEquals(inputBuffer.capacity(), customBufferSize); assertEquals(outputBuffer.capacity(), customBufferSize); assertEquals(pingBuffer.capacity(), customBufferSize); assertFalse(webSocketImpl.getEnabled()); } @Test public void testConfigureHandlerNull() { init(); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl.configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, null); assertNotNull(webSocketImpl.getWebSocketHandler()); assertTrue(webSocketImpl.getEnabled()); } @Test public void testConfigureHandlerNotNull() { init(); WebSocketImpl webSocketImpl = new WebSocketImpl(); WebSocketHandler webSocketHandler = new WebSocketHandlerImpl(); webSocketImpl.configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, webSocketHandler); Assert.assertEquals(webSocketHandler, webSocketImpl.getWebSocketHandler()); assertTrue(webSocketImpl.getEnabled()); } @Test public void testWriteUpgradeRequest() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, spyWebSocketHandler); webSocketImpl.writeUpgradeRequest(); verify(spyWebSocketHandler, times(1)) .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders); ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); outputBuffer.flip(); assertEquals(LENGTH_OF_UPGRADE_REQUEST, outputBuffer.remaining()); } @Test public void testWritePong() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, spyWebSocketHandler); ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); ByteBuffer pingBuffer = webSocketImpl.getPingBuffer(); webSocketImpl.writePong(); verify(spyWebSocketHandler, times(1)).createPong(pingBuffer, outputBuffer); } @Test public void testWriteClose() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, spyWebSocketHandler); String message = "Message"; ByteBuffer pingBuffer = webSocketImpl.getPingBuffer(); pingBuffer.clear(); pingBuffer.put(message.getBytes()); ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); webSocketImpl.writeClose(); assertTrue(Arrays.equals(pingBuffer.array(), outputBuffer.array())); } @Test public void testWrapCreatesSniffer() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl.configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, webSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); assertNotNull(transportWrapper); } @Test public void testWrapBufferEnabled() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); ByteBuffer srcBuffer = ByteBuffer.allocate(50); srcBuffer.clear(); ByteBuffer dstBuffer = ByteBuffer.allocate(50); webSocketImpl.isWebSocketEnabled = true; webSocketImpl.wrapBuffer(srcBuffer, dstBuffer); verify(mockWebSocketHandler, times(1)).wrapBuffer(srcBuffer, dstBuffer); } @Test public void testWrapBufferNotEnabled() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); ByteBuffer srcBuffer = ByteBuffer.allocate(25); srcBuffer.clear(); srcBuffer.put("abcdefghijklmnopqrstvwxyz".getBytes()); srcBuffer.flip(); ByteBuffer dstBuffer = ByteBuffer.allocate(25); dstBuffer.put("1234567890".getBytes()); webSocketImpl.isWebSocketEnabled = false; webSocketImpl.wrapBuffer(srcBuffer, dstBuffer); dstBuffer.flip(); assertTrue(Arrays.equals(srcBuffer.array(), dstBuffer.array())); verify(mockWebSocketHandler, times(0)).wrapBuffer((ByteBuffer) any(), (ByteBuffer) any()); } @Test public void testUnwrapBufferEnabled() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); ByteBuffer srcBuffer = ByteBuffer.allocate(50); srcBuffer.clear(); webSocketImpl.isWebSocketEnabled = true; webSocketImpl.unwrapBuffer(srcBuffer); verify(mockWebSocketHandler, times(1)).unwrapBuffer(srcBuffer); } @Test public void testUnwrapBufferNotEnabled() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); ByteBuffer srcBuffer = ByteBuffer.allocate(25); srcBuffer.clear(); srcBuffer.put("abcdefghijklmnopqrstvwxyz".getBytes()); srcBuffer.flip(); webSocketImpl.isWebSocketEnabled = false; assertTrue(webSocketImpl.unwrapBuffer(srcBuffer).getType() == WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_UNKNOWN); verify(mockWebSocketHandler, times(0)).wrapBuffer((ByteBuffer) any(), (ByteBuffer) any()); } @Test public void testPendingStateNotStarted() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl.configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, webSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); transportWrapper.pending(); ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); outputBuffer.flip(); assertTrue(outputBuffer.remaining() == LENGTH_OF_UPGRADE_REQUEST); } @Test public void testPendingStateNotStartedOutputNotEmpty() { init(); WebSocketImpl webSocketImpl = new WebSocketImpl(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); webSocketImpl.configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, webSocketHandler); String message = "Message"; ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); outputBuffer.put(message.getBytes()); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); assertTrue(message.length() == transportWrapper.pending()); ByteBuffer actual = webSocketImpl.getOutputBuffer(); assertTrue(Arrays.equals(outputBuffer.array(), actual.array())); } @Test public void testPendingStateNotStartedHeadClosed() { init(); WebSocketImpl webSocketImpl = new WebSocketImpl(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); webSocketImpl.configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, webSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); transportWrapper.close_tail(); assertTrue(transportWrapper.pending() == Transport.END_OF_STREAM); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_FAILED); ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); outputBuffer.flip(); assertTrue(outputBuffer.remaining() == LENGTH_OF_UPGRADE_REQUEST); } @Test public void testPendingStateConnecting() { init(); WebSocketImpl webSocketImpl = new WebSocketImpl(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl spyWebSocketHandler = spy(webSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); webSocketImpl.configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, webSocketHandler); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); outputBuffer.flip(); transportWrapper.pending(); assertTrue(outputBuffer.remaining() == LENGTH_OF_UPGRADE_REQUEST); } @Test public void testPendingStateConnectingHeadClosedEmptyBuffer() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl.configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, webSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); outputBuffer.clear(); transportWrapper.close_tail(); assertTrue(transportWrapper.pending() == Transport.END_OF_STREAM); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_FAILED); } @Test public void testPendingStateFlowEmptyOutput() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn("Request"); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); String message = "Message"; ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); outputBuffer.put(message.getBytes()); when(mockTransportOutput.pending()).thenReturn(0); assertEquals(transportWrapper.pending(), 0); verify(mockWebSocketHandler, times(0)).wrapBuffer((ByteBuffer) any(), (ByteBuffer) any()); } @Test public void testChunkedConnection() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl.configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, webSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); // Get the key that the upgrade verifier will expect String request = webSocketHandler.createUpgradeRequest("fakehost", "fakepath", "fakequery", 9999, "fakeprotocol", null); String[] lines = request.split("\r\n"); String extractedKey = null; for (String l : lines) { if (l.startsWith("Sec-WebSocket-Key: ")) { extractedKey = l.substring(19).trim(); break; } } String expectedKey = null; try { MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); expectedKey = Base64.encodeBase64StringLocal( messageDigest.digest((extractedKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes())).trim(); } catch (NoSuchAlgorithmException e) { // can't happen since SHA-1 is a known digest } // Assemble a response that the upgrade verifier will accept byte[] fakeInput = ( "http/1.1 101 switching protocols\nupgrade websocket\nconnection upgrade\nsec-websocket-protocol fakeprotocol\nsec-websocket-accept " + expectedKey).getBytes(); // Feed the response to the verifier, adding one byte at a time to simulate a response broken into chunks. // This test inspired by an issue with the IBM JRE which for some reason returned the service's response in multiple pieces. int i = 0; ByteBuffer inputBuffer = transportWrapper.tail(); for (i = 0; i < fakeInput.length - 1; i++) { inputBuffer.put(fakeInput[i]); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); } // Add the last byte and the state should change. inputBuffer.put(fakeInput[i]); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); } @Test public void testPendingStateFlowOutputNotEmpty() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn("Request"); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); String message = "Message"; ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); outputBuffer.put(message.getBytes()); when(mockTransportOutput.pending()).thenReturn(message.length()); when(mockWebSocketHandler.calculateHeaderSize(message.length())).thenReturn((int) WebSocketHeader.MIN_HEADER_LENGTH_MASKED); int expected = message.length() + WebSocketHeader.MIN_HEADER_LENGTH_MASKED; int actual = transportWrapper.pending(); assertEquals(expected, actual); verify(mockWebSocketHandler, times(0)).wrapBuffer((ByteBuffer) any(), (ByteBuffer) any()); verify(mockWebSocketHandler, times(1)).calculateHeaderSize(message.length()); } @Test public void testPendingStatePongChangesToFlow() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn("Request"); when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())) .thenReturn(new WebSocketHandler.WebsocketTuple(7, WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_PING)); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); String message = "Message"; ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); inputBuffer.clear(); inputBuffer.put(message.getBytes()); transportWrapper.process(); ByteBuffer pingBuffer = webSocketImpl.getPingBuffer(); inputBuffer.flip(); pingBuffer.flip(); assertTrue(Arrays.equals(inputBuffer.array(), pingBuffer.array())); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_PONG); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); } @Test public void testPendingStateClosingChangesToClosed() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn("Request"); when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())) .thenReturn(new WebSocketHandler.WebsocketTuple(7, WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_CLOSE)); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); String message = "Message"; ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); inputBuffer.clear(); inputBuffer.put(message.getBytes()); transportWrapper.process(); ByteBuffer pingBuffer = webSocketImpl.getPingBuffer(); inputBuffer.flip(); pingBuffer.flip(); assertTrue(Arrays.equals(inputBuffer.array(), pingBuffer.array())); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_CLOSING); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CLOSED); } @Test public void testPendingStateClosingHeadClosed() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn("Request"); when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())) .thenReturn(new WebSocketHandler.WebsocketTuple(7, WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_CLOSE)); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); String message = "Message"; ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); inputBuffer.clear(); inputBuffer.put(message.getBytes()); transportWrapper.process(); ByteBuffer pingBuffer = webSocketImpl.getPingBuffer(); inputBuffer.flip(); pingBuffer.flip(); assertTrue(Arrays.equals(inputBuffer.array(), pingBuffer.array())); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_CLOSING); transportWrapper.close_tail(); transportWrapper.pending(); assertTrue(transportWrapper.pending() == Transport.END_OF_STREAM); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_FAILED); } @Test public void testProcessStateNotStarted() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl.configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, webSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); verify(mockTransportInput, times(1)).process(); } @Test public void testProcessStateChangesFromConnectingToFlowOnValidReply() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn("Request"); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); } // @Test // public void testProcess_state_flow_repeated_reply() // { // init(); // // WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); // WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); // // WebSocketImpl webSocketImpl = new WebSocketImpl(); // webSocketImpl // .configure(_hostName,_webSocketPath,_webSocketQuery,_webSocketPort,_webSocketProtocol,_additionalHeaders,mockWebSocketHandler); // // TransportInput mockTransportInput = mock(TransportInput.class); // TransportOutput mockTransportOutput = mock(TransportOutput.class); // // TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); // // when(mockWebSocketHandler // .validateUpgradeReply((ByteBuffer) any())) // .thenReturn(true); // when(mockWebSocketHandler // .createUpgradeRequest(_hostName, _webSocketPath, _webSocketQuery, _webSocketPort, _webSocketProtocol, _additionalHeaders)) // .thenReturn("Request"); // // assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); // transportWrapper.pending(); // assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); // transportWrapper.process(); // assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); // // String message = "HTTP "; // ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); // inputBuffer.clear(); // inputBuffer.put(message.getBytes()); // // transportWrapper.process(); // verify(mockWebSocketHandler, times(0)).unwrapBuffer((ByteBuffer) any()); // } @Test public void testProcessStateFlowCallsUnderlyingAmqp() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = spy(new TransportInput() { ByteBuffer bb = ByteBufferUtils.newWriteableBuffer(4224); @Override public int capacity() { return bb.remaining(); } @Override public int position() { return bb.position(); } @Override public ByteBuffer tail() throws TransportException { return bb; } @Override public void process() throws TransportException { } @Override public void close_tail() { } }); TransportOutput mockTransportOutput = mock(TransportOutput.class); doNothing().when(mockTransportInput).process(); doNothing().when(mockTransportInput).close_tail(); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn("Request"); when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())) .thenReturn(new WebSocketHandler.WebsocketTuple(7, WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_AMQP)); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); String message = "Message"; ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); inputBuffer.clear(); inputBuffer.put(message.getBytes()); transportWrapper.process(); verify(mockTransportInput, times(1)).process(); } /*Not needed*/ // @Test // public void testProcess_state_flow_calls_underlying_empty() // { // init(); // // WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); // WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); // // WebSocketImpl webSocketImpl = new WebSocketImpl(); // webSocketImpl // .configure(_hostName, _webSocketPath, _webSocketQuery, _webSocketPort, _webSocketProtocol, _additionalHeaders, mockWebSocketHandler); // // TransportInput mockTransportInput = mock(TransportInput.class); // TransportOutput mockTransportOutput = mock(TransportOutput.class); // // TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); // // when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); // when(mockWebSocketHandler // .createUpgradeRequest(_hostName, _webSocketPath, _webSocketQuery, _webSocketPort, _webSocketProtocol, _additionalHeaders)) // .thenReturn("Request"); // when(mockWebSocketHandler // .unwrapBuffer((ByteBuffer) any())) // .thenReturn(new WebSocketHandler.WebsocketTuple(7, WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_UNKNOWN)); // // assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); // transportWrapper.pending(); // assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); // transportWrapper.process(); // assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); // // String message = "Message"; // ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); // inputBuffer.clear(); // inputBuffer.put(message.getBytes()); // // transportWrapper.process(); // verify(mockTransportInput, times(1)).process(); // } // @Test // public void testProcess_state_flow_calls_underlying_invalid() // { // init(); // // WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); // WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); // // WebSocketImpl webSocketImpl = new WebSocketImpl(); // webSocketImpl // .configure(_hostName, _webSocketPath, _webSocketQuery, _webSocketPort, _webSocketProtocol, _additionalHeaders, mockWebSocketHandler); // // TransportInput mockTransportInput = mock(TransportInput.class); // TransportOutput mockTransportOutput = mock(TransportOutput.class); // // TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); // // when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); // when(mockWebSocketHandler // .createUpgradeRequest(_hostName, _webSocketPath, _webSocketQuery, _webSocketPort, _webSocketProtocol, _additionalHeaders)) // .thenReturn("Request"); // when(mockWebSocketHandler // .unwrapBuffer((ByteBuffer) any())) // .thenReturn(new WebSocketHandler.WebsocketTuple(7, WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_UNKNOWN)); // // assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); // transportWrapper.pending(); // assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); // transportWrapper.process(); // assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); // // String message = "Message"; // ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); // inputBuffer.clear(); // inputBuffer.put(message.getBytes()); // // transportWrapper.process(); // verify(mockTransportInput, times(1)).process(); // } // @Test // public void testProcess_state_flow_calls_underlying_invalid_length() // { // init(); // // WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); // WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); // // WebSocketImpl webSocketImpl = new WebSocketImpl(); // webSocketImpl // .configure(_hostName, _webSocketPath, _webSocketQuery, _webSocketPort, _webSocketProtocol, _additionalHeaders, mockWebSocketHandler); // // TransportInput mockTransportInput = mock(TransportInput.class); // TransportOutput mockTransportOutput = mock(TransportOutput.class); // // TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); // // when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); // when(mockWebSocketHandler // .createUpgradeRequest(_hostName, _webSocketPath, _webSocketQuery, _webSocketPort, _webSocketProtocol, _additionalHeaders)) // .thenReturn("Request"); // when(mockWebSocketHandler // .unwrapBuffer((ByteBuffer) any())) // .thenReturn(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_UNKNOWN); // // assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); // transportWrapper.pending(); // assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); // transportWrapper.process(); // assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); // // String message = "Message"; // ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); // inputBuffer.clear(); // inputBuffer.put(message.getBytes()); // // transportWrapper.process(); // verify(mockTransportInput, times(1)).process(); // } // // @Test // public void testProcess_state_flow_calls_underlying_invalid_masked() // { // init(); // // WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); // WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); // // WebSocketImpl webSocketImpl = new WebSocketImpl(); // webSocketImpl // .configure(_hostName, _webSocketPath, _webSocketQuery, _webSocketPort, _webSocketProtocol, _additionalHeaders, mockWebSocketHandler); // // TransportInput mockTransportInput = mock(TransportInput.class); // TransportOutput mockTransportOutput = mock(TransportOutput.class); // // TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); // // when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); // when(mockWebSocketHandler // .createUpgradeRequest(_hostName, _webSocketPath, _webSocketQuery, _webSocketPort, _webSocketProtocol, _additionalHeaders)) // .thenReturn("Request"); // when(mockWebSocketHandler // .unwrapBuffer((ByteBuffer) any())) // .thenReturn(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_UNKNOWN); // // assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); // transportWrapper.pending(); // assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); // transportWrapper.process(); // assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); // // String message = "Message"; // ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); // inputBuffer.clear(); // inputBuffer.put(message.getBytes()); // // transportWrapper.process(); // verify(mockTransportInput, times(1)).process(); // } // @Test public void testProcessStateFlowChangesToPongAfterPingAndCopiesTheBuffer() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn("Request"); when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())) .thenReturn(new WebSocketHandler.WebsocketTuple(7, WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_PING)); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); String message = "Message"; ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); inputBuffer.clear(); inputBuffer.put(message.getBytes()); transportWrapper.process(); ByteBuffer pingBuffer = webSocketImpl.getPingBuffer(); inputBuffer.flip(); pingBuffer.flip(); assertTrue(Arrays.equals(inputBuffer.array(), pingBuffer.array())); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_PONG); } @Test public void testProcessStateFlowChangesToClosingAfterCloseAndCopiesTheBuffer() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn("Request"); when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())) .thenReturn(new WebSocketHandler.WebsocketTuple(7, WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_CLOSE)); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); String message = "Message"; ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); inputBuffer.clear(); inputBuffer.put(message.getBytes()); transportWrapper.process(); ByteBuffer pingBuffer = webSocketImpl.getPingBuffer(); inputBuffer.flip(); pingBuffer.flip(); assertTrue(Arrays.equals(inputBuffer.array(), pingBuffer.array())); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_CLOSING); } @Test public void testProcessStatePongChangesToFlowHeadClosed() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn("Request"); when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())) .thenReturn(new WebSocketHandler.WebsocketTuple(7, WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_PING)); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); String message = "Message"; ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); inputBuffer.clear(); inputBuffer.put(message.getBytes()); transportWrapper.process(); ByteBuffer pingBuffer = webSocketImpl.getPingBuffer(); inputBuffer.flip(); pingBuffer.flip(); assertTrue(Arrays.equals(inputBuffer.array(), pingBuffer.array())); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_PONG); ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); outputBuffer.clear(); transportWrapper.close_tail(); transportWrapper.pending(); assertTrue(transportWrapper.pending() == Transport.END_OF_STREAM); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_FAILED); } private byte[] createMessage(int size) { byte[] data = new byte[size]; Utils.getSecureRandom().nextBytes(data); byte finbit = (byte) (WebSocketHeader.FINBIT_MASK & 0xFF); byte opcode = WebSocketHeader.OPCODE_MASK & 0x2; byte firstbyte = (byte) (finbit | opcode); byte secondbyte = (byte) (size - 2); data[0] = firstbyte; data[1] = secondbyte; return data; } private byte[] getChunk(byte[] source, int chunkSize, int startPosition) { return Arrays.copyOfRange(source, startPosition, Math.min(source.length, startPosition + chunkSize)); } @Test public void testProcessMultipleWSRequestsChunks() { byte[] message1 = createMessage(100); byte[] message2 = createMessage(100); byte[] message3 = createMessage(100); byte[] concatenatedArray = new byte[message1.length + message2.length + message3.length]; final byte[] expectedFinalArray = new byte[concatenatedArray.length - 6]; final ByteBuffer actualFinalBuffer = ByteBufferUtils.newWriteableBuffer(4224); System.arraycopy(message1, 0, concatenatedArray, 0, message1.length); System.arraycopy(message2, 0, concatenatedArray, message1.length, message2.length); System.arraycopy(message3, 0, concatenatedArray, message1.length + message2.length, message3.length); System.arraycopy(message1, 2, expectedFinalArray, 0, message1.length - 2); System.arraycopy(message2, 2, expectedFinalArray, message1.length - 2, message2.length - 2); System.arraycopy(message3, 2, expectedFinalArray, message1.length + message2.length - 4, message3.length - 2); int chunkCount = 10; int bytesPerChunk = concatenatedArray.length / chunkCount; init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = spy(new TransportInput() { ByteBuffer bb = ByteBufferUtils.newWriteableBuffer(4224); @Override public int capacity() { return bb.remaining(); } @Override public int position() { return bb.position(); } @Override public ByteBuffer tail() throws TransportException { return bb; } @Override public void process() throws TransportException { bb.flip(); actualFinalBuffer.put(bb); } @Override public void close_tail() { } }); TransportOutput mockTransportOutput = mock(TransportOutput.class); //doNothing().when(mockTransportInput).process(); doNothing().when(mockTransportInput).close_tail(); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn("Request"); when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())).thenAnswer(new Answer() { @Override public WebSocketHandler.WebsocketTuple answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); ByteBuffer bb = (ByteBuffer) arguments[0]; bb.position(2); return new WebSocketHandler.WebsocketTuple(98, WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_AMQP); } }); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); ByteBuffer wsInputBuffer = webSocketImpl.getWsInputBuffer(); for (int i = 0; i < chunkCount; i++) { byte[] message = getChunk(concatenatedArray, bytesPerChunk, i * bytesPerChunk); inputBuffer.clear(); inputBuffer.put(message); transportWrapper.process(); mockTransportInput.tail().clear(); } actualFinalBuffer.flip(); byte[] actualFinalArray = new byte[actualFinalBuffer.limit()]; actualFinalBuffer.get(actualFinalArray); assertTrue(Arrays.equals(expectedFinalArray, actualFinalArray)); //Subtract 1 because the first 2 bytes are used as the header which come in 2 separate chunks //verify(mockTransportInput, times(chunkCount-1)).process(); } @Test public void testProcessSmallChunksOneBytePayloadNoMask() { final int payloadLength = 125; int chunkSize = 10; int chunkCount = 1 + payloadLength / chunkSize; init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = spy(new TransportInput() { ByteBuffer bb = ByteBufferUtils.newWriteableBuffer(4224); @Override public int capacity() { return bb.remaining(); } @Override public int position() { return bb.position(); } @Override public ByteBuffer tail() throws TransportException { return bb; } @Override public void process() throws TransportException { } @Override public void close_tail() { } }); TransportOutput mockTransportOutput = mock(TransportOutput.class); doNothing().when(mockTransportInput).process(); doNothing().when(mockTransportInput).close_tail(); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn("Request"); when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())).thenAnswer(new Answer() { @Override public WebSocketHandler.WebsocketTuple answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); ByteBuffer bb = (ByteBuffer) arguments[0]; bb.position(2); return new WebSocketHandler.WebsocketTuple(payloadLength, WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_AMQP); } }); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); ByteBuffer wsInputBuffer = webSocketImpl.getWsInputBuffer(); byte[] message = new byte[chunkSize]; byte finbit = (byte) (WebSocketHeader.FINBIT_MASK & 0xFF); byte opcode = WebSocketHeader.OPCODE_MASK & 0x2; byte firstbyte = (byte) (finbit | opcode); byte secondbyte = (byte) payloadLength; message[0] = firstbyte; message[1] = secondbyte; int currentLength = 0; for (int i = 0; i < chunkCount + 1; i++) { if (i == 0) { inputBuffer.clear(); inputBuffer.put(firstbyte); currentLength += 1; transportWrapper.process(); } else if (i == 1) { inputBuffer.clear(); inputBuffer.put(secondbyte); currentLength += 1; transportWrapper.process(); } else { char c = (char) (65 + i & 0xFF); for (int j = 0; j < chunkSize; j++) { message[j] += c; } inputBuffer.clear(); inputBuffer.put(message); currentLength += chunkSize; transportWrapper.process(); //Get the buffer from the underlying input to check ByteBuffer bb = mockTransportInput.tail(); bb.flip(); byte[] transportInputArray = new byte[bb.remaining()]; bb.duplicate().get(transportInputArray); //Check that the message chunk we sent is what the underlying input is going to process assertTrue(Arrays.equals(message, transportInputArray)); mockTransportInput.tail().clear(); } assertEquals(inputBuffer.position(), 0); assertEquals(inputBuffer.limit(), inputBuffer.capacity()); } //Subtract 1 because the first 2 bytes are used as the header which come in 2 separate chunks verify(mockTransportInput, times(chunkCount - 1)).process(); } @Test public void testHeadWebsocketNotEnabled() { init(); WebSocketImpl webSocketImpl = new WebSocketImpl(); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); webSocketImpl.isWebSocketEnabled = false; transportWrapper.head(); verify(mockTransportOutput, times(1)).head(); } @Test public void testHeadStateNotStarted() { init(); WebSocketImpl webSocketImpl = new WebSocketImpl(); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); webSocketImpl.isWebSocketEnabled = true; transportWrapper.head(); verify(mockTransportOutput, times(1)).head(); } @Test public void testHeadStateConnecting() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); String request = "Request"; when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn(request); //when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())).thenReturn(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_PING); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); ByteBuffer actual = transportWrapper.head(); byte[] a = new byte[actual.remaining()]; actual.get(a); assertTrue(Arrays.equals(request.getBytes(), a)); } @Test public void testHeadStateFlowUnderlyingHeadEmpty() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); String request = "Request"; when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn(request); //when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())).thenReturn(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_PING); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); ByteBuffer actual = transportWrapper.head(); byte[] a = new byte[actual.remaining()]; actual.get(a); assertTrue(Arrays.equals(request.getBytes(), a)); verify(mockTransportOutput, times(0)).head(); } @Test public void testHeadStateFlowUnderlyingHeadNotEmpty() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); String request = "Request"; when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn(request); //when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())).thenReturn(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_PING); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); when(mockTransportOutput.pending()).thenReturn(1024); ByteBuffer actual = transportWrapper.head(); byte[] a = new byte[actual.remaining()]; actual.get(a); assertTrue(Arrays.equals(request.getBytes(), a)); verify(mockWebSocketHandler, times(1)).wrapBuffer((ByteBuffer) any(), (ByteBuffer) any()); verify(mockTransportOutput, times(1)).head(); } @Test public void testHeadStatePong() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); String request = "Request"; when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn(request); when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())) .thenReturn(new WebSocketHandler.WebsocketTuple(7, WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_PING)); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); String message = "Message"; ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); inputBuffer.clear(); inputBuffer.put(message.getBytes()); transportWrapper.process(); ByteBuffer pingBuffer = webSocketImpl.getPingBuffer(); inputBuffer.flip(); pingBuffer.flip(); assertTrue(Arrays.equals(inputBuffer.array(), pingBuffer.array())); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_PONG); ByteBuffer actual = transportWrapper.head(); byte[] a = new byte[actual.remaining()]; actual.get(a); assertTrue(Arrays.equals(request.getBytes(), a)); } @Test public void testPopWebsocketNotEnabled() { init(); WebSocketImpl webSocketImpl = new WebSocketImpl(); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); webSocketImpl.isWebSocketEnabled = false; String message = "Message"; ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); outputBuffer.clear(); outputBuffer.put(message.getBytes()); transportWrapper.pop(message.getBytes().length); verify(mockTransportOutput, times(1)).pop(message.getBytes().length); } @Test public void testPopWebsocketNotStarted() { init(); WebSocketImpl webSocketImpl = new WebSocketImpl(); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); webSocketImpl.isWebSocketEnabled = true; String message = "Message"; ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); outputBuffer.clear(); outputBuffer.put(message.getBytes()); transportWrapper.pop(message.getBytes().length); verify(mockTransportOutput, times(1)).pop(message.getBytes().length); } @Test public void testPopWebsocketConnecting() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); String request = "Request"; when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn(request); //when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())).thenReturn(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_PING); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); String message = "Message"; ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); outputBuffer.clear(); outputBuffer.put(message.getBytes()); transportWrapper.pop(message.getBytes().length); ByteBuffer actual = webSocketImpl.getOutputBuffer(); assertTrue(actual.limit() == ALLOCATED_WEB_SOCKET_BUFFER_SIZE); assertTrue(actual.position() == 0); } @Test public void testPopStateConnectedFlow() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); String request = "Request"; when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn(request); //when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())).thenReturn(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_PING); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); int webSocketHeaderSize = transportWrapper.pending(); String message = "Message"; ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); outputBuffer.clear(); outputBuffer.put(message.getBytes()); transportWrapper.pop(message.getBytes().length); ByteBuffer actual = webSocketImpl.getOutputBuffer(); assertTrue(actual.limit() == ALLOCATED_WEB_SOCKET_BUFFER_SIZE); assertTrue(actual.position() == 0); verify(mockTransportOutput, times(1)).pop(message.getBytes().length - webSocketHeaderSize); } @Test public void testPopStateConnectedPong() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); String request = "Request"; when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn(request); when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())) .thenReturn(new WebSocketHandler.WebsocketTuple(7, WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_PING)); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); String message = "Message"; ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); inputBuffer.clear(); inputBuffer.put(message.getBytes()); transportWrapper.process(); ByteBuffer pingBuffer = webSocketImpl.getPingBuffer(); inputBuffer.flip(); pingBuffer.flip(); assertTrue(Arrays.equals(inputBuffer.array(), pingBuffer.array())); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_PONG); ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); transportWrapper.pop(message.getBytes().length); ByteBuffer actual = webSocketImpl.getOutputBuffer(); assertTrue(actual.limit() == ALLOCATED_WEB_SOCKET_BUFFER_SIZE); assertTrue(actual.position() == 0); verify(mockTransportOutput, times(1)).pop(message.getBytes().length); } @Test public void testPopWebsocketConnectingOutbutBufferIsNotEmpty() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); String request = "Request"; when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn(request); //when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())).thenReturn(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_PING); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); String message = "Message"; ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); outputBuffer.clear(); outputBuffer.put(message.getBytes()); outputBuffer.flip(); transportWrapper.pop(message.getBytes().length); verify(mockTransportOutput, times(1)).pop(message.getBytes().length); } @Test public void testPopStateConnectedFlowOutbutBufferIsNotEmpty() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); String request = "Request"; when(mockWebSocketHandler.validateUpgradeReply((ByteBuffer) any())).thenReturn(true); when(mockWebSocketHandler .createUpgradeRequest(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders)) .thenReturn(request); //when(mockWebSocketHandler.unwrapBuffer((ByteBuffer) any())).thenReturn(WebSocketHandler.WebSocketMessageType.WEB_SOCKET_MESSAGE_TYPE_PING); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_NOT_STARTED); transportWrapper.pending(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTING); transportWrapper.process(); assertTrue(webSocketImpl.getState() == WebSocket.WebSocketState.PN_WS_CONNECTED_FLOW); String message = "Message"; ByteBuffer outputBuffer = webSocketImpl.getOutputBuffer(); outputBuffer.clear(); outputBuffer.put(message.getBytes()); outputBuffer.flip(); transportWrapper.pop(message.getBytes().length); verify(mockTransportOutput, times(1)).pop(message.getBytes().length); } @Test public void testCapacityEnabled() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); String message = "Message"; ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); inputBuffer.clear(); inputBuffer.put(message.getBytes()); inputBuffer.flip(); webSocketImpl.isWebSocketEnabled = true; int actual = transportWrapper.capacity(); assertTrue(message.length() == actual); verify(mockTransportInput, times(0)).capacity(); } @Test public void testCapacityEnabledTailClosed() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); String message = "Message"; ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); inputBuffer.clear(); inputBuffer.put(message.getBytes()); inputBuffer.flip(); webSocketImpl.isWebSocketEnabled = true; transportWrapper.close_tail(); int actual = transportWrapper.capacity(); assertTrue(Transport.END_OF_STREAM == actual); verify(mockTransportInput, times(0)).capacity(); } @Test public void testCapacityNotEnabled() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); String message = "Message"; ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); inputBuffer.clear(); inputBuffer.put(message.getBytes()); inputBuffer.flip(); webSocketImpl.isWebSocketEnabled = false; transportWrapper.capacity(); verify(mockTransportInput, times(1)).capacity(); } @Test public void testPositionEnabled() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); String message = "Message"; ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); inputBuffer.clear(); inputBuffer.put(message.getBytes()); webSocketImpl.isWebSocketEnabled = true; int actual = transportWrapper.position(); assertTrue(message.length() == actual); verify(mockTransportInput, times(0)).position(); } @Test public void testPositionEnabledTailClosed() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); String message = "Message"; ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); inputBuffer.clear(); inputBuffer.put(message.getBytes()); webSocketImpl.isWebSocketEnabled = true; transportWrapper.close_tail(); int actual = transportWrapper.position(); assertTrue(Transport.END_OF_STREAM == actual); verify(mockTransportInput, times(0)).position(); } @Test public void testPositionNotEnabled() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); String message = "Message"; ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); inputBuffer.clear(); inputBuffer.put(message.getBytes()); webSocketImpl.isWebSocketEnabled = false; transportWrapper.position(); verify(mockTransportInput, times(1)).position(); } @Test public void testTail() { init(); WebSocketImpl webSocketImpl = new WebSocketImpl(); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); webSocketImpl.isWebSocketEnabled = true; String message = "Message"; ByteBuffer inputBuffer = webSocketImpl.getInputBuffer(); inputBuffer.clear(); inputBuffer.put(message.getBytes()); inputBuffer.flip(); ByteBuffer actual = transportWrapper.tail(); byte[] a = new byte[actual.remaining()]; actual.get(a); assertTrue(Arrays.equals(message.getBytes(), a)); verify(mockTransportInput, times(0)).tail(); } @Test public void testTailWebsocketNotEnabled() { init(); WebSocketImpl webSocketImpl = new WebSocketImpl(); TransportInput mockTransportInput = mock(TransportInput.class); TransportOutput mockTransportOutput = mock(TransportOutput.class); TransportWrapper transportWrapper = webSocketImpl.wrap(mockTransportInput, mockTransportOutput); webSocketImpl.isWebSocketEnabled = false; transportWrapper.tail(); verify(mockTransportInput, times(1)).tail(); } @Test public void testToString() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl .configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, additionalHeaders, mockWebSocketHandler); webSocketImpl.isWebSocketEnabled = true; String actual = webSocketImpl.toString(); String expected1 = String.join("", "WebSocketImpl [isWebSocketEnabled=true", ", state=PN_WS_NOT_STARTED", ", protocol=" + webSocketProtocol, ", host=" + hostName, ", path=" + webSocketPath, ", query=" + webSocketQuery, ", port=" + webSocketPort); String expected2 = ", additionalHeaders=header3:content3, header2:content2, header1:content1]"; assertTrue(actual.startsWith(expected1)); actual = actual.substring(expected1.length()); assertTrue(actual.equals(expected2)); } @Test public void testToStringNoAdditionalHeaders() { init(); WebSocketHandlerImpl webSocketHandler = new WebSocketHandlerImpl(); WebSocketHandlerImpl mockWebSocketHandler = mock(webSocketHandler.getClass()); WebSocketImpl webSocketImpl = new WebSocketImpl(); webSocketImpl.configure(hostName, webSocketPath, webSocketQuery, webSocketPort, webSocketProtocol, null, mockWebSocketHandler); webSocketImpl.isWebSocketEnabled = true; String actual = webSocketImpl.toString(); String expected = String.join("", "WebSocketImpl [isWebSocketEnabled=true", ", state=PN_WS_NOT_STARTED", ", protocol=" + webSocketProtocol, ", host=" + hostName, ", path=" + webSocketPath, ", query=" + webSocketQuery, ", port=" + webSocketPort, "]"); assertEquals("Unexpected value for toString()", expected, actual); } } WebSocketSnifferTest.java000066400000000000000000000051061460332530100435740ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transport/ws/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.ws.impl; import com.microsoft.azure.proton.transport.ws.WebSocketHeader; import org.apache.qpid.proton.engine.impl.TransportWrapper; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; public class WebSocketSnifferTest { @Test public void testMakeDeterminationWrapper1() { TransportWrapper mockTransportWrapper1 = mock(TransportWrapper.class); TransportWrapper mockTransportWrapper2 = mock(TransportWrapper.class); WebSocketSniffer webSocketSniffer = new WebSocketSniffer(mockTransportWrapper1, mockTransportWrapper2); assertEquals("Incorrect header size", WebSocketHeader.MIN_HEADER_LENGTH_MASKED, webSocketSniffer.bufferSize()); byte[] bytes = new byte[WebSocketHeader.MIN_HEADER_LENGTH_MASKED]; bytes[0] = WebSocketHeader.FINAL_OPCODE_BINARY; webSocketSniffer.makeDetermination(bytes); assertEquals("Incorrect wrapper selected", mockTransportWrapper1, webSocketSniffer.getSelectedTransportWrapper()); } @Test public void testMakeDeterminationWrapper2() { TransportWrapper mockTransportWrapper1 = mock(TransportWrapper.class); TransportWrapper mockTransportWrapper2 = mock(TransportWrapper.class); WebSocketSniffer webSocketSniffer = new WebSocketSniffer(mockTransportWrapper1, mockTransportWrapper2); assertEquals("Incorrect header size", WebSocketHeader.MIN_HEADER_LENGTH_MASKED, webSocketSniffer.bufferSize()); byte[] bytes = new byte[WebSocketHeader.MIN_HEADER_LENGTH_MASKED]; bytes[0] = (byte) 0x81; webSocketSniffer.makeDetermination(bytes); assertEquals("Incorrect wrapper selected", mockTransportWrapper2, webSocketSniffer.getSelectedTransportWrapper()); } @Test(expected = IllegalArgumentException.class) public void testMakeDeterminationInsufficientBytes() { TransportWrapper mockTransportWrapper1 = mock(TransportWrapper.class); TransportWrapper mockTransportWrapper2 = mock(TransportWrapper.class); WebSocketSniffer webSocketSniffer = new WebSocketSniffer(mockTransportWrapper1, mockTransportWrapper2); assertEquals("Incorrect header size", WebSocketHeader.MIN_HEADER_LENGTH_MASKED, webSocketSniffer.bufferSize()); byte[] bytes = new byte[WebSocketHeader.MIN_HEADER_LENGTH_MASKED - 1]; bytes[0] = WebSocketHeader.FINAL_OPCODE_BINARY; webSocketSniffer.makeDetermination(bytes); } } WebSocketUpgradeTest.java000066400000000000000000000577371460332530100436100ustar00rootroot00000000000000qpid-proton-j-extensions-qpid-proton-j-extensions_1.2.5/src/test/java/com/microsoft/azure/proton/transport/ws/impl// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.azure.proton.transport.ws.impl; import org.junit.Test; import java.security.InvalidParameterException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map; import java.util.Scanner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class WebSocketUpgradeTest { static final String RFC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; @Test public void testCreateUpgradeRequestAllParam() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; String queryKey = "?iothub-no-client-cert="; String queryValue = "true"; Map additionalHeaders = new HashMap(); additionalHeaders.put("header1", "content1"); additionalHeaders.put("header2", "content2"); additionalHeaders.put("header3", "content3"); WebSocketUpgrade webSocketUpgrade = new WebSocketUpgrade(hostName, webSocketPath, queryKey + queryValue, webSocketPort, webSocketProtocol, additionalHeaders); String actual = webSocketUpgrade.createUpgradeRequest(); Boolean isLineCountOk = false; Boolean isStatusLineOk = false; Boolean isUpgradeHeaderOk = false; Boolean isConnectionHeaderOk = false; Boolean isWebSocketVersionHeaderOk = false; Boolean isWebSocketKeyHeaderOk = false; Boolean isWebSocketProtocolHeaderOk = false; Boolean isHostHeaderOk = false; Boolean isAdditonalHeader1Ok = false; Boolean isAdditonalHeader2Ok = false; Boolean isAdditonalHeader3Ok = false; Scanner scanner = new Scanner(actual); int lineCount = 0; while (scanner.hasNextLine()) { lineCount++; String line = scanner.nextLine(); if (line.equals("GET https://" + hostName + "/" + webSocketPath + queryKey + queryValue + " HTTP/1.1")) { isStatusLineOk = true; continue; } if (line.equals("Connection: Upgrade,Keep-Alive")) { isConnectionHeaderOk = true; continue; } if (line.equals("Upgrade: websocket")) { isUpgradeHeaderOk = true; continue; } if (line.equals("Sec-WebSocket-Version: 13")) { isWebSocketVersionHeaderOk = true; continue; } if (line.startsWith("Sec-WebSocket-Key: ")) { String keyBase64 = line.substring(19); if (keyBase64.length() == 24) { byte[] decoded = Base64.decodeBase64Local(keyBase64.getBytes()); if (decoded.length == 16) { isWebSocketKeyHeaderOk = true; } } continue; } if (line.equals("Sec-WebSocket-Protocol: " + webSocketProtocol)) { isWebSocketProtocolHeaderOk = true; continue; } if (line.equals("Host: host_XXX")) { isHostHeaderOk = true; continue; } if (line.equals("header1: content1")) { isAdditonalHeader1Ok = true; continue; } if (line.equals("header2: content2")) { isAdditonalHeader2Ok = true; continue; } if (line.equals("header3: content3")) { isAdditonalHeader3Ok = true; continue; } } if (lineCount == 11) { isLineCountOk = true; } assertTrue(isLineCountOk); assertTrue(isStatusLineOk); assertTrue(isUpgradeHeaderOk); assertTrue(isConnectionHeaderOk); assertTrue(isWebSocketVersionHeaderOk); assertTrue(isWebSocketKeyHeaderOk); assertTrue(isWebSocketProtocolHeaderOk); assertTrue(isHostHeaderOk); assertTrue(isAdditonalHeader1Ok); assertTrue(isAdditonalHeader2Ok); assertTrue(isAdditonalHeader3Ok); } @Test public void testCreateUpgradeRequestNoAdditonalHeaders() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; String queryKey = "?iothub-no-client-cert="; String queryValue = "true"; WebSocketUpgrade webSocketUpgrade = new WebSocketUpgrade(hostName, webSocketPath, queryKey + queryValue, webSocketPort, webSocketProtocol, null); String actual = webSocketUpgrade.createUpgradeRequest(); Boolean isLineCountOk = false; Boolean isStatusLineOk = false; Boolean isUpgradeHeaderOk = false; Boolean isConnectionHeaderOk = false; Boolean isWebSocketVersionHeaderOk = false; Boolean isWebSocketKeyHeaderOk = false; Boolean isWebSocketProtocolHeaderOk = false; Boolean isHostHeaderOk = false; Scanner scanner = new Scanner(actual); int lineCount = 0; while (scanner.hasNextLine()) { lineCount++; String line = scanner.nextLine(); if (line.equals("GET https://" + hostName + "/" + webSocketPath + queryKey + queryValue + " HTTP/1.1")) { isStatusLineOk = true; continue; } if (line.equals("Connection: Upgrade,Keep-Alive")) { isConnectionHeaderOk = true; continue; } if (line.equals("Upgrade: websocket")) { isUpgradeHeaderOk = true; continue; } if (line.equals("Sec-WebSocket-Version: 13")) { isWebSocketVersionHeaderOk = true; continue; } if (line.startsWith("Sec-WebSocket-Key: ")) { String keyBase64 = line.substring(19); if (keyBase64.length() == 24) { byte[] decoded = Base64.decodeBase64Local(keyBase64.getBytes()); if (decoded.length == 16) { isWebSocketKeyHeaderOk = true; } } continue; } if (line.equals("Sec-WebSocket-Protocol: " + webSocketProtocol)) { isWebSocketProtocolHeaderOk = true; continue; } if (line.equals("Host: host_XXX")) { isHostHeaderOk = true; continue; } } if (lineCount == 8) { isLineCountOk = true; } assertTrue(isLineCountOk); assertTrue(isStatusLineOk); assertTrue(isUpgradeHeaderOk); assertTrue(isConnectionHeaderOk); assertTrue(isWebSocketVersionHeaderOk); assertTrue(isWebSocketKeyHeaderOk); assertTrue(isWebSocketProtocolHeaderOk); assertTrue(isHostHeaderOk); } @Test public void testCreateUpgradeRequestPathStartWithSlash() { String hostName = "host_XXX"; String webSocketPath = "/path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; String queryKey = "?iothub-no-client-cert="; String queryValue = "true"; WebSocketUpgrade webSocketUpgrade = new WebSocketUpgrade(hostName, webSocketPath, queryKey + queryValue, webSocketPort, webSocketProtocol, null); String actual = webSocketUpgrade.createUpgradeRequest(); Boolean isLineCountOk = false; Boolean isStatusLineOk = false; Boolean isUpgradeHeaderOk = false; Boolean isConnectionHeaderOk = false; Boolean isWebSocketVersionHeaderOk = false; Boolean isWebSocketKeyHeaderOk = false; Boolean isWebSocketProtocolHeaderOk = false; Boolean isHostHeaderOk = false; Scanner scanner = new Scanner(actual); int lineCount = 0; while (scanner.hasNextLine()) { lineCount++; String line = scanner.nextLine(); if (line.equals("GET https://" + hostName + webSocketPath + queryKey + queryValue + " HTTP/1.1")) { isStatusLineOk = true; continue; } if (line.equals("Connection: Upgrade,Keep-Alive")) { isConnectionHeaderOk = true; continue; } if (line.equals("Upgrade: websocket")) { isUpgradeHeaderOk = true; continue; } if (line.equals("Sec-WebSocket-Version: 13")) { isWebSocketVersionHeaderOk = true; continue; } if (line.startsWith("Sec-WebSocket-Key: ")) { String keyBase64 = line.substring(19); if (keyBase64.length() == 24) { byte[] decoded = Base64.decodeBase64Local(keyBase64.getBytes()); if (decoded.length == 16) { isWebSocketKeyHeaderOk = true; } } continue; } if (line.equals("Sec-WebSocket-Protocol: " + webSocketProtocol)) { isWebSocketProtocolHeaderOk = true; continue; } if (line.equals("Host: host_XXX")) { isHostHeaderOk = true; continue; } } if (lineCount == 8) { isLineCountOk = true; } assertTrue(isLineCountOk); assertTrue(isStatusLineOk); assertTrue(isUpgradeHeaderOk); assertTrue(isConnectionHeaderOk); assertTrue(isWebSocketVersionHeaderOk); assertTrue(isWebSocketKeyHeaderOk); assertTrue(isWebSocketProtocolHeaderOk); assertTrue(isHostHeaderOk); } @Test(expected = InvalidParameterException.class) public void testCreateUpgradeRequestEmptyHost() { String hostName = ""; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; Map additionalHeaders = new HashMap(); additionalHeaders.put("header1", "content1"); additionalHeaders.put("header2", "content2"); additionalHeaders.put("header3", "content3"); WebSocketUpgrade webSocketUpgrade = new WebSocketUpgrade(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, additionalHeaders); webSocketUpgrade.createUpgradeRequest(); } @Test(expected = InvalidParameterException.class) public void testCreateUpgradeRequestEmptyProtocol() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = ""; Map additionalHeaders = new HashMap(); additionalHeaders.put("header1", "content1"); additionalHeaders.put("header2", "content2"); additionalHeaders.put("header3", "content3"); WebSocketUpgrade webSocketUpgrade = new WebSocketUpgrade(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, additionalHeaders); webSocketUpgrade.createUpgradeRequest(); } @Test public void testValidateUpgradeReply() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; WebSocketUpgrade webSocketUpgrade = new WebSocketUpgrade(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, null); String upgradeRequest = webSocketUpgrade.createUpgradeRequest(); String keyBase64 = upgradeRequest .substring(upgradeRequest.lastIndexOf("Sec-WebSocket-Key: ") + 19, upgradeRequest.lastIndexOf("Sec-WebSocket-Key: ") + 43); try { MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); String serverKey = Base64.encodeBase64StringLocal(messageDigest.digest((keyBase64 + RFC_GUID).getBytes())).trim(); String responseStr = String.join("\n", "HTTP/1.1 101 Switching Protocols", "Upgrade: websocket", "Server: XXXYYYZZZ", "Sec-WebSocket-Protocol: " + webSocketProtocol, "Connection: Upgrade", "Sec-WebSocket-Accept: " + serverKey, "Date: Thu, 03 Mar 2016 22:46:15 GMT", "Sec-WebSocket-Protocol: " + webSocketProtocol, "\n"); assertTrue(webSocketUpgrade.validateUpgradeReply(responseStr.getBytes())); } catch (NoSuchAlgorithmException e) { assertTrue(false); } } @Test public void testValidateUpgradeReplyInvalidKey() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; WebSocketUpgrade webSocketUpgrade = new WebSocketUpgrade(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, null); String upgradeRequest = webSocketUpgrade.createUpgradeRequest(); String keyBase64 = upgradeRequest .substring(upgradeRequest.lastIndexOf("Sec-WebSocket-Key: ") + 19, upgradeRequest.lastIndexOf("Sec-WebSocket-Key: ") + 43); // Generate new key webSocketUpgrade.createUpgradeRequest(); try { MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); String serverKey = Base64.encodeBase64StringLocal(messageDigest.digest((keyBase64 + RFC_GUID).getBytes())).trim(); String responseStr = String.join("\n", "HTTP/1.1 101 Switching Protocols", "Upgrade: websocket", "Server: XXXYYYZZZ", "Sec-WebSocket-Protocol: " + webSocketProtocol, "Connection: Upgrade", "Sec-WebSocket-Accept: " + serverKey, "Date: Thu, 03 Mar 2016 22:46:15 GMT"); assertFalse(webSocketUpgrade.validateUpgradeReply(responseStr.getBytes())); } catch (NoSuchAlgorithmException e) { assertTrue(false); } } @Test public void testValidateUpgradeReplyPlainKey() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; WebSocketUpgrade webSocketUpgrade = new WebSocketUpgrade(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, null); String serverKey = "ABCDEFGHIJKLM123"; String responseStr = String.join("\n", "HTTP/1.1 101 Switching Protocols", "Upgrade: websocket", "Server: XXXYYYZZZ", "Sec-WebSocket-Protocol: " + webSocketProtocol, "Connection: Upgrade", "Sec-WebSocket-Accept: " + serverKey, "Date: Thu, 03 Mar 2016 22:46:15 GMT"); assertFalse(webSocketUpgrade.validateUpgradeReply(responseStr.getBytes())); } @Test public void testValidateUpgradeReplyMissingStatusLine() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; WebSocketUpgrade webSocketUpgrade = new WebSocketUpgrade(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, null); String upgradeRequest = webSocketUpgrade.createUpgradeRequest(); String keyBase64 = upgradeRequest .substring(upgradeRequest.lastIndexOf("Sec-WebSocket-Key: ") + 19, upgradeRequest.lastIndexOf("Sec-WebSocket-Key: ") + 43); try { MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); String serverKey = Base64.encodeBase64StringLocal(messageDigest.digest((keyBase64 + RFC_GUID).getBytes())).trim(); String responseStr = String.join("\n", "Upgrade: websocket", "Server: XXXYYYZZZ", "Sec-WebSocket-Protocol: " + webSocketProtocol, "Connection: Upgrade", "Sec-WebSocket-Accept: " + serverKey, "Date: Thu, 03 Mar 2016 22:46:15 GMT"); assertFalse(webSocketUpgrade.validateUpgradeReply(responseStr.getBytes())); } catch (NoSuchAlgorithmException e) { assertTrue(false); } } @Test public void testValidateUpgradeReplyMissingUpgradeHeader() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; WebSocketUpgrade webSocketUpgrade = new WebSocketUpgrade(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, null); String upgradeRequest = webSocketUpgrade.createUpgradeRequest(); String keyBase64 = upgradeRequest .substring(upgradeRequest.lastIndexOf("Sec-WebSocket-Key: ") + 19, upgradeRequest.lastIndexOf("Sec-WebSocket-Key: ") + 43); try { MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); String serverKey = Base64.encodeBase64StringLocal(messageDigest.digest((keyBase64 + RFC_GUID).getBytes())).trim(); String responseStr = String.join("\n", "HTTP/1.1 101 Switching Protocols", "Server: XXXYYYZZZ", "Sec-WebSocket-Protocol: " + webSocketProtocol, "Connection: Upgrade", "Sec-WebSocket-Accept: " + serverKey, "Date: Thu, 03 Mar 2016 22:46:15 GMT"); assertFalse(webSocketUpgrade.validateUpgradeReply(responseStr.getBytes())); } catch (NoSuchAlgorithmException e) { assertTrue(false); } } @Test public void testValidateUpgradeReplyMissingProtocolHeader() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; WebSocketUpgrade webSocketUpgrade = new WebSocketUpgrade(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, null); String upgradeRequest = webSocketUpgrade.createUpgradeRequest(); String keyBase64 = upgradeRequest .substring(upgradeRequest.lastIndexOf("Sec-WebSocket-Key: ") + 19, upgradeRequest.lastIndexOf("Sec-WebSocket-Key: ") + 43); try { MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); String serverKey = Base64.encodeBase64StringLocal(messageDigest.digest((keyBase64 + RFC_GUID).getBytes())).trim(); String responseStr = String.join("\n", "HTTP/1.1 101 Switching Protocols", "Upgrade: websocket", "Server: XXXYYYZZZ", "Connection: Upgrade", "Sec-WebSocket-Accept: " + serverKey, "Date: Thu, 03 Mar 2016 22:46:15 GMT"); assertFalse(webSocketUpgrade.validateUpgradeReply(responseStr.getBytes())); } catch (NoSuchAlgorithmException e) { assertTrue(false); } } @Test public void testValidateUpgradeReplyMissingConnectionHeader() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; WebSocketUpgrade webSocketUpgrade = new WebSocketUpgrade(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, null); String upgradeRequest = webSocketUpgrade.createUpgradeRequest(); String keyBase64 = upgradeRequest .substring(upgradeRequest.lastIndexOf("Sec-WebSocket-Key: ") + 19, upgradeRequest.lastIndexOf("Sec-WebSocket-Key: ") + 43); try { MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); String serverKey = Base64.encodeBase64StringLocal(messageDigest.digest((keyBase64 + RFC_GUID).getBytes())).trim(); String responseStr = String.join("\n", "HTTP/1.1 101 Switching Protocols", "Upgrade: websocket", "Server: XXXYYYZZZ", "Sec-WebSocket-Protocol: " + webSocketProtocol, "Sec-WebSocket-Accept: " + serverKey, "Date: Thu, 03 Mar 2016 22:46:15 GMT"); assertFalse(webSocketUpgrade.validateUpgradeReply(responseStr.getBytes())); } catch (NoSuchAlgorithmException e) { assertTrue(false); } } @Test public void testValidateUpgradeReplyMissingAcceptHeader() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; WebSocketUpgrade webSocketUpgrade = new WebSocketUpgrade(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, null); String responseStr = String.join("\n", "HTTP/1.1 101 Switching Protocols", "Upgrade: websocket", "Server: XXXYYYZZZ", "Sec-WebSocket-Protocol: " + webSocketProtocol, "Connection: Upgrade", "Date: Thu, 03 Mar 2016 22:46:15 GMT"); assertFalse(webSocketUpgrade.validateUpgradeReply(responseStr.getBytes())); } @Test public void testValidateUpgradeReplyEmptyResponse() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; Map additionalHeaders = new HashMap(); additionalHeaders.put("header1", "content1"); additionalHeaders.put("header2", "content2"); additionalHeaders.put("header3", "content3"); WebSocketUpgrade webSocketUpgrade = new WebSocketUpgrade(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, additionalHeaders); String responseStr = ""; assertFalse(webSocketUpgrade.validateUpgradeReply(responseStr.getBytes())); } @Test public void testToString() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; Map additionalHeaders = new HashMap(); additionalHeaders.put("header1", "content1"); additionalHeaders.put("header2", "content2"); additionalHeaders.put("header3", "content3"); WebSocketUpgrade webSocketUpgrade = new WebSocketUpgrade(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, additionalHeaders); webSocketUpgrade.createUpgradeRequest(); String actual = webSocketUpgrade.toString(); String expexted1 = "WebSocketUpgrade [host=" + hostName + ", path=/" + webSocketPath + ", port=" + webSocketPort + ", protocol=" + webSocketProtocol + ", webSocketKey="; String expected2 = ", additionalHeaders=header3:content3, header2:content2, header1:content1]"; assertTrue(actual.startsWith(expexted1)); actual = actual.substring(expexted1.length() + 24); assertTrue(actual.equals(expected2)); } @Test public void testToStringNoAdditionalHeaders() { String hostName = "host_XXX"; String webSocketPath = "path1/path2"; int webSocketPort = 1234567890; String webSocketProtocol = "subprotocol_name"; WebSocketUpgrade webSocketUpgrade = new WebSocketUpgrade(hostName, webSocketPath, "", webSocketPort, webSocketProtocol, null); String expected = String.join("", "WebSocketUpgrade [host=" + hostName, ", path=/" + webSocketPath, ", port=" + webSocketPort, ", protocol=" + webSocketProtocol, ", webSocketKey=]"); String actual = webSocketUpgrade.toString(); assertEquals("Unexpected value for toString()", expected, actual); } }