diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..717863a0e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. . +2. .. +3. ... +4. .... + +**Example application to reproduce the issue** +A link to a github project/gist to reproduce the issue. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Debug log** +The the debug log (set the log level to `TRACE`) to help explain your problem. + +**Environment(please complete the following information):** + - Version used: + - Java version: + - Operating System and version: + - Endpoint Name and version: + - Link to your project: + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..1fe59490f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or links about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 000000000..887c4f032 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,14 @@ +--- +name: Question +about: Question on how to use or achieve something + +--- + +**Describe what you would like to know or do** +A clear and concise description of what the problem is. + +**Describe the solution you'd considered** +A clear and concise description of any solutions you've considered. + +**Additional context** +Add any other context or links about the question here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..8990a2a23 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,33 @@ + + +## Description + + +## Related Issue + + + + + +## Motivation and Context + + +## How Has This Been Tested? + + + + +## Types of changes + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to change) + +## Checklist: + + +- [ ] My code follows the code style of this project. +- [ ] My change requires a change to the documentation. +- [ ] I have updated the documentation accordingly. +- [ ] I have added tests to cover my changes. +- [ ] All new and existing tests passed. diff --git a/.github/workflows/checkstyle.yml b/.github/workflows/checkstyle.yml new file mode 100644 index 000000000..197b3f3aa --- /dev/null +++ b/.github/workflows/checkstyle.yml @@ -0,0 +1,21 @@ +# This workflow will build a Java project with Maven +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven + +name: Java Code Style Check with Maven + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Code Style Check + run: mvn -B checkstyle:check --file pom.xml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..145292c6c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: Continuous Integration + +on: [push, pull_request] + +jobs: + Build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Maven Version + run: mvn --version + - name: Build + run: mvn -DskipTests package --file pom.xml + + Test: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Maven Version + run: mvn --version + - name: Test + run: mvn test --file pom.xml diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml new file mode 100644 index 000000000..538a5665b --- /dev/null +++ b/.github/workflows/sonar.yml @@ -0,0 +1,35 @@ +name: SonarQube +on: + push: + branches: + - master +jobs: + build: + name: SonarQube + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Cache SonarCloud packages + uses: actions/cache@v1 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: mvn -B org.jacoco:jacoco-maven-plugin:prepare-agent install org.jacoco:jacoco-maven-plugin:report verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dmaven.test.failure.ignore=true diff --git a/.gitignore b/.gitignore index d7e41aae5..845b9273d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,8 @@ target .classpath bin -/doc \ No newline at end of file +/doc +*.xml +.idea/.name +lib/junit-4.7.jar +*.jar \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 067c53e42..000000000 --- a/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: java -jdk: - - oraclejdk7 - -script: "./gradlew test" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..6dfbb71ca --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,452 @@ +# Change log + +############################################################################### +## Version Release 1.6.0 (2024/12/15) + +#### Breaking Changes + +* [Issue 1434](https://github.com/TooTallNate/Java-WebSocket/issues/1434) - Drop Java 1.7 support ([PR 1435](https://github.com/TooTallNate/Java-WebSocket/pull/1435)) +* [PR 1435](https://github.com/TooTallNate/Java-WebSocket/pull/1435) - Drop support for Java 1.7 + +#### Bugs Fixed + +* [Issue 1437](https://github.com/TooTallNate/Java-WebSocket/issues/1437) - Question: How can the compression threshold be set for the PerMessageDeflateExtension in a Deflate Client? ([PR 1439](https://github.com/TooTallNate/Java-WebSocket/pull/1439)) +* [Issue 1400](https://github.com/TooTallNate/Java-WebSocket/issues/1400) - PerMessageDeflateExtension#setDeflater()/#setInflater() is overwritten in case of no_context_takeover ([PR 1439](https://github.com/TooTallNate/Java-WebSocket/pull/1439)) +* [PR 1439](https://github.com/TooTallNate/Java-WebSocket/pull/1439) - Clone PerMessageDeflateExtension values correctly + +#### New Features + +* [Issue 1440](https://github.com/TooTallNate/Java-WebSocket/issues/1440) - Support for inherited sockets ([PR 1442](https://github.com/TooTallNate/Java-WebSocket/pull/1442)) +* [PR 1442](https://github.com/TooTallNate/Java-WebSocket/pull/1442) - Socket activation + +In this release 4 issues and 3 pull requests were closed. + +############################################################################### +## Version Release 1.5.7 (2024/07/08) + +#### Breaking Changes + +* [PR 1399](https://github.com/TooTallNate/Java-WebSocket/pull/1399) - Have connectBlocking clean up after a timeout + +#### Bugs Fixed + +* [PR 1419](https://github.com/TooTallNate/Java-WebSocket/pull/1419) - Fix issue #1418: WebSocketServer sometimes misses GET request after SSL handshake + +#### New Features + +* [PR 1407](https://github.com/TooTallNate/Java-WebSocket/pull/1407) - Allow setting custom TCP receive buffer size +* [PR 1399](https://github.com/TooTallNate/Java-WebSocket/pull/1399) - Have connectBlocking clean up after a timeout + +In this release 0 issues and 4 pull requests were closed. + +############################################################################### +## Version Release 1.5.6 (2024/02/06) + +#### Bugs Fixed + +* [Issue 1382](https://github.com/TooTallNate/Java-WebSocket/issues/1382) - WebSocketClient.upgradeSocketToSSL is enforcing TLS 1.2 ([PR 1387](https://github.com/TooTallNate/Java-WebSocket/pull/1387)) +* [PR 1387](https://github.com/TooTallNate/Java-WebSocket/pull/1387) - Retrieve default SSL socket factory + +#### New Features + +* [Issue 1390](https://github.com/TooTallNate/Java-WebSocket/issues/1390) - Thread created by NamedThreadFactory should be a daemon ([PR 1391](https://github.com/TooTallNate/Java-WebSocket/pull/1391)) +* [PR 1391](https://github.com/TooTallNate/Java-WebSocket/pull/1391) - Provide way to start the client/server as daemons + +In this release 2 issues and 2 pull requests were closed. + +############################################################################### + +## Version Release 1.5.5 (2023/12/18) + +#### Bugs Fixed + +* [Issue 1365](https://github.com/TooTallNate/Java-WebSocket/issues/1365) - Hang on reconnectBlocking +* [Issue 1364](https://github.com/TooTallNate/Java-WebSocket/issues/1364) - NPE during reconnect ([PR 1367](https://github.com/TooTallNate/Java-WebSocket/pull/1367)) +* [PR 1367](https://github.com/TooTallNate/Java-WebSocket/pull/1367) - Fix multiple issues related to reconnect + +In this release 2 issues and 1 pull request were closed. + +############################################################################### + +## Version Release 1.5.4 (2023/07/20) + +#### New Features + +* [Issue 1308](https://github.com/TooTallNate/Java-WebSocket/issues/1308) - Add support for Java modules ([PR 1309](https://github.com/TooTallNate/Java-WebSocket/pull/1309)) +* [PR 1309](https://github.com/TooTallNate/Java-WebSocket/pull/1309) - Add support for Java modules + +#### Refactoring + +* [PR 1259](https://github.com/TooTallNate/Java-WebSocket/pull/1259) - Replace usages of deprecated constructor Integer(String) with Integer.parseInt + +In this release 1 issue and 2 pull requests were closed. + +############################################################################### + +## Version Release 1.5.3 (2022/04/09) + +#### Bugs Fixed + +* [Issue 1230](https://github.com/TooTallNate/Java-WebSocket/issues/1230) - CONTINUOUS should be decoded depending on the first frame ([PR 1232 ](https://github.com/TooTallNate/Java-WebSocket/pull/1232) by [@marci4](https://github.com/marci4)) +* [Issue 1203](https://github.com/TooTallNate/Java-WebSocket/issues/1203) - Lost connection detection not working on delayed connect-Call ([PR 1204 ](https://github.com/TooTallNate/Java-WebSocket/pull/1204) by [@marci4](https://github.com/marci4)) +* [Issue 1164](https://github.com/TooTallNate/Java-WebSocket/issues/1164) - [Android & Node.js Server] Problem using PerMessageDeflateExtension with custom ping/pong messages ? ([PR 1165 ](https://github.com/TooTallNate/Java-WebSocket/pull/1165) by [@marci4](https://github.com/marci4)) +* [Issue 1160](https://github.com/TooTallNate/Java-WebSocket/issues/1160) - `WebSocketWorker` does not handle `Throwable` ([PR 1223 ](https://github.com/TooTallNate/Java-WebSocket/pull/1223) by [@Serpion-ua](https://github.com/Serpion-ua)) +* [Issue 1142](https://github.com/TooTallNate/Java-WebSocket/issues/1142) - Verifying server certificate ([PR 1143 ](https://github.com/TooTallNate/Java-WebSocket/pull/1143) by [@marci4](https://github.com/marci4)) +* [PR 1227](https://github.com/TooTallNate/Java-WebSocket/pull/1227) - Correct web socket closing, by [@Serpion-ua](https://github.com/Serpion-ua) +* [PR 1223](https://github.com/TooTallNate/Java-WebSocket/pull/1223) - Issue-1160 Added java.lang.Error handling in WebSocketImpl and WebSocketServer, by [@Serpion-ua](https://github.com/Serpion-ua) +* [PR 1212](https://github.com/TooTallNate/Java-WebSocket/pull/1212) - high cpu when channel close exception fix, by [@Adeptius](https://github.com/Adeptius) + +#### New Features + +* [PR 1185](https://github.com/TooTallNate/Java-WebSocket/pull/1185) - Added support unresolved socket addresses, by [@vtsykun](https://github.com/vtsykun) + +In this release 5 issues and 8 pull requests were closed. + +############################################################################### + +## Version Release 1.5.2 (2021/04/05) + +#### Bugs Fixed + +* [Issue 1132](https://github.com/TooTallNate/Java-WebSocket/issues/1132) - Draft_6455 flagged by Veracode CWE-331 replace Random with SecureRandom ([PR 1133 ](https://github.com/TooTallNate/Java-WebSocket/pull/1133) by [@marci4](https://github.com/marci4)) +* [Issue 1053](https://github.com/TooTallNate/Java-WebSocket/issues/1053) - It's an invalid check null with SEC_WEB_SOCKET_KEY . ([PR 1054 ](https://github.com/TooTallNate/Java-WebSocket/pull/1054) by [@dota17](https://github.com/dota17)) +* [Issue 1026](https://github.com/TooTallNate/Java-WebSocket/issues/1026) - Force client to use the valid schema ([PR 1025 ](https://github.com/TooTallNate/Java-WebSocket/pull/1025) by [@yindex](https://github.com/yindex)) +* [PR 1070](https://github.com/TooTallNate/Java-WebSocket/pull/1070) - Prioritise using provided socket factory when creating socket with proxy, by [@marci4](https://github.com/marci4) +* [PR 1028](https://github.com/TooTallNate/Java-WebSocket/pull/1028) - Fixed typo in WebSocketClient.reset's error message, by [@alphaho](https://github.com/alphaho) +* [PR 1018](https://github.com/TooTallNate/Java-WebSocket/pull/1018) - Added missing return character, by [@pawankgupta-se](https://github.com/pawankgupta-se) + +#### New Features + +* [Issue 1008](https://github.com/TooTallNate/Java-WebSocket/issues/1008) - Improve Sec-WebSocket-Protocol usability ([PR 1034 ](https://github.com/TooTallNate/Java-WebSocket/pull/1034) by [@marci4](https://github.com/marci4)) + +#### Refactoring + +* [Issue 1050](https://github.com/TooTallNate/Java-WebSocket/issues/1050) - What about adding the CodeFormatterProfile.xml with the code format ? ([PR 1060 ](https://github.com/TooTallNate/Java-WebSocket/pull/1060) by [@dota17](https://github.com/dota17)) +* [PR 1072](https://github.com/TooTallNate/Java-WebSocket/pull/1072) - Improve code quality, by [@marci4](https://github.com/marci4) +* [PR 1060](https://github.com/TooTallNate/Java-WebSocket/pull/1060) - Using Google Java Code Style To Reformat Code, by [@dota17](https://github.com/dota17) + +In this release 5 issues and 9 pull requests were closed. + +############################################################################### + +## Version Release 1.5.1 (2020/05/10) + +#### Bugs Fixed + +* [Issue 1011](https://github.com/TooTallNate/Java-WebSocket/issues/1011) - Crash on Android due to missing method `setEndpointIdentificationAlgorithm` on 1.5.0. ([PR 1014](https://github.com/TooTallNate/Java-WebSocket/pull/1014)) + +In this release 1 issue and 1 pull request were closed. + +############################################################################### + +## Version Release 1.5.0 (2020/05/06) + +#### Breaking Changes + +This release requires API Level 1.7. + +#### Security + +This release contains a security fix for [CVE-2020-11050](https://nvd.nist.gov/vuln/detail/CVE-2020-11050). + +Take a look at the advisory [here](https://github.com/TooTallNate/Java-WebSocket/security/advisories/GHSA-gw55-jm4h-x339) for more information. + +#### New Features + +* [Issue 574](https://github.com/TooTallNate/Java-WebSocket/issues/574) - Implementation of per message deflate extension ([PR 866](https://github.com/TooTallNate/Java-WebSocket/pull/866)) +* [PR 866](https://github.com/TooTallNate/Java-WebSocket/pull/866) - Add PerMessageDeflate Extension support, see #574 +* [Issue 997](https://github.com/TooTallNate/Java-WebSocket/issues/997) - Access to SSLParameters used by the WebSocketClient ([PR 1000](https://github.com/TooTallNate/Java-WebSocket/pull/1000)) +* [Issue 574](https://github.com/TooTallNate/Java-WebSocket/issues/574) - Implementation of per message deflate extension ([PR 866](https://github.com/TooTallNate/Java-WebSocket/pull/866)) +* [PR 1001](https://github.com/TooTallNate/Java-WebSocket/pull/1001) - Allow user to specify max number of pending connections to a server +* [PR 1000](https://github.com/TooTallNate/Java-WebSocket/pull/1000) - SSLParameters for WebSocketClient +* [PR 866](https://github.com/TooTallNate/Java-WebSocket/pull/866) - Add PerMessageDeflate Extension support, see #574 + +In this release 3 issues and 4 pull requests were closed. + +############################################################################### + +## Version Release 1.4.1 (2020/03/12) + +#### Bugs Fixed + +* [Issue 940](https://github.com/TooTallNate/Java-WebSocket/issues/940) - WebSocket handshake fails over WSS, if client uses TLS False Start ([PR 943](https://github.com/TooTallNate/Java-WebSocket/pull/943)) +* [Issue 921](https://github.com/TooTallNate/Java-WebSocket/issues/921) - ConcurrentModificationException when looping connections +* [Issue 905](https://github.com/TooTallNate/Java-WebSocket/issues/905) - IOException wrapped in InternalError not handled properly ([PR 901](https://github.com/TooTallNate/Java-WebSocket/pull/901)) +* [Issue 900](https://github.com/TooTallNate/Java-WebSocket/issues/900) - OnClose is not called when client disconnect ([PR 914](https://github.com/TooTallNate/Java-WebSocket/pull/914)) +* [Issue 869](https://github.com/TooTallNate/Java-WebSocket/issues/869) - Lost connection detection is sensitive to changes in system time ([PR 878](https://github.com/TooTallNate/Java-WebSocket/pull/878)) +* [Issue 665](https://github.com/TooTallNate/Java-WebSocket/issues/665) - Data read with end of SSL handshake is discarded ([PR 943](https://github.com/TooTallNate/Java-WebSocket/pull/943)) +* [PR 943](https://github.com/TooTallNate/Java-WebSocket/pull/943) - Merge pull request #943 from da-als/master +* [PR 922](https://github.com/TooTallNate/Java-WebSocket/pull/922) - Fix ConcurrentModificationException when iterating through connection +* [PR 914](https://github.com/TooTallNate/Java-WebSocket/pull/914) - Merge pull request #914 from marci4/Issue900 +* [PR 902](https://github.com/TooTallNate/Java-WebSocket/pull/902) - ConcurrentModificationException when using broadcast +* [PR 901](https://github.com/TooTallNate/Java-WebSocket/pull/901) - fix when proxy tunneling failed (IOException is hidden) JDK-8173 +* [PR 878](https://github.com/TooTallNate/Java-WebSocket/pull/878) - Replace TimerTask with ScheduledExecutorService + +#### New Features + +* [Issue 969](https://github.com/TooTallNate/Java-WebSocket/issues/969) - Loggers should be declared non-static ([PR 970](https://github.com/TooTallNate/Java-WebSocket/pull/970)) +* [Issue 962](https://github.com/TooTallNate/Java-WebSocket/issues/962) - Improvements in socket connect to server ([PR 964](https://github.com/TooTallNate/Java-WebSocket/pull/964)) +* [Issue 941](https://github.com/TooTallNate/Java-WebSocket/issues/941) - How to send customized ping message on connectionLostTimeout interval ([PR 944](https://github.com/TooTallNate/Java-WebSocket/pull/944)) +* [Issue 890](https://github.com/TooTallNate/Java-WebSocket/issues/890) - Would like to get SSLSession from WebSocket on server to examine client certificates ([PR 893](https://github.com/TooTallNate/Java-WebSocket/pull/893)) +* [Issue 865](https://github.com/TooTallNate/Java-WebSocket/issues/865) - Append new headers to the client when reconnecting +* [Issue 859](https://github.com/TooTallNate/Java-WebSocket/issues/859) - Hot wo specify a custom DNS Resolver ([PR 906](https://github.com/TooTallNate/Java-WebSocket/pull/906)) +* [PR 971](https://github.com/TooTallNate/Java-WebSocket/pull/971) - Enabled OSGi metadata in MANIFST-MF for created JAR +* [PR 964](https://github.com/TooTallNate/Java-WebSocket/pull/964) - Use socket isConnected() method rather than isBound() +* [PR 944](https://github.com/TooTallNate/Java-WebSocket/pull/944) - Add ability to customize ping messages with custom data +* [PR 906](https://github.com/TooTallNate/Java-WebSocket/pull/906) - Implemented a custom DNS resolver, see #859 +* [PR 893](https://github.com/TooTallNate/Java-WebSocket/pull/893) - Provide a way to access the SSLSession of a websocket instance +* [PR 868](https://github.com/TooTallNate/Java-WebSocket/pull/868) - Add a way to put additional headers to handshake for connecting/reconnecting, see #865 + +#### Refactoring + +* [Issue 907](https://github.com/TooTallNate/Java-WebSocket/issues/907) - build fails with Gradle 5+ ([PR 908](https://github.com/TooTallNate/Java-WebSocket/pull/908)) +* [Issue 869](https://github.com/TooTallNate/Java-WebSocket/issues/869) - Lost connection detection is sensitive to changes in system time ([PR 878](https://github.com/TooTallNate/Java-WebSocket/pull/878)) +* [PR 970](https://github.com/TooTallNate/Java-WebSocket/pull/970) - Made loggers non-static to support deployment in containers +* [PR 931](https://github.com/TooTallNate/Java-WebSocket/pull/931) - Create new github actions +* [PR 908](https://github.com/TooTallNate/Java-WebSocket/pull/908) - Remove outdated 'wrapper' task from build.gradle (#907) +* [PR 878](https://github.com/TooTallNate/Java-WebSocket/pull/878) - Replace TimerTask with ScheduledExecutorService +* [PR 874](https://github.com/TooTallNate/Java-WebSocket/pull/874) - Update dependencies + +In this release 14 issues and 17 pull requests were closed. + +## Version Release 1.4.0 (2019/02/19) + +#### Breaking Changes + +* [Issue 753](https://github.com/TooTallNate/Java-WebSocket/issues/753) - Breaking changes in 1.4 +* [Issue 670](https://github.com/TooTallNate/Java-WebSocket/issues/670) - Use a logging framework such as as SLF4J instead of System.out.println ([PR 754](https://github.com/TooTallNate/Java-WebSocket/pull/754)) + +#### Bugs Fixed + +* [Issue 855](https://github.com/TooTallNate/Java-WebSocket/issues/855) - WebSocketServer cannot be started without .start() ([PR 856](https://github.com/TooTallNate/Java-WebSocket/pull/856)) +* [Issue 847](https://github.com/TooTallNate/Java-WebSocket/issues/847) - java.nio.BufferUnderflowException ([PR 849](https://github.com/TooTallNate/Java-WebSocket/pull/849)) +* [Issue 834](https://github.com/TooTallNate/Java-WebSocket/issues/834) - Workers should not be started before the server +* [Issue 827](https://github.com/TooTallNate/Java-WebSocket/issues/827) - WebSocketClient close() +* [Issue 784](https://github.com/TooTallNate/Java-WebSocket/issues/784) - Building with gradle fails +* [Issue 773](https://github.com/TooTallNate/Java-WebSocket/issues/773) - Memory leak in WebSocketImpl.outQueue ([PR 781](https://github.com/TooTallNate/Java-WebSocket/pull/781)) +* [PR 856](https://github.com/TooTallNate/Java-WebSocket/pull/856) - Move the startup of the WebSocketWorker inside of run() +* [PR 850](https://github.com/TooTallNate/Java-WebSocket/pull/850) - Fix issue #834 by starting WebSocketWorker of the WebSocketServer in the start function +* [PR 849](https://github.com/TooTallNate/Java-WebSocket/pull/849) - Fix issue #847 +* [PR 846](https://github.com/TooTallNate/Java-WebSocket/pull/846) - Pass on exit code in WebSocketClient close function - fixes bug #827 +* [PR 824](https://github.com/TooTallNate/Java-WebSocket/pull/824) - Synchronize AbstractWebSocket +* [PR 785](https://github.com/TooTallNate/Java-WebSocket/pull/785) - Update build.gradle +* [PR 781](https://github.com/TooTallNate/Java-WebSocket/pull/781) - Null the reference of the WebSocketImpl +* [PR 771](https://github.com/TooTallNate/Java-WebSocket/pull/771) - Test for 765 +* [PR 770](https://github.com/TooTallNate/Java-WebSocket/pull/770) - Use a SocketFactory to support reconnecting +* [PR 769](https://github.com/TooTallNate/Java-WebSocket/pull/769) - Close WebSocketFactory when updated +* [PR 757](https://github.com/TooTallNate/Java-WebSocket/pull/757) - -keyalg RSA is needed or you'll get SSLHandshakeException: no cipher … + +#### New Features + +* [Issue 845](https://github.com/TooTallNate/Java-WebSocket/issues/845) - Generate changelog.md ([PR 851](https://github.com/TooTallNate/Java-WebSocket/pull/851)) +* [Issue 838](https://github.com/TooTallNate/Java-WebSocket/issues/838) - Allow for two-way ssl(SSLEngine.setNeedClientAuth()) +* [Issue 670](https://github.com/TooTallNate/Java-WebSocket/issues/670) - Use a logging framework such as as SLF4J instead of System.out.println ([PR 754](https://github.com/TooTallNate/Java-WebSocket/pull/754)) +* [Issue 598](https://github.com/TooTallNate/Java-WebSocket/issues/598) - Memory Management ([PR 761](https://github.com/TooTallNate/Java-WebSocket/pull/761)) +* [PR 839](https://github.com/TooTallNate/Java-WebSocket/pull/839) - SSLEngineWebSocketServerFactory allows more customization +* [PR 761](https://github.com/TooTallNate/Java-WebSocket/pull/761) - Implements Memory Management + +#### Refactoring + +* [Issue 845](https://github.com/TooTallNate/Java-WebSocket/issues/845) - Generate changelog.md ([PR 851](https://github.com/TooTallNate/Java-WebSocket/pull/851)) +* [Issue 819](https://github.com/TooTallNate/Java-WebSocket/issues/819) - Ant build removed on master ? +* [Issue 784](https://github.com/TooTallNate/Java-WebSocket/issues/784) - Building with gradle fails +* [Issue 753](https://github.com/TooTallNate/Java-WebSocket/issues/753) - Breaking changes in 1.4 +* [Issue 749](https://github.com/TooTallNate/Java-WebSocket/issues/749) - Improve code quality for 1.4.0 +* [PR 848](https://github.com/TooTallNate/Java-WebSocket/pull/848) - Removed unused/unrelated imports (including deprecated CORBA) +* [PR 833](https://github.com/TooTallNate/Java-WebSocket/pull/833) - Fix some sonarqube errors +* [PR 824](https://github.com/TooTallNate/Java-WebSocket/pull/824) - Synchronize AbstractWebSocket +* [PR 821](https://github.com/TooTallNate/Java-WebSocket/pull/821) - Remove outdated build instructions from README +* [PR 805](https://github.com/TooTallNate/Java-WebSocket/pull/805) - More improvement +* [PR 789](https://github.com/TooTallNate/Java-WebSocket/pull/789) - WebSocketServer code quality +* [PR 785](https://github.com/TooTallNate/Java-WebSocket/pull/785) - Update build.gradle +* [PR 768](https://github.com/TooTallNate/Java-WebSocket/pull/768) - Fixed several issues related to the code quality +* [PR 754](https://github.com/TooTallNate/Java-WebSocket/pull/754) - Using SLF4J and refactored code + +In this release 16 issues and 22 pull requests were closed. + +## Version Release 1.3.9 (2018-08-05) + +#### Bugs Fixed + +* [Issue 694](https://github.com/TooTallNate/Java-WebSocket/issues/694) - AssertionError at WebSocketImpl.isOpen +* [Issue 685](https://github.com/TooTallNate/Java-WebSocket/issues/685) - Exclude default port from wss host ([PR 683](https://github.com/TooTallNate/Java-WebSocket/pull/683)) +* [PR 746](https://github.com/TooTallNate/Java-WebSocket/pull/746) - Fixed typo in Draft_6455 +* [PR 722](https://github.com/TooTallNate/Java-WebSocket/pull/722) - Catch exceptions in AbstractWebSocket +* [PR 708](https://github.com/TooTallNate/Java-WebSocket/pull/708) - Enable and Disable ping/pong + +#### New Features + +* [Issue 711](https://github.com/TooTallNate/Java-WebSocket/issues/711) - broadcasting a ByteBuffer +* [Issue 699](https://github.com/TooTallNate/Java-WebSocket/issues/699) - Enable and Disable ping/pong +* [PR 738](https://github.com/TooTallNate/Java-WebSocket/pull/738) - Adjust readme +* [PR 737](https://github.com/TooTallNate/Java-WebSocket/pull/737) - Prepare for automatic snapshot deploy +* [PR 724](https://github.com/TooTallNate/Java-WebSocket/pull/724) - added a timeout option for connectBlocking +* [PR 712](https://github.com/TooTallNate/Java-WebSocket/pull/712) - Added a broadcast method for ByteBuffers +* [PR 708](https://github.com/TooTallNate/Java-WebSocket/pull/708) - Enable and Disable ping/pong + +#### Refactoring + +* [PR 739](https://github.com/TooTallNate/Java-WebSocket/pull/739) - Exception when using reconnect in websocket thread +* [PR 736](https://github.com/TooTallNate/Java-WebSocket/pull/736) - Change example section +* [PR 733](https://github.com/TooTallNate/Java-WebSocket/pull/733) - Remove static from synchronize object +* [PR 702](https://github.com/TooTallNate/Java-WebSocket/pull/702) - Removed assertion from WebSocketImpl.isOpen (see #694) +* [PR 682](https://github.com/TooTallNate/Java-WebSocket/pull/682) - Deprecate Connecting and additional tests + +In this release 4 issues and 13 pull requests were closed. + +## Version Release 1.3.8 (2018-03-05) + +#### Bugs Fixed + +* [Issue 668](https://github.com/TooTallNate/Java-WebSocket/issues/668) - When a server fails to start it does not cleanup its WebSocketWorker threads +* [PR 662](https://github.com/TooTallNate/Java-WebSocket/pull/662) - NPE on already bound port + +#### New Features + +* [Issue 256](https://github.com/TooTallNate/Java-WebSocket/issues/256) - how to reconnect websocket ([PR 654](https://github.com/TooTallNate/Java-WebSocket/pull/654)) +* [PR 654](https://github.com/TooTallNate/Java-WebSocket/pull/654) - WebSocketClient supports reconnecting +* [PR 651](https://github.com/TooTallNate/Java-WebSocket/pull/651) - Support for close code 1012-1014 + +#### Refactoring + +* [Issue 669](https://github.com/TooTallNate/Java-WebSocket/issues/669) - Include information in the onClose call for the connection lost detection ([PR 671](https://github.com/TooTallNate/Java-WebSocket/pull/671)) +* [Issue 666](https://github.com/TooTallNate/Java-WebSocket/issues/666) - Give the main WebSocketClient thread and AbstractWebSocket Timer a name ([PR 667](https://github.com/TooTallNate/Java-WebSocket/pull/667)) +* [PR 675](https://github.com/TooTallNate/Java-WebSocket/pull/675) - Change thread name +* [PR 671](https://github.com/TooTallNate/Java-WebSocket/pull/671) - Include reason for dc due to lost connection detection +* [PR 667](https://github.com/TooTallNate/Java-WebSocket/pull/667) - Give all threads a custom name + +In this release 4 issues and 6 pull requests were closed. + +## Version Release 1.3.7 (2017-12-11) + +#### Bugs Fixed + +* [Issue 621](https://github.com/TooTallNate/Java-WebSocket/issues/621) - conn.close() in server's onOpen method causes null pointer exception ([PR 622](https://github.com/TooTallNate/Java-WebSocket/pull/622)) +* [Issue 620](https://github.com/TooTallNate/Java-WebSocket/issues/620) - Investigate cause for #580 ([PR 628](https://github.com/TooTallNate/Java-WebSocket/pull/628)) +* [Issue 609](https://github.com/TooTallNate/Java-WebSocket/issues/609) - A connection will be in readystate Open when onWebSocketClose is called ([PR 610](https://github.com/TooTallNate/Java-WebSocket/pull/610)) +* [Issue 606](https://github.com/TooTallNate/Java-WebSocket/issues/606) - WebsocketNotConnectedException in Timer-0 ping +* [PR 628](https://github.com/TooTallNate/Java-WebSocket/pull/628) - Graceful shutdown on stop() +* [PR 622](https://github.com/TooTallNate/Java-WebSocket/pull/622) - Fix for #621 +* [PR 610](https://github.com/TooTallNate/Java-WebSocket/pull/610) - Check if connection is open on sendPing & change readystate on closeConnection + +#### New Features + +* [Issue 608](https://github.com/TooTallNate/Java-WebSocket/issues/608) - Sec-WebSocket-Protocol header not supported ([PR 614](https://github.com/TooTallNate/Java-WebSocket/pull/614)) +* [PR 627](https://github.com/TooTallNate/Java-WebSocket/pull/627) - Added setAttachment and getAttachment to WebSocket interface +* [PR 614](https://github.com/TooTallNate/Java-WebSocket/pull/614) - Protocol + +#### Refactoring + +* [PR 635](https://github.com/TooTallNate/Java-WebSocket/pull/635) - Mark AbstractClientProxyChannel as deprecated +* [PR 614](https://github.com/TooTallNate/Java-WebSocket/pull/614) - Protocol +* [PR 610](https://github.com/TooTallNate/Java-WebSocket/pull/610) - Check if connection is open on sendPing & change readystate on closeConnection + +In this release 5 issues and 8 pull requests were closed. + +## Version Release 1.3.6 (2017-11-09) + +#### Bugs Fixed + +* [Issue 579](https://github.com/TooTallNate/Java-WebSocket/issues/579) - Exception with sending ping without server access +* [PR 603](https://github.com/TooTallNate/Java-WebSocket/pull/603) - Check for sending a close frame + +#### Refactoring + +* [Issue 577](https://github.com/TooTallNate/Java-WebSocket/issues/577) - Improve onClose behaviour on client side +* [PR 597](https://github.com/TooTallNate/Java-WebSocket/pull/597) - Code cleanups +* [PR 596](https://github.com/TooTallNate/Java-WebSocket/pull/596) - Improved OpeningHandshakeRejection test +* [PR 591](https://github.com/TooTallNate/Java-WebSocket/pull/591) - Adjusted examples +* [PR 589](https://github.com/TooTallNate/Java-WebSocket/pull/589) - Include whole invalid status line +* [PR 578](https://github.com/TooTallNate/Java-WebSocket/pull/578) - Refactoring and improved onClose behaviour + +In this release 2 issues and 6 pull requests were closed. + +## Version Release 1.3.5 (2017-10-13) + +#### Bugs Fixed + +* [Issue 564](https://github.com/TooTallNate/Java-WebSocket/issues/564) - Continuous binary getting swallowed? ([PR 570](https://github.com/TooTallNate/Java-WebSocket/pull/570)) +* [Issue 530](https://github.com/TooTallNate/Java-WebSocket/issues/530) - onWebsocketHandshakeReceivedAsServer throwing InvalidDataException has no effect +* [Issue 512](https://github.com/TooTallNate/Java-WebSocket/issues/512) - AssertionError in WebSocketServer.removeConnection +* [Issue 508](https://github.com/TooTallNate/Java-WebSocket/issues/508) - Ant fails due to missing `dist/` directory +* [Issue 504](https://github.com/TooTallNate/Java-WebSocket/issues/504) - Clean up connections after connection closed +* [Issue 390](https://github.com/TooTallNate/Java-WebSocket/issues/390) - Websocket server returning 401; can't handle on client side +* [PR 506](https://github.com/TooTallNate/Java-WebSocket/pull/506) - Connections dont always get cleaned up after lost connection + +#### New Features + +* [Issue 528](https://github.com/TooTallNate/Java-WebSocket/issues/528) - so_reuseaddr +* [Issue 463](https://github.com/TooTallNate/Java-WebSocket/issues/463) - Support for Compression Extensions for WebSocket +* [PR 529](https://github.com/TooTallNate/Java-WebSocket/pull/529) - Added setter for SO_REUSEADDR +* [PR 510](https://github.com/TooTallNate/Java-WebSocket/pull/510) - Add true WSS support to WebSocketClient + +#### Refactoring + +* [Issue 545](https://github.com/TooTallNate/Java-WebSocket/issues/545) - java.io.IOException: Broken pipe +* [Issue 539](https://github.com/TooTallNate/Java-WebSocket/issues/539) - Improve memory usage +* [Issue 516](https://github.com/TooTallNate/Java-WebSocket/issues/516) - Improve handling of IOExceptions causing eot() +* [PR 558](https://github.com/TooTallNate/Java-WebSocket/pull/558) - Code cleanups +* [PR 553](https://github.com/TooTallNate/Java-WebSocket/pull/553) - Removal of deprecated drafts +* [PR 510](https://github.com/TooTallNate/Java-WebSocket/pull/510) - Add true WSS support to WebSocketClient +* [PR 500](https://github.com/TooTallNate/Java-WebSocket/pull/500) - Making WebSocket.send() thread-safe + +In this release 11 issues and 7 pull requests were closed. + +## Version Release 1.3.4 (2017-06-02) + +#### Breaking Changes + +* [Issue 478](https://github.com/TooTallNate/Java-WebSocket/issues/478) - Draft_10, Draft_17, Draft_75 and Draft_76 are now deprecated + +#### Bugs Fixed + +* [Issue 484](https://github.com/TooTallNate/Java-WebSocket/issues/484) - Problems with WSS running on linux and Edge(or ie) browser +* [Issue 481](https://github.com/TooTallNate/Java-WebSocket/issues/481) - No Javadoc attached when using from Gradle +* [Issue 473](https://github.com/TooTallNate/Java-WebSocket/issues/473) - Improve lost connection detection +* [Issue 466](https://github.com/TooTallNate/Java-WebSocket/issues/466) - Instability on WSS Connections, only works when one client abandon connection +* [Issue 465](https://github.com/TooTallNate/Java-WebSocket/issues/465) - Bad rsv 4 on android +* [Issue 294](https://github.com/TooTallNate/Java-WebSocket/issues/294) - Issue in SSL implementation : protocole ws:// is always use in Draft_76.java +* [Issue 222](https://github.com/TooTallNate/Java-WebSocket/issues/222) - Worker threads do not close if bind() fails +* [Issue 120](https://github.com/TooTallNate/Java-WebSocket/issues/120) - Closing wss connections might not work as expected +* [PR 477](https://github.com/TooTallNate/Java-WebSocket/pull/477) - Fix for #222 +* [PR 472](https://github.com/TooTallNate/Java-WebSocket/pull/472) - Fix for #466 +* [PR 470](https://github.com/TooTallNate/Java-WebSocket/pull/470) - Fix #465 + +#### New Features + +* [PR 497](https://github.com/TooTallNate/Java-WebSocket/pull/497) - Added new AutobahnServerTest for SSL and fixed errors in closeframe +* [PR 493](https://github.com/TooTallNate/Java-WebSocket/pull/493) - Clear implementations for frames and SSLWebsocketServerFactory +* [PR 489](https://github.com/TooTallNate/Java-WebSocket/pull/489) - Possibility to override worker thread allocation logic in WebSocketSe… +* [PR 487](https://github.com/TooTallNate/Java-WebSocket/pull/487) - Example for LetsEncrypt +* [PR 483](https://github.com/TooTallNate/Java-WebSocket/pull/483) - Introduction of Draft_6455 +* [PR 480](https://github.com/TooTallNate/Java-WebSocket/pull/480) - Lostconnection + +#### Refactoring + +* [Issue 473](https://github.com/TooTallNate/Java-WebSocket/issues/473) - Improve lost connection detection +* [Issue 222](https://github.com/TooTallNate/Java-WebSocket/issues/222) - Worker threads do not close if bind() fails +* [PR 493](https://github.com/TooTallNate/Java-WebSocket/pull/493) - Clear implementations for frames and SSLWebsocketServerFactory +* [PR 488](https://github.com/TooTallNate/Java-WebSocket/pull/488) - New SSLSocketChannel +* [PR 486](https://github.com/TooTallNate/Java-WebSocket/pull/486) - ByteBuffer and JUnitTests +* [PR 483](https://github.com/TooTallNate/Java-WebSocket/pull/483) - Introduction of Draft_6455 +* [PR 480](https://github.com/TooTallNate/Java-WebSocket/pull/480) - Lostconnection +* [PR 469](https://github.com/TooTallNate/Java-WebSocket/pull/469) - Cleanups & JavaDocs + +In this release 11 issues and 15 pull requests were closed. + +## Version Release 1.3.3 (2017-04-26) + +#### Bugs Fixed + +* [Issue 458](https://github.com/TooTallNate/Java-WebSocket/issues/458) - 100% cpu when using SSL +* [Issue 362](https://github.com/TooTallNate/Java-WebSocket/issues/362) - race problem when starting server with port 0 +* [Issue 302](https://github.com/TooTallNate/Java-WebSocket/issues/302) - Client blocking connect and close methods return too soon + +#### New Features + +* [Issue 452](https://github.com/TooTallNate/Java-WebSocket/issues/452) - Unable to verify hostname after handshake +* [Issue 339](https://github.com/TooTallNate/Java-WebSocket/issues/339) - setTCPNoDelay inaccessible +* [Issue 271](https://github.com/TooTallNate/Java-WebSocket/issues/271) - There is no notification for websocket server success start +* [PR 462](https://github.com/TooTallNate/Java-WebSocket/pull/462) - Make TCP_NODELAY accessible + +In this release 6 issues and 1 pull request were closed. diff --git a/LICENSE b/LICENSE index 5a93449e0..bc4515cc6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ - Copyright (c) 2010-2012 Nathan Rajlich + Copyright (c) 2010-2020 Nathan Rajlich Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/README.markdown b/README.markdown index 88349fa48..75607d3a2 100644 --- a/README.markdown +++ b/README.markdown @@ -1,74 +1,65 @@ -[![Build Status](https://travis-ci.org/ck1125/Java-WebSocket.png?branch=master)](https://travis-ci.org/ck1125/Java-WebSocket) Java WebSockets =============== +[![Javadocs](https://www.javadoc.io/badge/org.java-websocket/Java-WebSocket.svg)](https://www.javadoc.io/doc/org.java-websocket/Java-WebSocket) +[![Maven Central](https://img.shields.io/maven-central/v/org.java-websocket/Java-WebSocket.svg)](https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket) This repository contains a barebones WebSocket server and client implementation written in 100% Java. The underlying classes are implemented `java.nio`, which allows for a non-blocking event-driven model (similar to the -[WebSocket API](http://dev.w3.org/html5/websockets/) for web browsers). +[WebSocket API](https://html.spec.whatwg.org/multipage/web-sockets.html) for web browsers). Implemented WebSocket protocol versions are: * [RFC 6455](http://tools.ietf.org/html/rfc6455) - * [Hybi 17](http://tools.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-17.txt) - * [Hybi 10](http://tools.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-10.txt) - * [Hixie 76](http://tools.ietf.org/id/draft-hixie-thewebsocketprotocol-76.txt) - * [Hixie 75](http://tools.ietf.org/id/draft-hixie-thewebsocketprotocol-75.txt) + * [RFC 7692](http://tools.ietf.org/html/rfc7692) -[Here](https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts) some more details about protocol versions/drafts. +[Here](https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts) some more details about protocol versions/drafts. +[PerMessageDeflateExample](https://github.com/TooTallNate/Java-WebSocket/wiki/PerMessageDeflateExample) enable the extension with reference to both a server and client example. -##Build -You can build using Ant or Maven but there is nothing against just putting the source path ```src/main/java ``` on your applications buildpath. +## Getting Started -###Ant +### Dependency management tools -``` bash -ant -``` - -will create the javadoc of this library at ```doc/``` and build the library itself: ```dest/java_websocket.jar``` - -The ant targets are: ```compile```, ```jar```, ```doc``` and ```clean``` +Below is a brief guide to using dependency management tools like maven or gradle. -###Maven - -To use maven just add this dependency to your pom.xml: +#### Maven +To use maven add this dependency to your pom.xml: ```xml - org.java-websocket - Java-WebSocket - 1.3.0 - + org.java-websocket + Java-WebSocket + 1.6.0 + ``` -Running the Examples -------------------- - -**Note:** If you're on Windows, then replace the `:` (colon) in the classpath -in the commands below with a `;` (semicolon). +#### Gradle +To use Gradle add the maven central repository to your repositories list: +```xml +mavenCentral() +``` +Then you can just add the latest version to your build. +```xml +compile "org.java-websocket:Java-WebSocket:1.6.0" +``` +Or this option if you use gradle 7.0 and above. +```xml +implementation 'org.java-websocket:Java-WebSocket:1.6.0' +``` -After you build the library you can start the chat server (a `WebSocketServer` subclass): +#### Logging -``` bash -java -cp build/examples:dist/java_websocket.jar ChatServer -``` +This library uses [SLF4J](https://www.slf4j.org/) for logging and does not ship with any default logging implementation. -Now that the server is started, you need to connect some clients. Run the -Java chat client (a `WebSocketClient` subclass): +Exceptions are using the log level `ERROR` and debug logging will be done with log level `TRACE`. -``` bash -java -cp build/examples:dist/java_websocket.jar ChatClient -``` +Feel free to use whichever logging framework you desire and use the corresponding [binding](https://mvnrepository.com/artifact/org.slf4j) in your dependency management. -The chat client is a simple Swing GUI application that allows you to send -messages to all other connected clients, and receive messages from others in a -text box. +If you want to get started, take a look at the SimpleLogger [example](https://github.com/TooTallNate/Java-WebSocket/wiki/SimpleLogger-example). -In the example folder is also a simple HTML file chat client `chat.html`, which can be opened by any browser. If the browser natively supports the WebSocket API, then it's -implementation will be used, otherwise it will fall back to a -[Flash-based WebSocket Implementation](http://github.com/gimite/web-socket-js). +### Standalone jar +If you do not use any dependency management tool, you can find the latest standalone jar [here](https://github.com/TooTallNate/Java-WebSocket/releases/latest). Writing your own WebSocket Server --------------------------------- @@ -79,16 +70,24 @@ server-side of the A WebSocket server by itself doesn't do anything except establish socket connections though HTTP. After that it's up to **your** subclass to add purpose. +An example for a WebSocketServer can be found in both the [wiki](https://github.com/TooTallNate/Java-WebSocket/wiki#server-example) and the [example](https://github.com/TooTallNate/Java-WebSocket/tree/master/src/main/example) folder. Writing your own WebSocket Client --------------------------------- The `org.java_websocket.client.WebSocketClient` abstract class can connect to valid WebSocket servers. The constructor expects a valid `ws://` URI to -connect to. Important events `onOpen`, `onClose`, `onMessage` and `onIOError` -get fired throughout the life of the WebSocketClient, and must be implemented +connect to. Important events `onOpen`, `onClose`, `onMessage` and `onError` +get fired throughout the life of the WebSocketClient, and must be implemented in **your** subclass. +An example for a WebSocketClient can be found in both the [wiki](https://github.com/TooTallNate/Java-WebSocket/wiki#client-example) and the [example](https://github.com/TooTallNate/Java-WebSocket/tree/master/src/main/example) folder. + +Examples +------------------- + +You can find a lot of examples [here](https://github.com/TooTallNate/Java-WebSocket/tree/master/src/main/example). + WSS Support --------------------------------- This library supports wss. @@ -97,16 +96,15 @@ To see how to use wss please take a look at the examples.
If you do not have a valid **certificate** in place then you will have to create a self signed one. Browsers will simply refuse the connection in case of a bad certificate and will not ask the user to accept it. So the first step will be to make a browser to accept your self signed certificate. ( https://bugzilla.mozilla.org/show_bug.cgi?id=594502 ).
-If the websocket server url is `wss://localhost:8000` visit the url `https://localhost:8000` with your browser. The browser will recognize the handshake and allow you to accept the certificate. This technique is also demonstrated in this [video](http://www.youtube.com/watch?v=F8lBdfAZPkU). +If the websocket server url is `wss://localhost:8000` visit the url `https://localhost:8000` with your browser. The browser will recognize the handshake and allow you to accept the certificate. The vm option `-Djavax.net.debug=all` can help to find out if there is a problem with the certificate. It is currently not possible to accept ws and wss connections at the same time via the same websocket server instance. -For some reason firefox does not allow multible connections to the same wss server if the server uses a different port than the default port(443). - +For some reason Firefox does not allow multiple connections to the same wss server if the server uses a different port than the default port (443). -If you want to use `wss` on the android platfrom you should take a look at [this](http://blog.antoine.li/2010/10/22/android-trusting-ssl-certificates/). +If you want to use `wss` on the android platform you should take a look at [this](https://github.com/TooTallNate/Java-WebSocket/wiki/FAQ:-Secure-WebSockets#wss-on-android). I ( @Davidiusdadi ) would be glad if you would give some feedback whether wss is working fine for you or not. @@ -115,47 +113,10 @@ Minimum Required JDK `Java-WebSocket` is known to work with: - * Java 1.5 (aka SE 6) - * Android 1.6 (API 4) + * Java 8 and higher Other JRE implementations may work as well, but haven't been tested. - -Testing in Android Emulator ---------------------------- - -Please note Android Emulator has issues using `IPv6 addresses`. Executing any -socket related code (like this library) inside it will address an error - -``` bash -java.net.SocketException: Bad address family -``` - -You have to manually disable `IPv6` by calling - -``` java -java.lang.System.setProperty("java.net.preferIPv6Addresses", "false"); -java.lang.System.setProperty("java.net.preferIPv4Stack", "true"); -``` - -somewhere in your project, before instantiating the `WebSocketClient` class. -You can check if you are currently testing in the Android Emulator like this - -``` java -if ("google_sdk".equals( Build.PRODUCT )) { - // ... disable IPv6 -} -``` - - -Getting Support ---------------- - -If you are looking for help using `Java-WebSocket` you might want to check out the -[#java-websocket](http://webchat.freenode.net/?channels=java-websocket) IRC room -on the FreeNode IRC network. - - License ------- diff --git a/autobahn reports/servers/index.html b/autobahn reports/servers/index.html index a5d20b2b4..d9056d0b3 100644 --- a/autobahn reports/servers/index.html +++ b/autobahn reports/servers/index.html @@ -206,6 +206,11 @@ text-align: center; } +td.case_unimplemented { + background-color: #800080; + text-align: center; +} + td.case_failed { background-color: #900; text-align: center; @@ -283,10 +288,10 @@
Toggle Details

-
WebSockets Protocol Test Report
-
Autobahn WebSockets
+
Autobahn WebSocket Testsuite Report
+
Autobahn WebSocket
-

Summary report generated on 2012-02-04T15:47:22Z (UTC) by Autobahn WebSockets v0.4.10.

+

Summary report generated on 2017-04-13T14:56:18.388Z (UTC) by Autobahn WebSocket Testsuite v0.7.6/v0.10.9.

@@ -321,3293 +326,5617 @@
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + -
1 Framingtootallnate/websocketTooTallNateWebsocket
1.1 Text Messages
Case 1.1.1PassNonePass1000
Case 1.1.2PassNonePass1000
Case 1.1.3PassNonePass1000
Case 1.1.4PassNonePass1000
Case 1.1.5PassNonePass1000
Case 1.1.6PassNonePass1000
Case 1.1.7PassNonePass1000
Case 1.1.8PassNonePass1000
1 Framingtootallnate/websocketTooTallNateWebsocket
1.2 Binary Messages
Case 1.2.1PassNonePass1000
Case 1.2.2PassNonePass1000
Case 1.2.3PassNonePass1000
Case 1.2.4PassNonePass1000
Case 1.2.5PassNonePass1000
Case 1.2.6PassNonePass1000
Case 1.2.7PassNonePass1000
Case 1.2.8PassNonePass1000
2 Pings/Pongstootallnate/websocketTooTallNateWebsocket
Case 2.1PassNonePass1000
Case 2.2PassNonePass1000
Case 2.3PassNonePass1000
Case 2.4PassNonePass1000
Case 2.5PassNonePass1002
Case 2.6PassNonePass1000
Case 2.7PassNonePass1000
Case 2.8PassNonePass1000
Case 2.9PassNonePass1000
Case 2.10PassNonePass1000
Case 2.11PassNonePass1000
3 Reserved Bitstootallnate/websocketTooTallNateWebsocket
Case 3.1PassNonePass1002
Case 3.2Non-StrictNoneNon-Strict1002
Case 3.3PassNonePass1002
Case 3.4PassNonePass1002
Case 3.5PassNonePass1002
Case 3.6PassNonePass1002
Case 3.7PassNonePass1002
4 Opcodestootallnate/websocketTooTallNateWebsocket
4.1 Non-control Opcodes
Case 4.1.1PassNonePass1002
Case 4.1.2PassNonePass1002
Case 4.1.3Non-StrictNoneNon-Strict1002
Case 4.1.4Non-StrictNoneNon-Strict1002
Case 4.1.5PassNonePass1002
4 Opcodestootallnate/websocketTooTallNateWebsocket
4.2 Control Opcodes
Case 4.2.1PassNonePass1002
Case 4.2.2PassNonePass1002
Case 4.2.3Non-StrictNoneNon-Strict1002
Case 4.2.4Non-StrictNoneNon-Strict1002
Case 4.2.5PassNonePass1002
5 Fragmentationtootallnate/websocketTooTallNateWebsocket
Case 5.1PassNonePass1002
Case 5.2PassNonePass1002
Case 5.3PassNonePass1000
Case 5.4PassNonePass1000
Case 5.5PassNonePass1000
Case 5.6PassNonePass1000
Case 5.7PassNonePass1000
Case 5.8PassNonePass1000
Case 5.9PassNonePass1002
Case 5.10PassNonePass1002
Case 5.11PassNonePass1002
Case 5.12PassNonePass1002
Case 5.13PassNonePass1002
Case 5.14PassNonePass1002
Case 5.15PassNonePass1002
Case 5.16PassNonePass1002
Case 5.17PassNonePass1002
Case 5.18PassNonePass1002
Case 5.19PassNonePass1000
Case 5.20PassNonePass1000
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.1 Valid UTF-8 with zero payload fragments
Case 6.1.1PassNonePass1000
Case 6.1.2PassNonePass1000
Case 6.1.3PassNonePass1000
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.2 Valid UTF-8 unfragmented, fragmented on code-points and within code-points
Case 6.2.1PassNonePass1000
Case 6.2.2PassNonePass1000
Case 6.2.3PassNonePass1000
Case 6.2.4PassNonePass1000
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.3 Invalid UTF-8 differently fragmented
Case 6.3.1FailFailPass1007
Case 6.3.2FailFailPass1007
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.4 Fail-fast on invalid UTF-8
Case 6.4.1Non-StrictNonePass1007
Case 6.4.2Non-StrictNonePass1007
Case 6.4.3Non-StrictNoneNon-Strict1007
Case 6.4.4Non-StrictNoneNon-Strict1007
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.5 Some valid UTF-8 sequences
Case 6.5.1PassNonePass1000
Case 6.5.2Pass1000
Case 6.5.3Pass1000
Case 6.5.4Pass1000
Case 6.5.5Pass1000
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.6 All prefixes of a valid UTF-8 string that contains multi-byte code points
Case 6.6.1PassNonePass1007
Case 6.6.2PassNonePass1000
Case 6.6.3PassNonePass1007
Case 6.6.4PassNonePass1007
Case 6.6.5PassNonePass1000
Case 6.6.6PassNonePass1007
Case 6.6.7PassNonePass1000
Case 6.6.8PassNonePass1007
Case 6.6.9PassNonePass1000
Case 6.6.10PassNonePass1007
Case 6.6.11PassNonePass1000
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.7 First possible sequence of a certain length
Case 6.7.1PassNonePass1000
Case 6.7.2PassNonePass1000
Case 6.7.3PassNonePass1000
Case 6.7.4PassNonePass1000
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.8 First possible sequence length 5/6 (invalid codepoints)
Case 6.8.1PassNonePass1007
Case 6.8.2PassNonePass1007
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.9 Last possible sequence of a certain length
Case 6.9.1PassNonePass1000
Case 6.9.2PassNonePass1000
Case 6.9.3PassNonePass1000
Case 6.9.4PassNonePass1000
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.10 Last possible sequence length 4/5/6 (invalid codepoints)
Case 6.10.1PassNonePass1007
Case 6.10.2PassNonePass1007
Case 6.10.3PassNonePass1007
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.11 Other boundary conditions
Case 6.11.1PassNonePass1000
Case 6.11.2PassNonePass1000
Case 6.11.3PassNonePass1000
Case 6.11.4PassNonePass1000
Case 6.11.5PassNonePass1007
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.12 Unexpected continuation bytes
Case 6.12.1PassNonePass1007
Case 6.12.2PassNonePass1007
Case 6.12.3PassNonePass1007
Case 6.12.4PassNonePass1007
Case 6.12.5PassNonePass1007
Case 6.12.6PassNonePass1007
Case 6.12.7PassNonePass1007
Case 6.12.8PassNonePass1007
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.13 Lonely start characters
Case 6.13.1PassNonePass1007
Case 6.13.2PassNonePass1007
Case 6.13.3PassNonePass1007
Case 6.13.4PassNonePass1007
Case 6.13.5PassNonePass1007
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.14 Sequences with last continuation byte missing
Case 6.14.1PassNonePass1007
Case 6.14.2PassNonePass1007
Case 6.14.3PassNonePass1007
Case 6.14.4PassNonePass1007
Case 6.14.5PassNonePass1007
Case 6.14.6PassNonePass1007
Case 6.14.7PassNonePass1007
Case 6.14.8PassNonePass1007
Case 6.14.9PassNonePass1007
Case 6.14.10PassNonePass1007
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.15 Concatenation of incomplete sequences
Case 6.15.1PassNonePass1007
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.16 Impossible bytes
Case 6.16.1PassNonePass1007
Case 6.16.2PassNonePass1007
Case 6.16.3PassNonePass1007
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.17 Examples of an overlong ASCII character
Case 6.17.1PassNonePass1007
Case 6.17.2PassNonePass1007
Case 6.17.3PassNonePass1007
Case 6.17.4PassNonePass1007
Case 6.17.5PassNonePass1007
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.18 Maximum overlong sequences
Case 6.18.1PassNonePass1007
Case 6.18.2PassNonePass1007
Case 6.18.3PassNonePass1007
Case 6.18.4PassNonePass1007
Case 6.18.5PassNonePass1007
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.19 Overlong representation of the NUL character
Case 6.19.1PassNonePass1007
Case 6.19.2PassNonePass1007
Case 6.19.3PassNonePass1007
Case 6.19.4PassNonePass1007
Case 6.19.5PassNonePass1007
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.20 Single UTF-16 surrogates
Case 6.20.1FailFailPass1007
Case 6.20.2FailFailPass1007
Case 6.20.3FailFailPass1007
Case 6.20.4FailFailPass1007
Case 6.20.5FailFailPass1007
Case 6.20.6FailFailPass1007
Case 6.20.7FailFailPass1007
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.21 Paired UTF-16 surrogates
Case 6.21.1FailFailPass1007
Case 6.21.2FailFailPass1007
Case 6.21.3FailFailPass1007
Case 6.21.4FailFailPass1007
Case 6.21.5FailFailPass1007
Case 6.21.6FailFailPass1007
Case 6.21.7FailFailPass1007
Case 6.21.8FailFailPass1007
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.22 Non-character code points (valid UTF-8)
Case 6.22.1PassNonePass1000
Case 6.22.2PassNonePass1000
Case 6.22.3PassNonePass1000
Case 6.22.4PassNonePass1000
Case 6.22.5PassNonePass1000
Case 6.22.6PassNonePass1000
Case 6.22.7PassNonePass1000
Case 6.22.8PassNonePass1000
Case 6.22.9PassNonePass1000
Case 6.22.10PassNonePass1000
Case 6.22.11PassNonePass1000
Case 6.22.12PassNonePass1000
Case 6.22.13PassNonePass1000
Case 6.22.14PassNonePass1000
Case 6.22.15PassNonePass1000
Case 6.22.16PassNonePass1000
Case 6.22.17PassNonePass1000
Case 6.22.18PassNonePass1000
Case 6.22.19PassNonePass1000
Case 6.22.20PassNonePass1000
Case 6.22.21PassNonePass1000
Case 6.22.22PassNonePass1000
Case 6.22.23PassNonePass1000
Case 6.22.24PassNonePass1000
Case 6.22.25PassNonePass1000
Case 6.22.26PassNonePass1000
Case 6.22.27PassNonePass1000
Case 6.22.28PassNonePass1000
Case 6.22.29PassNonePass1000
Case 6.22.30PassNonePass1000
Case 6.22.31PassNonePass1000
Case 6.22.32PassNonePass1000
Case 6.22.33PassNonePass1000
Case 6.22.34PassNonePass1000
6 UTF-8 Handlingtootallnate/websocketTooTallNateWebsocket
6.23 Unicode replacement character6.23 Unicode specials (i.e. replacement char)
Case 6.23.1PassNonePass1000
Case 6.23.2Pass1000
Case 6.23.3Pass1000
Case 6.23.4Pass1000
Case 6.23.5Pass1000
Case 6.23.6Pass1000
Case 6.23.7Pass1000
7 Close Handlingtootallnate/websocketTooTallNateWebsocket
7.1 Basic close behavior (fuzzer initiated)
Case 7.1.1PassNonePass1000
Case 7.1.2PassNonePass1000
Case 7.1.3PassNonePass1000
Case 7.1.4PassNonePass1000
Case 7.1.5PassNonePass1000
Case 7.1.6InfoNoneInfo1000
7 Close Handlingtootallnate/websocketTooTallNateWebsocket
7.3 Close frame structure: payload length (fuzzer initiated)
Case 7.3.1PassNonePassNone
Case 7.3.2PassNonePassNone
Case 7.3.3PassNonePass1000
Case 7.3.4PassNonePass1000
Case 7.3.5PassNonePass1000
Case 7.3.6PassNonePass1002
7 Close Handlingtootallnate/websocketTooTallNateWebsocket
7.5 Close frame structure: payload value (fuzzer initiated)
Case 7.5.1PassNonePass1007
7 Close Handlingtootallnate/websocketTooTallNateWebsocket
7.7 Close frame structure: valid close codes (fuzzer initiated)
Case 7.7.1PassNonePass1000
Case 7.7.2PassNonePass1001
Case 7.7.3PassNonePass1002
Case 7.7.4PassNonePass1003
Case 7.7.5PassNonePass1007
Case 7.7.6PassNonePass1008
Case 7.7.7PassNonePass1009
Case 7.7.8PassNonePass1010
Case 7.7.9PassNonePass1011
Case 7.7.10PassNonePass3000
Case 7.7.11PassNonePass3999
Case 7.7.12PassNonePass4000
Case 7.7.13PassNonePass4999
7 Close Handlingtootallnate/websocketTooTallNateWebsocket
7.9 Close frame structure: invalid close codes (fuzzer initiated)
Case 7.9.1PassNonePass1002
Case 7.9.2PassNonePass1002
Case 7.9.3PassNonePass1002
Case 7.9.4PassNonePass1002
Case 7.9.5PassNonePass1002
Case 7.9.6PassNonePassNone
Case 7.9.7PassNonePass1002
Case 7.9.8PassNonePassNone
Case 7.9.9PassNonePassNone
Case 7.9.10PassNonePassNone
Case 7.9.11PassNone
Case 7.9.12PassNone
Case 7.9.13PassNonePassNone
7 Close Handlingtootallnate/websocketTooTallNateWebsocket
7.13 Informational close information (fuzzer initiated)
Case 7.13.1InfoNoneInfo1002
Case 7.13.2InfoNoneInfo1002
9 Limits/Performancetootallnate/websocketTooTallNateWebsocket
9.1 Text Message (increasing size)
Case 9.1.1Pass
67 ms
NonePass
4 ms
1000
Case 9.1.2Pass
260 ms
NonePass
10 ms
1000
Case 9.1.3Pass
1042 ms
NonePass
31 ms
1000
Case 9.1.4Pass
4180 ms
NonePass
149 ms
1000
Case 9.1.5Pass
8364 ms
NonePass
233 ms
1000
Case 9.1.6Pass
16993 ms
NonePass
521 ms
1000
9 Limits/Performancetootallnate/websocketTooTallNateWebsocket
9.2 Binary Message (increasing size)
Case 9.2.1Pass
39 ms
NonePass
3 ms
1000
Case 9.2.2Pass
85 ms
NonePass
7 ms
1000
Case 9.2.3Pass
338 ms
NonePass
28 ms
1000
Case 9.2.4Pass
1375 ms
NonePass
103 ms
1000
Case 9.2.5Pass
2740 ms
NonePass
231 ms
1000
Case 9.2.6Pass
5554 ms
NonePass
408 ms
1000
9 Limits/Performancetootallnate/websocketTooTallNateWebsocket
9.3 Fragmented Text Message (fixed size, increasing fragment size)
Case 9.3.1Pass
70578 ms
NonePass
32384 ms
1000
Case 9.3.2Pass
20991 ms
NonePass
8061 ms
1000
Case 9.3.3Pass
8471 ms
NonePass
2199 ms
1000
Case 9.3.4Pass
5286 ms
NonePass
564 ms
1000
Case 9.3.5Pass
4403 ms
NonePass
179 ms
1000
Case 9.3.6Pass
4160 ms
NonePass
86 ms
1000
Case 9.3.7Pass
4123 ms
NonePass
71 ms
1000
Case 9.3.8Pass
4096 ms
NonePass
69 ms
1000
Case 9.3.9Pass
4074 ms
NonePass
73 ms
1000
9 Limits/Performancetootallnate/websocketTooTallNateWebsocket
9.4 Fragmented Binary Message (fixed size, increasing fragment size)
Case 9.4.1Pass
69384 ms
NonePass
2445 ms
1000
Case 9.4.2Pass
17962 ms
NonePass
654 ms
1000
Case 9.4.3Pass
5637 ms
NonePass
205 ms
1000
Case 9.4.4Pass
2497 ms
NonePass
68 ms
1000
Case 9.4.5Pass
1597 ms
NonePass
42 ms
1000
Case 9.4.6Pass
1359 ms
NonePass
39 ms
1000
Case 9.4.7Pass
1299 ms
NonePass
55 ms
1000
Case 9.4.8Pass
1296 ms
NonePass
70 ms
1000
Case 9.4.9Pass
1273 ms
NonePass
50 ms
1000
9 Limits/Performancetootallnate/websocketTooTallNateWebsocket
9.5 Text Message (fixed size, increasing chop size)
Case 9.5.1Pass
17529 ms
NonePass
16426 ms
1000
Case 9.5.2Pass
9293 ms
NonePass
8233 ms
1000
Case 9.5.3Pass
5166 ms
NonePass
4126 ms
1000
Case 9.5.4Pass
3168 ms
NonePass
2072 ms
1000
Case 9.5.5Pass
2137 ms
NonePass
1049 ms
1000
Case 9.5.6Pass
1571 ms
NonePass
534 ms
1000
9 Limits/Performancetootallnate/websocketTooTallNateWebsocket
9.6 Binary Text Message (fixed size, increasing chop size)
Case 9.6.1Pass
16825 ms
NonePass
16426 ms
1000
Case 9.6.2Pass
8639 ms
NonePass
8227 ms
1000
Case 9.6.3Pass
4504 ms
NonePass
4122 ms
1000
Case 9.6.4Pass
2434 ms
NonePass
2074 ms
1000
Case 9.6.5Pass
1461 ms
NonePass
1048 ms
1000
Case 9.6.6Pass
909 ms
NonePass
533 ms
1000
9 Limits/Performancetootallnate/websocketTooTallNateWebsocket
9.7 Text Message Roundtrip Time (fixed number, increasing size)
Case 9.7.1Pass
267 ms
NonePass
160 ms
1000
Case 9.7.2Pass
289 ms
NonePass
164 ms
1000
Case 9.7.3Pass
321 ms
NonePass
169 ms
1000
Case 9.7.4Pass
551 ms
NonePass
186 ms
1000
Case 9.7.5Pass
1435 ms
NonePass
239 ms
1000
Case 9.7.6Pass
4887 ms
NonePass
439 ms
1000
9 Limits/Performancetootallnate/websocketTooTallNateWebsocket
9.8 Binary Message Roundtrip Time (fixed number, increasing size)
Case 9.8.1Pass
227 ms
NonePass
154 ms
1000
Case 9.8.2Pass
259 ms
NonePass
165 ms
1000
Case 9.8.3Pass
255 ms
NonePass
162 ms
1000
Case 9.8.4Pass
355 ms
NonePass
177 ms
1000
Case 9.8.5Pass
636 ms
NonePass
220 ms
1000
Case 9.8.6Pass
1940 ms
NonePass
395 ms
1000
10 Autobahn Protocol Optionstootallnate/websocket10 MiscTooTallNateWebsocket
10.1 Auto-Fragmentation
Case 10.1.1PassNonePass1000
-

-
-
- -

Case 1.1.1

- Up -

Case Description

Send text message with payload 0.

-

Case Expectation

Receive echo'ed text message (with empty payload). Clean close with normal code.

-
- -

Case 1.1.2

- Up -

Case Description

Send text message message with payload of length 125.

-

Case Expectation

Receive echo'ed text message (with payload as sent). Clean close with normal code.

-
- -

Case 1.1.3

- Up -

Case Description

Send text message message with payload of length 126.

-

Case Expectation

Receive echo'ed text message (with payload as sent). Clean close with normal code.

-
- -

Case 1.1.4

- Up -

Case Description

Send text message message with payload of length 127.

-

Case Expectation

Receive echo'ed text message (with payload as sent). Clean close with normal code.

-
- -

Case 1.1.5

- Up -

Case Description

Send text message message with payload of length 128.

-

Case Expectation

Receive echo'ed text message (with payload as sent). Clean close with normal code.

-
- -

Case 1.1.6

- Up -

Case Description

Send text message message with payload of length 65535.

-

Case Expectation

Receive echo'ed text message (with payload as sent). Clean close with normal code.

-
- -

Case 1.1.7

- Up -

Case Description

Send text message message with payload of length 65536.

-

Case Expectation

Receive echo'ed text message (with payload as sent). Clean close with normal code.

-
- -

Case 1.1.8

- Up -

Case Description

Send text message message with payload of length 65536. Sent out data in chops of 997 octets.

-

Case Expectation

Receive echo'ed text message (with payload as sent). Clean close with normal code.

-
- -

Case 1.2.1

- Up -

Case Description

Send binary message with payload 0.

-

Case Expectation

Receive echo'ed binary message (with empty payload). Clean close with normal code.

-
- -

Case 1.2.2

- Up -

Case Description

Send binary message message with payload of length 125.

-

Case Expectation

Receive echo'ed binary message (with payload as sent). Clean close with normal code.

-
- -

Case 1.2.3

- Up -

Case Description

Send binary message message with payload of length 126.

-

Case Expectation

Receive echo'ed binary message (with payload as sent). Clean close with normal code.

-
- -

Case 1.2.4

- Up -

Case Description

Send binary message message with payload of length 127.

-

Case Expectation

Receive echo'ed binary message (with payload as sent). Clean close with normal code.

-
- -

Case 1.2.5

- Up -

Case Description

Send binary message message with payload of length 128.

-

Case Expectation

Receive echo'ed binary message (with payload as sent). Clean close with normal code.

-
- -

Case 1.2.6

- Up -

Case Description

Send binary message message with payload of length 65535.

-

Case Expectation

Receive echo'ed binary message (with payload as sent). Clean close with normal code.

-
- -

Case 1.2.7

- Up -

Case Description

Send binary message message with payload of length 65536.

-

Case Expectation

Receive echo'ed binary message (with payload as sent). Clean close with normal code.

-
- -

Case 1.2.8

- Up -

Case Description

Send binary message message with payload of length 65536. Sent out data in chops of 997 octets.

-

Case Expectation

Receive echo'ed binary message (with payload as sent). Clean close with normal code.

-
- -

Case 2.1

- Up -

Case Description

Send ping without payload.

-

Case Expectation

Pong (with empty payload) is sent in reply to Ping. Clean close with normal code.

-
- -

Case 2.2

- Up -

Case Description

Send ping with small text payload.

-

Case Expectation

Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.

-
- -

Case 2.3

- Up -

Case Description

Send ping with small binary (non UTF-8) payload.

-

Case Expectation

Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.

-
- -

Case 2.4

- Up -

Case Description

Send ping with binary payload of 125 octets.

-

Case Expectation

Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.

-
- -

Case 2.5

- Up -

Case Description

Send ping with binary payload of 126 octets.

-

Case Expectation

Connection is failed immediately (1002/Protocol Error), since control frames are only allowed to have payload up to and including 125 octets..

-
- -

Case 2.6

- Up -

Case Description

Send ping with binary payload of 125 octets, send in octet-wise chops.

-

Case Expectation

Pong with payload echo'ed is sent in reply to Ping. Implementations must be TCP clean. Clean close with normal code.

-
- -

Case 2.7

- Up -

Case Description

Send unsolicited pong without payload. Verify nothing is received. Clean close with normal code.

-

Case Expectation

Nothing.

-
- -

Case 2.8

- Up -

Case Description

Send unsolicited pong with payload. Verify nothing is received. Clean close with normal code.

-

Case Expectation

Nothing.

-
- -

Case 2.9

- Up -

Case Description

Send unsolicited pong with payload. Send ping with payload. Verify pong for ping is received.

-

Case Expectation

Nothing in reply to own Pong, but Pong with payload echo'ed in reply to Ping. Clean close with normal code.

-
- -

Case 2.10

- Up -

Case Description

Send 10 Pings with payload.

-

Case Expectation

Pongs for our Pings with all the payloads. Note: This is not required by the Spec .. but we check for this behaviour anyway. Clean close with normal code.

-
- -

Case 2.11

- Up -

Case Description

Send 10 Pings with payload. Send out octets in octet-wise chops.

-

Case Expectation

Pongs for our Pings with all the payloads. Note: This is not required by the Spec .. but we check for this behaviour anyway. Clean close with normal code.

-
- -

Case 3.1

- Up -

Case Description

Send small text message with RSV = 1.

-

Case Expectation

The connection is failed immediately (1002/protocol error), since RSV must be 0, when no extension defining RSV meaning has been negoiated.

-
- -

Case 3.2

- Up -

Case Description

Send small text message, then send again with RSV = 2, then send Ping.

-

Case Expectation

Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.

-
- -

Case 3.3

- Up -

Case Description

Send small text message, then send again with RSV = 3, then send Ping. Octets are sent in frame-wise chops. Octets are sent in octet-wise chops.

-

Case Expectation

Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.

-
- -

Case 3.4

- Up -

Case Description

Send small text message, then send again with RSV = 4, then send Ping. Octets are sent in octet-wise chops.

-

Case Expectation

Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.

-
- -

Case 3.5

- Up -

Case Description

Send small binary message with RSV = 5.

-

Case Expectation

The connection is failed immediately, since RSV must be 0.

-
- -

Case 3.6

- Up -

Case Description

Send Ping with RSV = 6.

-

Case Expectation

The connection is failed immediately, since RSV must be 0.

-
- -

Case 3.7

- Up -

Case Description

Send Close with RSV = 7.

-

Case Expectation

The connection is failed immediately, since RSV must be 0.

-
- -

Case 4.1.1

- Up -

Case Description

Send frame with reserved non-control Opcode = 3.

-

Case Expectation

The connection is failed immediately.

-
- -

Case 4.1.2

- Up -

Case Description

Send frame with reserved non-control Opcode = 4 and non-empty payload.

-

Case Expectation

The connection is failed immediately.

-
- -

Case 4.1.3

- Up -

Case Description

Send small text message, then send frame with reserved non-control Opcode = 5, then send Ping.

-

Case Expectation

Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.

-
- -

Case 4.1.4

- Up -

Case Description

Send small text message, then send frame with reserved non-control Opcode = 6 and non-empty payload, then send Ping.

-

Case Expectation

Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.

-
- + + 12 WebSocket Compression (different payloads) + TooTallNateWebsocket + + + 12.1 Large JSON data file (utf8, 194056 bytes) + + + Case 12.1.1 + Unimplemented + + + Case 12.1.2 + Unimplemented + + + Case 12.1.3 + Unimplemented + + + Case 12.1.4 + Unimplemented + + + Case 12.1.5 + Unimplemented + + + Case 12.1.6 + Unimplemented + + + Case 12.1.7 + Unimplemented + + + Case 12.1.8 + Unimplemented + + + Case 12.1.9 + Unimplemented + + + Case 12.1.10 + Unimplemented + + + Case 12.1.11 + Unimplemented + + + Case 12.1.12 + Unimplemented + + + Case 12.1.13 + Unimplemented + + + Case 12.1.14 + Unimplemented + + + Case 12.1.15 + Unimplemented + + + Case 12.1.16 + Unimplemented + + + Case 12.1.17 + Unimplemented + + + Case 12.1.18 + Unimplemented + + + 12 WebSocket Compression (different payloads) + TooTallNateWebsocket + + + 12.2 Lena Picture, Bitmap 512x512 bw (binary, 263222 bytes) + + + Case 12.2.1 + Unimplemented + + + Case 12.2.2 + Unimplemented + + + Case 12.2.3 + Unimplemented + + + Case 12.2.4 + Unimplemented + + + Case 12.2.5 + Unimplemented + + + Case 12.2.6 + Unimplemented + + + Case 12.2.7 + Unimplemented + + + Case 12.2.8 + Unimplemented + + + Case 12.2.9 + Unimplemented + + + Case 12.2.10 + Unimplemented + + + Case 12.2.11 + Unimplemented + + + Case 12.2.12 + Unimplemented + + + Case 12.2.13 + Unimplemented + + + Case 12.2.14 + Unimplemented + + + Case 12.2.15 + Unimplemented + + + Case 12.2.16 + Unimplemented + + + Case 12.2.17 + Unimplemented + + + Case 12.2.18 + Unimplemented + + + 12 WebSocket Compression (different payloads) + TooTallNateWebsocket + + + 12.3 Human readable text, Goethe's Faust I (German) (binary, 222218 bytes) + + + Case 12.3.1 + Unimplemented + + + Case 12.3.2 + Unimplemented + + + Case 12.3.3 + Unimplemented + + + Case 12.3.4 + Unimplemented + + + Case 12.3.5 + Unimplemented + + + Case 12.3.6 + Unimplemented + + + Case 12.3.7 + Unimplemented + + + Case 12.3.8 + Unimplemented + + + Case 12.3.9 + Unimplemented + + + Case 12.3.10 + Unimplemented + + + Case 12.3.11 + Unimplemented + + + Case 12.3.12 + Unimplemented + + + Case 12.3.13 + Unimplemented + + + Case 12.3.14 + Unimplemented + + + Case 12.3.15 + Unimplemented + + + Case 12.3.16 + Unimplemented + + + Case 12.3.17 + Unimplemented + + + Case 12.3.18 + Unimplemented + + + 12 WebSocket Compression (different payloads) + TooTallNateWebsocket + + + 12.4 Large HTML file (utf8, 263527 bytes) + + + Case 12.4.1 + Unimplemented + + + Case 12.4.2 + Unimplemented + + + Case 12.4.3 + Unimplemented + + + Case 12.4.4 + Unimplemented + + + Case 12.4.5 + Unimplemented + + + Case 12.4.6 + Unimplemented + + + Case 12.4.7 + Unimplemented + + + Case 12.4.8 + Unimplemented + + + Case 12.4.9 + Unimplemented + + + Case 12.4.10 + Unimplemented + + + Case 12.4.11 + Unimplemented + + + Case 12.4.12 + Unimplemented + + + Case 12.4.13 + Unimplemented + + + Case 12.4.14 + Unimplemented + + + Case 12.4.15 + Unimplemented + + + Case 12.4.16 + Unimplemented + + + Case 12.4.17 + Unimplemented + + + Case 12.4.18 + Unimplemented + + + 12 WebSocket Compression (different payloads) + TooTallNateWebsocket + + + 12.5 A larger PDF (binary, 1042328 bytes) + + + Case 12.5.1 + Unimplemented + + + Case 12.5.2 + Unimplemented + + + Case 12.5.3 + Unimplemented + + + Case 12.5.4 + Unimplemented + + + Case 12.5.5 + Unimplemented + + + Case 12.5.6 + Unimplemented + + + Case 12.5.7 + Unimplemented + + + Case 12.5.8 + Unimplemented + + + Case 12.5.9 + Unimplemented + + + Case 12.5.10 + Unimplemented + + + Case 12.5.11 + Unimplemented + + + Case 12.5.12 + Unimplemented + + + Case 12.5.13 + Unimplemented + + + Case 12.5.14 + Unimplemented + + + Case 12.5.15 + Unimplemented + + + Case 12.5.16 + Unimplemented + + + Case 12.5.17 + Unimplemented + + + Case 12.5.18 + Unimplemented + + + 13 WebSocket Compression (different parameters) + TooTallNateWebsocket + + + 13.1 Large JSON data file (utf8, 194056 bytes) - client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)] / server accept (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)] + + + Case 13.1.1 + Unimplemented + + + Case 13.1.2 + Unimplemented + + + Case 13.1.3 + Unimplemented + + + Case 13.1.4 + Unimplemented + + + Case 13.1.5 + Unimplemented + + + Case 13.1.6 + Unimplemented + + + Case 13.1.7 + Unimplemented + + + Case 13.1.8 + Unimplemented + + + Case 13.1.9 + Unimplemented + + + Case 13.1.10 + Unimplemented + + + Case 13.1.11 + Unimplemented + + + Case 13.1.12 + Unimplemented + + + Case 13.1.13 + Unimplemented + + + Case 13.1.14 + Unimplemented + + + Case 13.1.15 + Unimplemented + + + Case 13.1.16 + Unimplemented + + + Case 13.1.17 + Unimplemented + + + Case 13.1.18 + Unimplemented + + + 13 WebSocket Compression (different parameters) + TooTallNateWebsocket + + + 13.2 Large JSON data file (utf8, 194056 bytes) - client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)] / server accept (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)] + + + Case 13.2.1 + Unimplemented + + + Case 13.2.2 + Unimplemented + + + Case 13.2.3 + Unimplemented + + + Case 13.2.4 + Unimplemented + + + Case 13.2.5 + Unimplemented + + + Case 13.2.6 + Unimplemented + + + Case 13.2.7 + Unimplemented + + + Case 13.2.8 + Unimplemented + + + Case 13.2.9 + Unimplemented + + + Case 13.2.10 + Unimplemented + + + Case 13.2.11 + Unimplemented + + + Case 13.2.12 + Unimplemented + + + Case 13.2.13 + Unimplemented + + + Case 13.2.14 + Unimplemented + + + Case 13.2.15 + Unimplemented + + + Case 13.2.16 + Unimplemented + + + Case 13.2.17 + Unimplemented + + + Case 13.2.18 + Unimplemented + + + 13 WebSocket Compression (different parameters) + TooTallNateWebsocket + + + 13.3 Large JSON data file (utf8, 194056 bytes) - client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)] / server accept (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)] + + + Case 13.3.1 + Unimplemented + + + Case 13.3.2 + Unimplemented + + + Case 13.3.3 + Unimplemented + + + Case 13.3.4 + Unimplemented + + + Case 13.3.5 + Unimplemented + + + Case 13.3.6 + Unimplemented + + + Case 13.3.7 + Unimplemented + + + Case 13.3.8 + Unimplemented + + + Case 13.3.9 + Unimplemented + + + Case 13.3.10 + Unimplemented + + + Case 13.3.11 + Unimplemented + + + Case 13.3.12 + Unimplemented + + + Case 13.3.13 + Unimplemented + + + Case 13.3.14 + Unimplemented + + + Case 13.3.15 + Unimplemented + + + Case 13.3.16 + Unimplemented + + + Case 13.3.17 + Unimplemented + + + Case 13.3.18 + Unimplemented + + + 13 WebSocket Compression (different parameters) + TooTallNateWebsocket + + + 13.4 Large JSON data file (utf8, 194056 bytes) - client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)] / server accept (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)] + + + Case 13.4.1 + Unimplemented + + + Case 13.4.2 + Unimplemented + + + Case 13.4.3 + Unimplemented + + + Case 13.4.4 + Unimplemented + + + Case 13.4.5 + Unimplemented + + + Case 13.4.6 + Unimplemented + + + Case 13.4.7 + Unimplemented + + + Case 13.4.8 + Unimplemented + + + Case 13.4.9 + Unimplemented + + + Case 13.4.10 + Unimplemented + + + Case 13.4.11 + Unimplemented + + + Case 13.4.12 + Unimplemented + + + Case 13.4.13 + Unimplemented + + + Case 13.4.14 + Unimplemented + + + Case 13.4.15 + Unimplemented + + + Case 13.4.16 + Unimplemented + + + Case 13.4.17 + Unimplemented + + + Case 13.4.18 + Unimplemented + + + 13 WebSocket Compression (different parameters) + TooTallNateWebsocket + + + 13.5 Large JSON data file (utf8, 194056 bytes) - client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)] / server accept (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)] + + + Case 13.5.1 + Unimplemented + + + Case 13.5.2 + Unimplemented + + + Case 13.5.3 + Unimplemented + + + Case 13.5.4 + Unimplemented + + + Case 13.5.5 + Unimplemented + + + Case 13.5.6 + Unimplemented + + + Case 13.5.7 + Unimplemented + + + Case 13.5.8 + Unimplemented + + + Case 13.5.9 + Unimplemented + + + Case 13.5.10 + Unimplemented + + + Case 13.5.11 + Unimplemented + + + Case 13.5.12 + Unimplemented + + + Case 13.5.13 + Unimplemented + + + Case 13.5.14 + Unimplemented + + + Case 13.5.15 + Unimplemented + + + Case 13.5.16 + Unimplemented + + + Case 13.5.17 + Unimplemented + + + Case 13.5.18 + Unimplemented + + + 13 WebSocket Compression (different parameters) + TooTallNateWebsocket + + + 13.6 Large JSON data file (utf8, 194056 bytes) - client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)] / server accept (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)] + + + Case 13.6.1 + Unimplemented + + + Case 13.6.2 + Unimplemented + + + Case 13.6.3 + Unimplemented + + + Case 13.6.4 + Unimplemented + + + Case 13.6.5 + Unimplemented + + + Case 13.6.6 + Unimplemented + + + Case 13.6.7 + Unimplemented + + + Case 13.6.8 + Unimplemented + + + Case 13.6.9 + Unimplemented + + + Case 13.6.10 + Unimplemented + + + Case 13.6.11 + Unimplemented + + + Case 13.6.12 + Unimplemented + + + Case 13.6.13 + Unimplemented + + + Case 13.6.14 + Unimplemented + + + Case 13.6.15 + Unimplemented + + + Case 13.6.16 + Unimplemented + + + Case 13.6.17 + Unimplemented + + + Case 13.6.18 + Unimplemented + + + 13 WebSocket Compression (different parameters) + TooTallNateWebsocket + + + 13.7 Large JSON data file (utf8, 194056 bytes) - client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)] / server accept (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)] + + + Case 13.7.1 + Unimplemented + + + Case 13.7.2 + Unimplemented + + + Case 13.7.3 + Unimplemented + + + Case 13.7.4 + Unimplemented + + + Case 13.7.5 + Unimplemented + + + Case 13.7.6 + Unimplemented + + + Case 13.7.7 + Unimplemented + + + Case 13.7.8 + Unimplemented + + + Case 13.7.9 + Unimplemented + + + Case 13.7.10 + Unimplemented + + + Case 13.7.11 + Unimplemented + + + Case 13.7.12 + Unimplemented + + + Case 13.7.13 + Unimplemented + + + Case 13.7.14 + Unimplemented + + + Case 13.7.15 + Unimplemented + + + Case 13.7.16 + Unimplemented + + + Case 13.7.17 + Unimplemented + + + Case 13.7.18 + Unimplemented + + +

+
+
+ +

Case 1.1.1

+ Up +

Case Description

Send text message with payload 0.

+

Case Expectation

Receive echo'ed text message (with empty payload). Clean close with normal code.

+
+ +

Case 1.1.2

+ Up +

Case Description

Send text message message with payload of length 125.

+

Case Expectation

Receive echo'ed text message (with payload as sent). Clean close with normal code.

+
+ +

Case 1.1.3

+ Up +

Case Description

Send text message message with payload of length 126.

+

Case Expectation

Receive echo'ed text message (with payload as sent). Clean close with normal code.

+
+ +

Case 1.1.4

+ Up +

Case Description

Send text message message with payload of length 127.

+

Case Expectation

Receive echo'ed text message (with payload as sent). Clean close with normal code.

+
+ +

Case 1.1.5

+ Up +

Case Description

Send text message message with payload of length 128.

+

Case Expectation

Receive echo'ed text message (with payload as sent). Clean close with normal code.

+
+ +

Case 1.1.6

+ Up +

Case Description

Send text message message with payload of length 65535.

+

Case Expectation

Receive echo'ed text message (with payload as sent). Clean close with normal code.

+
+ +

Case 1.1.7

+ Up +

Case Description

Send text message message with payload of length 65536.

+

Case Expectation

Receive echo'ed text message (with payload as sent). Clean close with normal code.

+
+ +

Case 1.1.8

+ Up +

Case Description

Send text message message with payload of length 65536. Sent out data in chops of 997 octets.

+

Case Expectation

Receive echo'ed text message (with payload as sent). Clean close with normal code.

+
+ +

Case 1.2.1

+ Up +

Case Description

Send binary message with payload 0.

+

Case Expectation

Receive echo'ed binary message (with empty payload). Clean close with normal code.

+
+ +

Case 1.2.2

+ Up +

Case Description

Send binary message message with payload of length 125.

+

Case Expectation

Receive echo'ed binary message (with payload as sent). Clean close with normal code.

+
+ +

Case 1.2.3

+ Up +

Case Description

Send binary message message with payload of length 126.

+

Case Expectation

Receive echo'ed binary message (with payload as sent). Clean close with normal code.

+
+ +

Case 1.2.4

+ Up +

Case Description

Send binary message message with payload of length 127.

+

Case Expectation

Receive echo'ed binary message (with payload as sent). Clean close with normal code.

+
+ +

Case 1.2.5

+ Up +

Case Description

Send binary message message with payload of length 128.

+

Case Expectation

Receive echo'ed binary message (with payload as sent). Clean close with normal code.

+
+ +

Case 1.2.6

+ Up +

Case Description

Send binary message message with payload of length 65535.

+

Case Expectation

Receive echo'ed binary message (with payload as sent). Clean close with normal code.

+
+ +

Case 1.2.7

+ Up +

Case Description

Send binary message message with payload of length 65536.

+

Case Expectation

Receive echo'ed binary message (with payload as sent). Clean close with normal code.

+
+ +

Case 1.2.8

+ Up +

Case Description

Send binary message message with payload of length 65536. Sent out data in chops of 997 octets.

+

Case Expectation

Receive echo'ed binary message (with payload as sent). Clean close with normal code.

+
+ +

Case 2.1

+ Up +

Case Description

Send ping without payload.

+

Case Expectation

Pong (with empty payload) is sent in reply to Ping. Clean close with normal code.

+
+ +

Case 2.2

+ Up +

Case Description

Send ping with small text payload.

+

Case Expectation

Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.

+
+ +

Case 2.3

+ Up +

Case Description

Send ping with small binary (non UTF-8) payload.

+

Case Expectation

Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.

+
+ +

Case 2.4

+ Up +

Case Description

Send ping with binary payload of 125 octets.

+

Case Expectation

Pong with payload echo'ed is sent in reply to Ping. Clean close with normal code.

+
+ +

Case 2.5

+ Up +

Case Description

Send ping with binary payload of 126 octets.

+

Case Expectation

Connection is failed immediately (1002/Protocol Error), since control frames are only allowed to have payload up to and including 125 octets..

+
+ +

Case 2.6

+ Up +

Case Description

Send ping with binary payload of 125 octets, send in octet-wise chops.

+

Case Expectation

Pong with payload echo'ed is sent in reply to Ping. Implementations must be TCP clean. Clean close with normal code.

+
+ +

Case 2.7

+ Up +

Case Description

Send unsolicited pong without payload. Verify nothing is received. Clean close with normal code.

+

Case Expectation

Nothing.

+
+ +

Case 2.8

+ Up +

Case Description

Send unsolicited pong with payload. Verify nothing is received. Clean close with normal code.

+

Case Expectation

Nothing.

+
+ +

Case 2.9

+ Up +

Case Description

Send unsolicited pong with payload. Send ping with payload. Verify pong for ping is received.

+

Case Expectation

Nothing in reply to own Pong, but Pong with payload echo'ed in reply to Ping. Clean close with normal code.

+
+ +

Case 2.10

+ Up +

Case Description

Send 10 Pings with payload.

+

Case Expectation

Pongs for our Pings with all the payloads. Note: This is not required by the Spec .. but we check for this behaviour anyway. Clean close with normal code.

+
+ +

Case 2.11

+ Up +

Case Description

Send 10 Pings with payload. Send out octets in octet-wise chops.

+

Case Expectation

Pongs for our Pings with all the payloads. Note: This is not required by the Spec .. but we check for this behaviour anyway. Clean close with normal code.

+
+ +

Case 3.1

+ Up +

Case Description

Send small text message with RSV = 1.

+

Case Expectation

The connection is failed immediately (1002/protocol error), since RSV must be 0, when no extension defining RSV meaning has been negotiated.

+
+ +

Case 3.2

+ Up +

Case Description

Send small text message, then send again with RSV = 2, then send Ping.

+

Case Expectation

Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negotiated. The Pong is not received.

+
+ +

Case 3.3

+ Up +

Case Description

Send small text message, then send again with RSV = 3, then send Ping. Octets are sent in frame-wise chops. Octets are sent in octet-wise chops.

+

Case Expectation

Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negotiated. The Pong is not received.

+
+ +

Case 3.4

+ Up +

Case Description

Send small text message, then send again with RSV = 4, then send Ping. Octets are sent in octet-wise chops.

+

Case Expectation

Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negotiated. The Pong is not received.

+
+ +

Case 3.5

+ Up +

Case Description

Send small binary message with RSV = 5.

+

Case Expectation

The connection is failed immediately, since RSV must be 0.

+
+ +

Case 3.6

+ Up +

Case Description

Send Ping with RSV = 6.

+

Case Expectation

The connection is failed immediately, since RSV must be 0.

+
+ +

Case 3.7

+ Up +

Case Description

Send Close with RSV = 7.

+

Case Expectation

The connection is failed immediately, since RSV must be 0.

+
+ +

Case 4.1.1

+ Up +

Case Description

Send frame with reserved non-control Opcode = 3.

+

Case Expectation

The connection is failed immediately.

+
+ +

Case 4.1.2

+ Up +

Case Description

Send frame with reserved non-control Opcode = 4 and non-empty payload.

+

Case Expectation

The connection is failed immediately.

+
+ +

Case 4.1.3

+ Up +

Case Description

Send small text message, then send frame with reserved non-control Opcode = 5, then send Ping.

+

Case Expectation

Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.

+
+ +

Case 4.1.4

+ Up +

Case Description

Send small text message, then send frame with reserved non-control Opcode = 6 and non-empty payload, then send Ping.

+

Case Expectation

Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.

+
+

Case 4.1.5

Up -

Case Description

Send small text message, then send frame with reserved non-control Opcode = 7 and non-empty payload, then send Ping.

-

Case Expectation

Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.

+

Case Description

Send small text message, then send frame with reserved non-control Opcode = 7 and non-empty payload, then send Ping.

+

Case Expectation

Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.

+
+ +

Case 4.2.1

+ Up +

Case Description

Send frame with reserved control Opcode = 11.

+

Case Expectation

The connection is failed immediately.

+
+ +

Case 4.2.2

+ Up +

Case Description

Send frame with reserved control Opcode = 12 and non-empty payload.

+

Case Expectation

The connection is failed immediately.

+
+ +

Case 4.2.3

+ Up +

Case Description

Send small text message, then send frame with reserved control Opcode = 13, then send Ping.

+

Case Expectation

Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.

+
+ +

Case 4.2.4

+ Up +

Case Description

Send small text message, then send frame with reserved control Opcode = 14 and non-empty payload, then send Ping.

+

Case Expectation

Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.

+
+ +

Case 4.2.5

+ Up +

Case Description

Send small text message, then send frame with reserved control Opcode = 15 and non-empty payload, then send Ping.

+

Case Expectation

Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.

+
+ +

Case 5.1

+ Up +

Case Description

Send Ping fragmented into 2 fragments.

+

Case Expectation

Connection is failed immediately, since control message MUST NOT be fragmented.

+
+ +

Case 5.2

+ Up +

Case Description

Send Pong fragmented into 2 fragments.

+

Case Expectation

Connection is failed immediately, since control message MUST NOT be fragmented.

+
+ +

Case 5.3

+ Up +

Case Description

Send text Message fragmented into 2 fragments.

+

Case Expectation

Message is processed and echo'ed back to us.

+
+ +

Case 5.4

+ Up +

Case Description

Send text Message fragmented into 2 fragments, octets are sent in frame-wise chops.

+

Case Expectation

Message is processed and echo'ed back to us.

+
+ +

Case 5.5

+ Up +

Case Description

Send text Message fragmented into 2 fragments, octets are sent in octet-wise chops.

+

Case Expectation

Message is processed and echo'ed back to us.

+
+ +

Case 5.6

+ Up +

Case Description

Send text Message fragmented into 2 fragments, one ping with payload in-between.

+

Case Expectation

A pong is received, then the message is echo'ed back to us.

+
+ +

Case 5.7

+ Up +

Case Description

Send text Message fragmented into 2 fragments, one ping with payload in-between. Octets are sent in frame-wise chops.

+

Case Expectation

A pong is received, then the message is echo'ed back to us.

+
+ +

Case 5.8

+ Up +

Case Description

Send text Message fragmented into 2 fragments, one ping with payload in-between. Octets are sent in octet-wise chops.

+

Case Expectation

A pong is received, then the message is echo'ed back to us.

+
+ +

Case 5.9

+ Up +

Case Description

Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in one chop.

+

Case Expectation

The connection is failed immediately, since there is no message to continue.

+
+ +

Case 5.10

+ Up +

Case Description

Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in per-frame chops.

+

Case Expectation

The connection is failed immediately, since there is no message to continue.

+
+ +

Case 5.11

+ Up +

Case Description

Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in octet-wise chops.

+

Case Expectation

The connection is failed immediately, since there is no message to continue.

+
+ +

Case 5.12

+ Up +

Case Description

Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in one chop.

+

Case Expectation

The connection is failed immediately, since there is no message to continue.

+
+ +

Case 5.13

+ Up +

Case Description

Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in per-frame chops.

+

Case Expectation

The connection is failed immediately, since there is no message to continue.

+
+ +

Case 5.14

+ Up +

Case Description

Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in octet-wise chops.

+

Case Expectation

The connection is failed immediately, since there is no message to continue.

+
+ +

Case 5.15

+ Up +

Case Description

Send text Message fragmented into 2 fragments, then Continuation Frame with FIN = false where there is nothing to continue, then unfragmented Text Message, all sent in one chop.

+

Case Expectation

The connection is failed immediately, since there is no message to continue.

+
+ +

Case 5.16

+ Up +

Case Description

Repeated 2x: Continuation Frame with FIN = false (where there is nothing to continue), then text Message fragmented into 2 fragments.

+

Case Expectation

The connection is failed immediately, since there is no message to continue.

+
+ +

Case 5.17

+ Up +

Case Description

Repeated 2x: Continuation Frame with FIN = true (where there is nothing to continue), then text Message fragmented into 2 fragments.

+

Case Expectation

The connection is failed immediately, since there is no message to continue.

+
+ +

Case 5.18

+ Up +

Case Description

Send text Message fragmented into 2 fragments, with both frame opcodes set to text, sent in one chop.

+

Case Expectation

The connection is failed immediately, since all data frames after the initial data frame must have opcode 0.

+
+ +

Case 5.19

+ Up +

Case Description

A fragmented text message is sent in multiple frames. After + sending the first 2 frames of the text message, a Ping is sent. Then we wait 1s, + then we send 2 more text fragments, another Ping and then the final text fragment. + Everything is legal.

+

Case Expectation

The peer immediately answers the first Ping before + it has received the last text message fragment. The peer pong's back the Ping's + payload exactly, and echo's the payload of the fragmented message back to us.

+
+ +

Case 5.20

+ Up +

Case Description

Same as Case 5.19, but send all frames with SYNC = True. + Note, this does not change the octets sent in any way, only how the stream + is chopped up on the wire.

+

Case Expectation

Same as Case 5.19. Implementations must be agnostic to how + octet stream is chopped up on wire (must be TCP clean).

+
+ +

Case 6.1.1

+ Up +

Case Description

Send text message of length 0.

+

Case Expectation

A message is echo'ed back to us (with empty payload).

+
+ +

Case 6.1.2

+ Up +

Case Description

Send fragmented text message, 3 fragments each of length 0.

+

Case Expectation

A message is echo'ed back to us (with empty payload).

+
+ +

Case 6.1.3

+ Up +

Case Description

Send fragmented text message, 3 fragments, first and last of length 0, middle non-empty.

+

Case Expectation

A message is echo'ed back to us (with payload = payload of middle fragment).

+
+ +

Case 6.2.1

+ Up +

Case Description

Send a valid UTF-8 text message in one fragment.

MESSAGE:
Hello-µ@ßöäüàá-UTF-8!!
48656c6c6f2dc2b540c39fc3b6c3a4c3bcc3a0c3a12d5554462d382121

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.2.2

+ Up +

Case Description

Send a valid UTF-8 text message in two fragments, fragmented on UTF-8 code point boundary.

MESSAGE FRAGMENT 1:
Hello-µ@ßöä
48656c6c6f2dc2b540c39fc3b6c3a4

MESSAGE FRAGMENT 2:
üàá-UTF-8!!
c3bcc3a0c3a12d5554462d382121

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.2.3

+ Up +

Case Description

Send a valid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.

MESSAGE:
Hello-µ@ßöäüàá-UTF-8!!
48656c6c6f2dc2b540c39fc3b6c3a4c3bcc3a0c3a12d5554462d382121

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.2.4

+ Up +

Case Description

Send a valid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.

MESSAGE:
κόσμε
cebae1bdb9cf83cebcceb5

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.3.1

+ Up +

Case Description

Send invalid UTF-8 text message unfragmented.

MESSAGE:
cebae1bdb9cf83cebcceb5eda080656469746564

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.3.2

+ Up +

Case Description

Send invalid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.

MESSAGE:
cebae1bdb9cf83cebcceb5eda080656469746564

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.4.1

+ Up +

Case Description

Send invalid UTF-8 text message in 3 fragments (frames). +First frame payload is valid, then wait, then 2nd frame which contains the payload making the sequence invalid, then wait, then 3rd frame with rest. +Note that PART1 and PART3 are valid UTF-8 in themselves, PART2 is a 0x110000 encoded as in the UTF-8 integer encoding scheme, but the codepoint is invalid (out of range). +

MESSAGE PARTS:
+PART1 = cebae1bdb9cf83cebcceb5
+PART2 = f4908080
+PART3 = 656469746564
+

+

Case Expectation

The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.

+
+ +

Case 6.4.2

+ Up +

Case Description

Same as Case 6.4.1, but in 2nd frame, we send only up to and including the octet making the complete payload invalid. +

MESSAGE PARTS:
+PART1 = cebae1bdb9cf83cebcceb5f4
+PART2 = 90
+PART3 = 8080656469746564
+

+

Case Expectation

The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.

+
+ +

Case 6.4.3

+ Up +

Case Description

Same as Case 6.4.1, but we send message not in 3 frames, but in 3 chops of the same message frame. +

MESSAGE PARTS:
+PART1 = cebae1bdb9cf83cebcceb5
+PART2 = f4908080
+PART3 = 656469746564
+

+

Case Expectation

The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.

+
+ +

Case 6.4.4

+ Up +

Case Description

Same as Case 6.4.2, but we send message not in 3 frames, but in 3 chops of the same message frame. +

MESSAGE PARTS:
+PART1 = cebae1bdb9cf83cebcceb5f4
+PART2 = 90
+PART3 =
+

+

Case Expectation

The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.

+
+ +

Case 6.5.1

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0x68656c6c6f24776f726c64

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.5.2

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0x68656c6c6fc2a2776f726c64

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.5.3

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0x68656c6c6fe282ac776f726c64

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.5.4

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0x68656c6c6ff0a4ada2776f726c64

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.5.5

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xcebae1bdb9cf83cebcceb5

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.6.1

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xce

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.6.2

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xceba

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.6.3

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xcebae1

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.6.4

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xcebae1bd

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.6.5

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xcebae1bdb9

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.6.6

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xcebae1bdb9cf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.6.7

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xcebae1bdb9cf83

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.6.8

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xcebae1bdb9cf83ce

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.6.9

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xcebae1bdb9cf83cebc

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.6.10

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xcebae1bdb9cf83cebcce

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.6.11

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xcebae1bdb9cf83cebcceb5

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.7.1

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0x00

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.7.2

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xc280

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.7.3

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xe0a080

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.7.4

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf0908080

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.8.1

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xf888808080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.8.2

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xfc8480808080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.9.1

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0x7f

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.9.2

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xdfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.9.3

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xefbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.9.4

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf48fbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.10.1

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xf7bfbfbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.10.2

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xfbbfbfbfbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.10.3

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xfdbfbfbfbfbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.11.1

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xed9fbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.11.2

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xee8080

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.11.3

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xefbfbd

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.11.4

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf48fbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.11.5

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xf4908080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.12.1

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0x80

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.12.2

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.12.3

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0x80bf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.12.4

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0x80bf80

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.12.5

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0x80bf80bf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.12.6

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0x80bf80bf80

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.12.7

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0x80bf80bf80bf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.12.8

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0x808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbe

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.13.1

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xc020c120c220c320c420c520c620c720c820c920ca20cb20cc20cd20ce20cf20d020d120d220d320d420d520d620d720d820d920da20db20dc20dd20de20

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.13.2

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xe020e120e220e320e420e520e620e720e820e920ea20eb20ec20ed20ee20

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.13.3

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xf020f120f220f320f420f520f620

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.13.4

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xf820f920fa20

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.13.5

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xfc20

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.14.1

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xc0

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.14.2

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xe080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.14.3

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xf08080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.14.4

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xf8808080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.14.5

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xfc80808080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.14.6

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xdf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.14.7

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xefbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.14.8

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xf7bfbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.14.9

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xfbbfbfbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.14.10

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xfdbfbfbfbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.15.1

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xc0e080f08080f8808080fc80808080dfefbff7bfbffbbfbfbffdbfbfbfbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.16.1

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xfe

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.16.2

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xff

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.16.3

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xfefeffff

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.17.1

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xc0af

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.17.2

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xe080af

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.17.3

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xf08080af

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.17.4

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xf8808080af

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.17.5

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xfc80808080af

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.18.1

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xc1bf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.18.2

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xe09fbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.18.3

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xf08fbfbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.18.4

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xf887bfbfbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.18.5

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xfc83bfbfbfbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.19.1

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xc080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.19.2

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xe08080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.19.3

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xf0808080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.19.4

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xf880808080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.19.5

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xfc8080808080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.20.1

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xeda080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.20.2

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xedadbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.20.3

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xedae80

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.20.4

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xedafbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.20.5

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xedb080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.20.6

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xedbe80

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.20.7

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xedbfbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.21.1

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xeda080edb080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.21.2

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xeda080edbfbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.21.3

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xedadbfedb080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.21.4

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xedadbfedbfbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.21.5

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xedae80edb080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.21.6

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xedae80edbfbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.21.7

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xedafbfedb080

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.21.8

+ Up +

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

Payload: 0xedafbfedbfbf

+

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+
+ +

Case 6.22.1

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xefbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.2

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xefbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.3

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf09fbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.4

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf09fbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.5

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf0afbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.6

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf0afbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.7

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf0bfbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.8

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf0bfbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.9

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf18fbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.10

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf18fbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.11

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf19fbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.12

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf19fbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.13

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf1afbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.14

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf1afbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.15

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf1bfbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.16

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf1bfbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.17

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf28fbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.18

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf28fbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.19

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf29fbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.20

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf29fbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.21

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf2afbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.22

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf2afbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.23

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf2bfbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.24

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf2bfbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.25

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf38fbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.26

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf38fbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.27

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf39fbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.28

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf39fbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.29

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf3afbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.30

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf3afbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.31

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf3bfbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.32

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf3bfbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.33

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf48fbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.22.34

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xf48fbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.23.1

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xefbfb9

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.23.2

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xefbfba

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.23.3

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xefbfbb

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.23.4

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xefbfbc

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.23.5

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xefbfbd

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.23.6

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xefbfbe

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 6.23.7

+ Up +

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

Payload: 0xefbfbf

+

Case Expectation

The message is echo'ed back to us.

+
+ +

Case 7.1.1

+ Up +

Case Description

Send a message followed by a close frame

+

Case Expectation

Echoed message followed by clean close with normal code.

+
+ +

Case 7.1.2

+ Up +

Case Description

Send two close frames

+

Case Expectation

Clean close with normal code. Second close frame ignored.

+
+ +

Case 7.1.3

+ Up +

Case Description

Send a ping after close message

+

Case Expectation

Clean close with normal code, no pong.

+
+ +

Case 7.1.4

+ Up +

Case Description

Send text message after sending a close frame.

+

Case Expectation

Clean close with normal code. Text message ignored.

+
+ +

Case 7.1.5

+ Up +

Case Description

Send message fragment1 followed by close then fragment

+

Case Expectation

Clean close with normal code.

+
+ +

Case 7.1.6

+ Up +

Case Description

Send 256K message followed by close then a ping

+

Case Expectation

Case outcome depends on implementation defined close behavior. Message and close frame are sent back to back. If the close frame is processed before the text message write is complete (as can happen in asynchronous processing models) the close frame is processed first and the text message may not be received or may only be partially recieved.

+
+ +

Case 7.3.1

+ Up +

Case Description

Send a close frame with payload length 0 (no close code, no close reason)

+

Case Expectation

Clean close with normal code.

+
+ +

Case 7.3.2

+ Up +

Case Description

Send a close frame with payload length 1

+

Case Expectation

Clean close with protocol error or drop TCP.

+
+ +

Case 7.3.3

+ Up +

Case Description

Send a close frame with payload length 2 (regular close with a code)

+

Case Expectation

Clean close with normal code.

+
+ +

Case 7.3.4

+ Up +

Case Description

Send a close frame with close code and close reason

+

Case Expectation

Clean close with normal code.

+
+ +

Case 7.3.5

+ Up +

Case Description

Send a close frame with close code and close reason of maximum length (123)

+

Case Expectation

Clean close with normal code.

+
+ +

Case 7.3.6

+ Up +

Case Description

Send a close frame with close code and close reason which is too long (124) - total frame payload 126 octets

+

Case Expectation

Clean close with protocol error code or dropped TCP connection.

+
+ +

Case 7.5.1

+ Up +

Case Description

Send a close frame with invalid UTF8 payload

+

Case Expectation

Clean close with protocol error or invalid utf8 code or dropped TCP.

+
+ +

Case 7.7.1

+ Up +

Case Description

Send close with valid close code 1000

+

Case Expectation

Clean close with normal or echoed code

+
+ +

Case 7.7.2

+ Up +

Case Description

Send close with valid close code 1001

+

Case Expectation

Clean close with normal or echoed code

+
+ +

Case 7.7.3

+ Up +

Case Description

Send close with valid close code 1002

+

Case Expectation

Clean close with normal or echoed code

+
+ +

Case 7.7.4

+ Up +

Case Description

Send close with valid close code 1003

+

Case Expectation

Clean close with normal or echoed code

+
+ +

Case 7.7.5

+ Up +

Case Description

Send close with valid close code 1007

+

Case Expectation

Clean close with normal or echoed code

+
+ +

Case 7.7.6

+ Up +

Case Description

Send close with valid close code 1008

+

Case Expectation

Clean close with normal or echoed code

+
+ +

Case 7.7.7

+ Up +

Case Description

Send close with valid close code 1009

+

Case Expectation

Clean close with normal or echoed code


- -

Case 4.2.1

+ +

Case 7.7.8

Up -

Case Description

Send frame with reserved control Opcode = 11.

-

Case Expectation

The connection is failed immediately.

+

Case Description

Send close with valid close code 1010

+

Case Expectation

Clean close with normal or echoed code


- -

Case 4.2.2

+ +

Case 7.7.9

Up -

Case Description

Send frame with reserved control Opcode = 12 and non-empty payload.

-

Case Expectation

The connection is failed immediately.

+

Case Description

Send close with valid close code 1011

+

Case Expectation

Clean close with normal or echoed code


- -

Case 4.2.3

+ +

Case 7.7.10

Up -

Case Description

Send small text message, then send frame with reserved control Opcode = 13, then send Ping.

-

Case Expectation

Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.

+

Case Description

Send close with valid close code 3000

+

Case Expectation

Clean close with normal or echoed code


- -

Case 4.2.4

+ +

Case 7.7.11

Up -

Case Description

Send small text message, then send frame with reserved control Opcode = 14 and non-empty payload, then send Ping.

-

Case Expectation

Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.

+

Case Description

Send close with valid close code 3999

+

Case Expectation

Clean close with normal or echoed code


- -

Case 4.2.5

+ +

Case 7.7.12

Up -

Case Description

Send small text message, then send frame with reserved control Opcode = 15 and non-empty payload, then send Ping.

-

Case Expectation

Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.

+

Case Description

Send close with valid close code 4000

+

Case Expectation

Clean close with normal or echoed code


- -

Case 5.1

+ +

Case 7.7.13

Up -

Case Description

Send Ping fragmented into 2 fragments.

-

Case Expectation

Connection is failed immediately, since control message MUST NOT be fragmented.

+

Case Description

Send close with valid close code 4999

+

Case Expectation

Clean close with normal or echoed code


- -

Case 5.2

+ +

Case 7.9.1

Up -

Case Description

Send Pong fragmented into 2 fragments.

-

Case Expectation

Connection is failed immediately, since control message MUST NOT be fragmented.

+

Case Description

Send close with invalid close code 0

+

Case Expectation

Clean close with protocol error code or drop TCP

+
+ +

Case 7.9.2

+ Up +

Case Description

Send close with invalid close code 999

+

Case Expectation

Clean close with protocol error code or drop TCP

+
+ +

Case 7.9.3

+ Up +

Case Description

Send close with invalid close code 1004

+

Case Expectation

Clean close with protocol error code or drop TCP

+
+ +

Case 7.9.4

+ Up +

Case Description

Send close with invalid close code 1005

+

Case Expectation

Clean close with protocol error code or drop TCP

+
+ +

Case 7.9.5

+ Up +

Case Description

Send close with invalid close code 1006

+

Case Expectation

Clean close with protocol error code or drop TCP

+
+ +

Case 7.9.6

+ Up +

Case Description

Send close with invalid close code 1014

+

Case Expectation

Clean close with protocol error code or drop TCP

+
+ +

Case 7.9.7

+ Up +

Case Description

Send close with invalid close code 1015

+

Case Expectation

Clean close with protocol error code or drop TCP

+
+ +

Case 7.9.8

+ Up +

Case Description

Send close with invalid close code 1016

+

Case Expectation

Clean close with protocol error code or drop TCP

+
+ +

Case 7.9.9

+ Up +

Case Description

Send close with invalid close code 1100

+

Case Expectation

Clean close with protocol error code or drop TCP

+
+ +

Case 7.9.10

+ Up +

Case Description

Send close with invalid close code 2000

+

Case Expectation

Clean close with protocol error code or drop TCP

+
+ +

Case 7.9.11

+ Up +

Case Description

Send close with invalid close code 2999

+

Case Expectation

Clean close with protocol error code or drop TCP

+
+ +

Case 7.13.1

+ Up +

Case Description

Send close with close code 5000

+

Case Expectation

Actual events are undefined by the spec.

+
+ +

Case 7.13.2

+ Up +

Case Description

Send close with close code 65536

+

Case Expectation

Actual events are undefined by the spec.

+
+ +

Case 9.1.1

+ Up +

Case Description

Send text message message with payload of length 64 * 2**10 (64k).

+

Case Expectation

Receive echo'ed text message (with payload as sent).

+
+ +

Case 9.1.2

+ Up +

Case Description

Send text message message with payload of length 256 * 2**10 (256k).

+

Case Expectation

Receive echo'ed text message (with payload as sent).

+
+ +

Case 9.1.3

+ Up +

Case Description

Send text message message with payload of length 1 * 2**20 (1M).

+

Case Expectation

Receive echo'ed text message (with payload as sent).

+
+ +

Case 9.1.4

+ Up +

Case Description

Send text message message with payload of length 4 * 2**20 (4M).

+

Case Expectation

Receive echo'ed text message (with payload as sent).

+
+ +

Case 9.1.5

+ Up +

Case Description

Send text message message with payload of length 8 * 2**20 (8M).

+

Case Expectation

Receive echo'ed text message (with payload as sent).

+
+ +

Case 9.1.6

+ Up +

Case Description

Send text message message with payload of length 16 * 2**20 (16M).

+

Case Expectation

Receive echo'ed text message (with payload as sent).

+
+ +

Case 9.2.1

+ Up +

Case Description

Send binary message message with payload of length 64 * 2**10 (64k).

+

Case Expectation

Receive echo'ed binary message (with payload as sent).

+
+ +

Case 9.2.2

+ Up +

Case Description

Send binary message message with payload of length 256 * 2**10 (256k).

+

Case Expectation

Receive echo'ed binary message (with payload as sent).

+
+ +

Case 9.2.3

+ Up +

Case Description

Send binary message message with payload of length 1 * 2**20 (1M).

+

Case Expectation

Receive echo'ed binary message (with payload as sent).

+
+ +

Case 9.2.4

+ Up +

Case Description

Send binary message message with payload of length 4 * 2**20 (4M).

+

Case Expectation

Receive echo'ed binary message (with payload as sent).

+
+ +

Case 9.2.5

+ Up +

Case Description

Send binary message message with payload of length 8 * 2**20 (16M).

+

Case Expectation

Receive echo'ed binary message (with payload as sent).

+
+ +

Case 9.2.6

+ Up +

Case Description

Send binary message message with payload of length 16 * 2**20 (16M).

+

Case Expectation

Receive echo'ed binary message (with payload as sent).

+
+ +

Case 9.3.1

+ Up +

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64.

+

Case Expectation

Receive echo'ed text message (with payload as sent).

+
+ +

Case 9.3.2

+ Up +

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256.

+

Case Expectation

Receive echo'ed text message (with payload as sent).

+
+ +

Case 9.3.3

+ Up +

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1k.

+

Case Expectation

Receive echo'ed text message (with payload as sent).

+
+ +

Case 9.3.4

+ Up +

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4k.

+

Case Expectation

Receive echo'ed text message (with payload as sent).

+
+ +

Case 9.3.5

+ Up +

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 16k.

+

Case Expectation

Receive echo'ed text message (with payload as sent).

+
+ +

Case 9.3.6

+ Up +

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64k.

+

Case Expectation

Receive echo'ed text message (with payload as sent).

+
+ +

Case 9.3.7

+ Up +

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256k.

+

Case Expectation

Receive echo'ed text message (with payload as sent).

+
+ +

Case 9.3.8

+ Up +

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1M.

+

Case Expectation

Receive echo'ed text message (with payload as sent).


- -

Case 5.3

+ +

Case 9.3.9

Up -

Case Description

Send text Message fragmented into 2 fragments.

-

Case Expectation

Message is processed and echo'ed back to us.

+

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (8M). Sent out in fragments of 4M.

+

Case Expectation

Receive echo'ed text message (with payload as sent).


- -

Case 5.4

+ +

Case 9.4.1

Up -

Case Description

Send text Message fragmented into 2 fragments, octets are sent in frame-wise chops.

-

Case Expectation

Message is processed and echo'ed back to us.

+

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64.

+

Case Expectation

Receive echo'ed binary message (with payload as sent).


- -

Case 5.5

+ +

Case 9.4.2

Up -

Case Description

Send text Message fragmented into 2 fragments, octets are sent in octet-wise chops.

-

Case Expectation

Message is processed and echo'ed back to us.

+

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256.

+

Case Expectation

Receive echo'ed binary message (with payload as sent).


- -

Case 5.6

+ +

Case 9.4.3

Up -

Case Description

Send text Message fragmented into 2 fragments, one ping with payload in-between.

-

Case Expectation

A pong is received, then the message is echo'ed back to us.

+

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1k.

+

Case Expectation

Receive echo'ed binary message (with payload as sent).


- -

Case 5.7

+ +

Case 9.4.4

Up -

Case Description

Send text Message fragmented into 2 fragments, one ping with payload in-between. Octets are sent in frame-wise chops.

-

Case Expectation

A pong is received, then the message is echo'ed back to us.

+

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4k.

+

Case Expectation

Receive echo'ed binary message (with payload as sent).


- -

Case 5.8

+ +

Case 9.4.5

Up -

Case Description

Send text Message fragmented into 2 fragments, one ping with payload in-between. Octets are sent in octet-wise chops.

-

Case Expectation

A pong is received, then the message is echo'ed back to us.

+

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 16k.

+

Case Expectation

Receive echo'ed binary message (with payload as sent).


- -

Case 5.9

+ +

Case 9.4.6

Up -

Case Description

Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in one chop.

-

Case Expectation

The connection is failed immediately, since there is no message to continue.

+

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64k.

+

Case Expectation

Receive echo'ed binary message (with payload as sent).


- -

Case 5.10

+ +

Case 9.4.7

Up -

Case Description

Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in per-frame chops.

-

Case Expectation

The connection is failed immediately, since there is no message to continue.

+

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256k.

+

Case Expectation

Receive echo'ed binary message (with payload as sent).


- -

Case 5.11

+ +

Case 9.4.8

Up -

Case Description

Send unfragmented Text Message after Continuation Frame with FIN = true, where there is nothing to continue, sent in octet-wise chops.

-

Case Expectation

The connection is failed immediately, since there is no message to continue.

+

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1M.

+

Case Expectation

Receive echo'ed binary message (with payload as sent).


- -

Case 5.12

+ +

Case 9.4.9

Up -

Case Description

Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in one chop.

-

Case Expectation

The connection is failed immediately, since there is no message to continue.

+

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4M.

+

Case Expectation

Receive echo'ed binary message (with payload as sent).


- -

Case 5.13

+ +

Case 9.5.1

Up -

Case Description

Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in per-frame chops.

-

Case Expectation

The connection is failed immediately, since there is no message to continue.

+

Case Description

Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 64 octets.

+

Case Expectation

Receive echo'ed text message (with payload as sent).


- -

Case 5.14

+ +

Case 9.5.2

Up -

Case Description

Send unfragmented Text Message after Continuation Frame with FIN = false, where there is nothing to continue, sent in octet-wise chops.

-

Case Expectation

The connection is failed immediately, since there is no message to continue.

+

Case Description

Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 128 octets.

+

Case Expectation

Receive echo'ed text message (with payload as sent).


- -

Case 5.15

+ +

Case 9.5.3

Up -

Case Description

Send text Message fragmented into 2 fragments, then Continuation Frame with FIN = false where there is nothing to continue, then unfragmented Text Message, all sent in one chop.

-

Case Expectation

The connection is failed immediately, since there is no message to continue.

+

Case Description

Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 256 octets.

+

Case Expectation

Receive echo'ed text message (with payload as sent).


- -

Case 5.16

+ +

Case 9.5.4

Up -

Case Description

Repeated 2x: Continuation Frame with FIN = false (where there is nothing to continue), then text Message fragmented into 2 fragments.

-

Case Expectation

The connection is failed immediately, since there is no message to continue.

+

Case Description

Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 512 octets.

+

Case Expectation

Receive echo'ed text message (with payload as sent).


- -

Case 5.17

+ +

Case 9.5.5

Up -

Case Description

Repeated 2x: Continuation Frame with FIN = true (where there is nothing to continue), then text Message fragmented into 2 fragments.

-

Case Expectation

The connection is failed immediately, since there is no message to continue.

+

Case Description

Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 1024 octets.

+

Case Expectation

Receive echo'ed text message (with payload as sent).


- -

Case 5.18

+ +

Case 9.5.6

Up -

Case Description

Send text Message fragmented into 2 fragments, with both frame opcodes set to text, sent in one chop.

-

Case Expectation

The connection is failed immediately, since all data frames after the initial data frame must have opcode 0.

+

Case Description

Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 2048 octets.

+

Case Expectation

Receive echo'ed text message (with payload as sent).


- -

Case 5.19

+ +

Case 9.6.1

Up -

Case Description

A fragmented text message is sent in multiple frames. After - sending the first 2 frames of the text message, a Ping is sent. Then we wait 1s, - then we send 2 more text fragments, another Ping and then the final text fragment. - Everything is legal.

-

Case Expectation

The peer immediately answers the first Ping before - it has received the last text message fragment. The peer pong's back the Ping's - payload exactly, and echo's the payload of the fragmented message back to us.

+

Case Description

Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 64 octets.

+

Case Expectation

Receive echo'ed binary message (with payload as sent).


- -

Case 5.20

+ +

Case 9.6.2

Up -

Case Description

Same as Case 5.19, but send all frames with SYNC = True. - Note, this does not change the octets sent in any way, only how the stream - is chopped up on the wire.

-

Case Expectation

Same as Case 5.19. Implementations must be agnostic to how - octet stream is chopped up on wire (must be TCP clean).

+

Case Description

Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 128 octets.

+

Case Expectation

Receive echo'ed text message (with payload as sent).


- -

Case 6.1.1

+ +

Case 9.6.3

Up -

Case Description

Send text message of length 0.

-

Case Expectation

A message is echo'ed back to us (with empty payload).

+

Case Description

Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 256 octets.

+

Case Expectation

Receive echo'ed text message (with payload as sent).


- -

Case 6.1.2

+ +

Case 9.6.4

Up -

Case Description

Send fragmented text message, 3 fragments each of length 0.

-

Case Expectation

A message is echo'ed back to us (with empty payload).

+

Case Description

Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 512 octets.

+

Case Expectation

Receive echo'ed text message (with payload as sent).


- -

Case 6.1.3

+ +

Case 9.6.5

Up -

Case Description

Send fragmented text message, 3 fragments, first and last of length 0, middle non-empty.

-

Case Expectation

A message is echo'ed back to us (with payload = payload of middle fragment).

+

Case Description

Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 1024 octets.

+

Case Expectation

Receive echo'ed text message (with payload as sent).


- -

Case 6.2.1

+ +

Case 9.6.6

Up -

Case Description

Send a valid UTF-8 text message in one fragment.

MESSAGE:
Hello-µ@ßöäüàá-UTF-8!!
48656c6c6f2dc2b540c39fc3b6c3a4c3bcc3a0c3a12d5554462d382121

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 2048 octets.

+

Case Expectation

Receive echo'ed text message (with payload as sent).


- -

Case 6.2.2

+ +

Case 9.7.1

Up -

Case Description

Send a valid UTF-8 text message in two fragments, fragmented on UTF-8 code point boundary.

MESSAGE FRAGMENT 1:
Hello-µ@ßöä
48656c6c6f2dc2b540c39fc3b6c3a4

MESSAGE FRAGMENT 2:
üàá-UTF-8!!
c3bcc3a0c3a12d5554462d382121

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 text messages of payload size 0 to measure implementation/network RTT (round trip time) / latency.

+

Case Expectation

Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.2.3

+ +

Case 9.7.2

Up -

Case Description

Send a valid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.

MESSAGE:
Hello-µ@ßöäüàá-UTF-8!!
48656c6c6f2dc2b540c39fc3b6c3a4c3bcc3a0c3a12d5554462d382121

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 text messages of payload size 16 to measure implementation/network RTT (round trip time) / latency.

+

Case Expectation

Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.2.4

+ +

Case 9.7.3

Up -

Case Description

Send a valid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.

MESSAGE:
κόσμε
cebae1bdb9cf83cebcceb5

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 text messages of payload size 64 to measure implementation/network RTT (round trip time) / latency.

+

Case Expectation

Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.3.1

+ +

Case 9.7.4

Up -

Case Description

Send invalid UTF-8 text message unfragmented.

MESSAGE:
κόσμεedited
cebae1bdb9cf83cebcceb5eda080656469746564

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 text messages of payload size 256 to measure implementation/network RTT (round trip time) / latency.

+

Case Expectation

Receive echo'ed text messages (with payload as sent). Timeout case after 120 secs.


- -

Case 6.3.2

+ +

Case 9.7.5

Up -

Case Description

Send invalid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.

MESSAGE:
κόσμεedited
cebae1bdb9cf83cebcceb5eda080656469746564

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 text messages of payload size 1024 to measure implementation/network RTT (round trip time) / latency.

+

Case Expectation

Receive echo'ed text messages (with payload as sent). Timeout case after 240 secs.


- -

Case 6.4.1

+ +

Case 9.7.6

Up -

Case Description

Send invalid UTF-8 text message in 3 fragments (frames). -First frame payload is valid, then wait, then 2nd frame which contains the payload making the sequence invalid, then wait, then 3rd frame with rest. -Note that PART1 and PART3 are valid UTF-8 in themselves, PART2 is a 0x11000 encoded as in the UTF-8 integer encoding scheme, but the codepoint is invalid (out of range). -

MESSAGE PARTS:
-PART1 = κόσμε (cebae1bdb9cf83cebcceb5)
-PART2 = (f4908080)
-PART3 = edited (656469746564)
-

-

Case Expectation

The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.

+

Case Description

Send 1000 text messages of payload size 4096 to measure implementation/network RTT (round trip time) / latency.

+

Case Expectation

Receive echo'ed text messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.4.2

+ +

Case 9.8.1

Up -

Case Description

Same as Case 6.4.1, but in 2nd frame, we send only up to and including the octet making the complete payload invalid. -

MESSAGE PARTS:
-PART1 = κόσμε (cebae1bdb9cf83cebcceb5f4)
-PART2 = (90)
-PART3 = edited (8080656469746564)
-

-

Case Expectation

The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.

+

Case Description

Send 1000 binary messages of payload size 0 to measure implementation/network RTT (round trip time) / latency.

+

Case Expectation

Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.4.3

+ +

Case 9.8.2

Up -

Case Description

Same as Case 6.4.1, but we send message not in 3 frames, but in 3 chops of the same message frame. -

MESSAGE PARTS:
-PART1 = κόσμε (cebae1bdb9cf83cebcceb5)
-PART2 = (f4908080)
-PART3 = edited (656469746564)
-

-

Case Expectation

The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.

+

Case Description

Send 1000 binary messages of payload size 16 to measure implementation/network RTT (round trip time) / latency.

+

Case Expectation

Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.4.4

+ +

Case 9.8.3

Up -

Case Description

Same as Case 6.4.2, but we send message not in 3 frames, but in 3 chops of the same message frame. -

MESSAGE PARTS:
-PART1 = κόσμε (cebae1bdb9cf83cebcceb5f4)
-PART2 = (90)
-PART3 = ()
-

-

Case Expectation

The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.

+

Case Description

Send 1000 binary messages of payload size 64 to measure implementation/network RTT (round trip time) / latency.

+

Case Expectation

Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.5.1

+ +

Case 9.8.4

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
κόσμε
cebae1bdb9cf83cebcceb5

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 binary messages of payload size 256 to measure implementation/network RTT (round trip time) / latency.

+

Case Expectation

Receive echo'ed binary messages (with payload as sent). Timeout case after 120 secs.


- -

Case 6.6.1

+ +

Case 9.8.5

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

ce

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 binary messages of payload size 1024 to measure implementation/network RTT (round trip time) / latency.

+

Case Expectation

Receive echo'ed binary messages (with payload as sent). Timeout case after 240 secs.


- -

Case 6.6.2

+ +

Case 9.8.6

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
κ
ceba

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 binary messages of payload size 4096 to measure implementation/network RTT (round trip time) / latency.

+

Case Expectation

Receive echo'ed binary messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.6.3

+ +

Case 10.1.1

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:
κ
cebae1

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send text message with payload of length 65536 auto-fragmented with autoFragmentSize = 1300.

+

Case Expectation

Receive echo'ed text message (with payload as sent and transmitted frame counts as expected). Clean close with normal code.


- -

Case 6.6.4

+ +

Case 12.1.1

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:
κ
cebae1bd

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 16, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.6.5

+ +

Case 12.1.2

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
κό
cebae1bdb9

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 64, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.6.6

+ +

Case 12.1.3

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:
κό
cebae1bdb9cf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 256, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 120 secs.


- -

Case 6.6.7

+ +

Case 12.1.4

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
κόσ
cebae1bdb9cf83

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 1024, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 240 secs.


- -

Case 6.6.8

+ +

Case 12.1.5

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:
κόσ
cebae1bdb9cf83ce

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 4096, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.6.9

+ +

Case 12.1.6

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
κόσμ
cebae1bdb9cf83cebc

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.6.10

+ +

Case 12.1.7

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:
κόσμ
cebae1bdb9cf83cebcce

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.6.11

+ +

Case 12.1.8

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
κόσμε
cebae1bdb9cf83cebcceb5

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.7.1

+ +

Case 12.1.9

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:

00

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.7.2

+ +

Case 12.1.10

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
€
c280

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.7.3

+ +

Case 12.1.11

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:

e0a080

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.7.4

+ +

Case 12.1.12

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
𐀀
f0908080

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.8.1

+ +

Case 12.1.13

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

f888808080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.8.2

+ +

Case 12.1.14

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

fc8480808080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.9.1

+ +

Case 12.1.15

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:

7f

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.9.2

+ +

Case 12.1.16

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
߿
dfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 1024 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.9.3

+ +

Case 12.1.17

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
￿
efbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 4096 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.9.4

+ +

Case 12.1.18

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
􏿿
f48fbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 32768 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.10.1

+ +

Case 12.2.1

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

f7bfbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 16, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.10.2

+ +

Case 12.2.2

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

fbbfbfbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 64, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.10.3

+ +

Case 12.2.3

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

fdbfbfbfbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 256, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 120 secs.


- -

Case 6.11.1

+ +

Case 12.2.4

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:

ed9fbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 1024, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 240 secs.


- -

Case 6.11.2

+ +

Case 12.2.5

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:

ee8080

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 4096, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.11.3

+ +

Case 12.2.6

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:

efbfbd

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.11.4

+ +

Case 12.2.7

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
􏿿
f48fbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.11.5

+ +

Case 12.2.8

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

f4908080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.12.1

+ +

Case 12.2.9

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

80

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.12.2

+ +

Case 12.2.10

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

bf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.12.3

+ +

Case 12.2.11

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

80bf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.12.4

+ +

Case 12.2.12

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

80bf80

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.12.5

+ +

Case 12.2.13

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

80bf80bf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.12.6

+ +

Case 12.2.14

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

80bf80bf80

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.12.7

+ +

Case 12.2.15

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

80bf80bf80bf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.12.8

+ +

Case 12.2.16

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbe

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 1024 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.13.1

+ +

Case 12.2.17

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

c020c120c220c320c420c520c620c720c820c920ca20cb20cc20cd20ce20cf20d020d120d220d320d420d520d620d720d820d920da20db20dc20dd20de20

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 4096 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.13.2

+ +

Case 12.2.18

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

e020e120e220e320e420e520e620e720e820e920ea20eb20ec20ed20ee20

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 32768 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.13.3

+ +

Case 12.3.1

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

f020f120f220f320f420f520f620

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 16, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.13.4

+ +

Case 12.3.2

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

f820f920fa20

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 64, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.13.5

+ +

Case 12.3.3

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

fc20

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 256, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 120 secs.


- -

Case 6.14.1

+ +

Case 12.3.4

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

c0

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 1024, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 240 secs.


- -

Case 6.14.2

+ +

Case 12.3.5

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

e080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 4096, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.14.3

+ +

Case 12.3.6

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

f08080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.14.4

+ +

Case 12.3.7

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

f8808080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.14.5

+ +

Case 12.3.8

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

fc80808080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.14.6

+ +

Case 12.3.9

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

df

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.14.7

+ +

Case 12.3.10

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

efbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.14.8

+ +

Case 12.3.11

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

f7bfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.14.9

+ +

Case 12.3.12

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

fbbfbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.14.10

+ +

Case 12.3.13

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

fdbfbfbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.15.1

+ +

Case 12.3.14

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

c0e080f08080f8808080fc80808080dfefbff7bfbffbbfbfbffdbfbfbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.16.1

+ +

Case 12.3.15

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

fe

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.16.2

+ +

Case 12.3.16

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

ff

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 1024 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.16.3

+ +

Case 12.3.17

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

fefeffff

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 4096 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.17.1

+ +

Case 12.3.18

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

c0af

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 32768 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.17.2

+ +

Case 12.4.1

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

e080af

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 16, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.17.3

+ +

Case 12.4.2

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

f08080af

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 64, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.17.4

+ +

Case 12.4.3

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

f8808080af

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 256, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 120 secs.


- -

Case 6.17.5

+ +

Case 12.4.4

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

fc80808080af

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 1024, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 240 secs.


- -

Case 6.18.1

+ +

Case 12.4.5

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

c1bf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 4096, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.18.2

+ +

Case 12.4.6

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

e09fbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.18.3

+ +

Case 12.4.7

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

f08fbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.18.4

+ +

Case 12.4.8

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

f887bfbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.18.5

+ +

Case 12.4.9

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

fc83bfbfbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.19.1

+ +

Case 12.4.10

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

c080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.19.2

+ +

Case 12.4.11

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

e08080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.19.3

+ +

Case 12.4.12

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

f0808080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.19.4

+ +

Case 12.4.13

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

f880808080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.19.5

+ +

Case 12.4.14

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

fc8080808080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.20.1

+ +

Case 12.4.15

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

eda080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.20.2

+ +

Case 12.4.16

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edadbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 1024 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.20.3

+ +

Case 12.4.17

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edae80

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 4096 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.20.4

+ +

Case 12.4.18

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edafbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 32768 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.20.5

+ +

Case 12.5.1

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edb080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 16, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.20.6

+ +

Case 12.5.2

+ Up +

Case Description

Send 1000 compressed messages each of payload size 64, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.

+
+ +

Case 12.5.3

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edbe80

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 256, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 120 secs.


- -

Case 6.20.7

+ +

Case 12.5.4

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 1024, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 240 secs.


- -

Case 6.21.1

+ +

Case 12.5.5

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

eda080edb080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 4096, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.21.2

+ +

Case 12.5.6

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

eda080edbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.21.3

+ +

Case 12.5.7

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edadbfedb080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.21.4

+ +

Case 12.5.8

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edadbfedbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.21.5

+ +

Case 12.5.9

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edae80edb080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.21.6

+ +

Case 12.5.10

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edae80edbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 0 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.21.7

+ +

Case 12.5.11

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edafbfedb080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.21.8

+ +

Case 12.5.12

Up -

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edafbfedbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.1

+ +

Case 12.5.13

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:

efbfbe

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.2

+ +

Case 12.5.14

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
￿
efbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.3

+ +

Case 12.5.15

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
🿾
f09fbfbe

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 256 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.4

+ +

Case 12.5.16

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
🿿
f09fbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 1024 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.5

+ +

Case 12.5.17

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
𯿾
f0afbfbe

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 4096 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.6

+ +

Case 12.5.18

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
𯿿
f0afbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 32768 octets. Use default permessage-deflate offer.

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.7

+ +

Case 13.1.1

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
𿿾
f0bfbfbe

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 16, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.22.8

+ +

Case 13.1.2

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
𿿿
f0bfbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 64, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.22.9

+ +

Case 13.1.3

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
񏿾
f18fbfbe

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 256, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 120 secs.


- -

Case 6.22.10

+ +

Case 13.1.4

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
񏿿
f18fbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 1024, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 240 secs.


- -

Case 6.22.11

+ +

Case 13.1.5

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
񟿾
f19fbfbe

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 4096, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.12

+ +

Case 13.1.6

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
񟿿
f19fbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.13

+ +

Case 13.1.7

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
񯿾
f1afbfbe

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.14

+ +

Case 13.1.8

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
񯿿
f1afbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.15

+ +

Case 13.1.9

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
񿿾
f1bfbfbe

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.16

+ +

Case 13.1.10

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
񿿿
f1bfbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.17

+ +

Case 13.1.11

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
򏿾
f28fbfbe

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.18

+ +

Case 13.1.12

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
򏿿
f28fbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.19

+ +

Case 13.1.13

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
򟿾
f29fbfbe

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.20

+ +

Case 13.1.14

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
򟿿
f29fbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.21

+ +

Case 13.1.15

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
򯿾
f2afbfbe

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.22

+ +

Case 13.1.16

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
򯿿
f2afbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 1024 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.23

+ +

Case 13.1.17

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
򿿾
f2bfbfbe

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 4096 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.24

+ +

Case 13.1.18

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
򿿿
f2bfbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 32768 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.25

+ +

Case 13.2.1

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
󏿾
f38fbfbe

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 16, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.22.26

+ +

Case 13.2.2

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
󏿿
f38fbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 64, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 6.22.27

+ +

Case 13.2.3

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
󟿾
f39fbfbe

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 256, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 120 secs.


- -

Case 6.22.28

+ +

Case 13.2.4

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
󟿿
f39fbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 1024, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 240 secs.


- -

Case 6.22.29

+ +

Case 13.2.5

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
󯿾
f3afbfbe

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 4096, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.30

+ +

Case 13.2.6

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
󯿿
f3afbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.31

+ +

Case 13.2.7

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
󿿾
f3bfbfbe

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.32

+ +

Case 13.2.8

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
󿿿
f3bfbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.33

+ +

Case 13.2.9

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
􏿾
f48fbfbe

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.22.34

+ +

Case 13.2.10

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
􏿿
f48fbfbf

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 6.23.1

+ +

Case 13.2.11

Up -

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:

efbfbd

-

Case Expectation

The message is echo'ed back to us.

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.1.1

+ +

Case 13.2.12

Up -

Case Description

Send a message followed by a close frame

-

Case Expectation

Echoed message followed by clean close with normal code.

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.1.2

+ +

Case 13.2.13

Up -

Case Description

Send two close frames

-

Case Expectation

Clean close with normal code. Second close frame ignored.

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.1.3

+ +

Case 13.2.14

Up -

Case Description

Send a ping after close message

-

Case Expectation

Clean close with normal code, no pong.

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.1.4

+ +

Case 13.2.15

Up -

Case Description

Send text message after sending a close frame.

-

Case Expectation

Clean close with normal code. Text message ignored.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.1.5

+ +

Case 13.2.16

Up -

Case Description

Send message fragment1 followed by close then fragment

-

Case Expectation

Clean close with normal code.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 1024 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.1.6

+ +

Case 13.2.17

Up -

Case Description

Send 256K message followed by close then a ping

-

Case Expectation

Case outcome depends on implimentation defined close behavior. Message and close frame are sent back to back. If the close frame is processed before the text message write is complete (as can happen in asyncronous processing models) the close frame is processed first and the text message may not be recieved or may only be partially recieved.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 4096 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.3.1

+ +

Case 13.2.18

Up -

Case Description

Send a close frame with payload length 0 (no close code, no close reason)

-

Case Expectation

Clean close with normal code.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 32768 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.3.2

+ +

Case 13.3.1

Up -

Case Description

Send a close frame with payload length 1

-

Case Expectation

Clean close with protocol error or drop TCP.

+

Case Description

Send 1000 compressed messages each of payload size 16, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 7.3.3

+ +

Case 13.3.2

Up -

Case Description

Send a close frame with payload length 2 (regular close with a code)

-

Case Expectation

Clean close with normal code.

+

Case Description

Send 1000 compressed messages each of payload size 64, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 7.3.4

+ +

Case 13.3.3

Up -

Case Description

Send a close frame with close code and close reason

-

Case Expectation

Clean close with normal code.

+

Case Description

Send 1000 compressed messages each of payload size 256, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 120 secs.


- -

Case 7.3.5

+ +

Case 13.3.4

Up -

Case Description

Send a close frame with close code and close reason of maximum length (123)

-

Case Expectation

Clean close with normal code.

+

Case Description

Send 1000 compressed messages each of payload size 1024, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 240 secs.


- -

Case 7.3.6

+ +

Case 13.3.5

Up -

Case Description

Send a close frame with close code and close reason which is too long (124) - total frame payload 126 octets

-

Case Expectation

Clean close with protocol error code or dropped TCP connection.

+

Case Description

Send 1000 compressed messages each of payload size 4096, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.5.1

+ +

Case 13.3.6

Up -

Case Description

Send a close frame with invalid UTF8 payload

-

Case Expectation

Clean close with protocol error or invalid utf8 code or dropped TCP.

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.7.1

+ +

Case 13.3.7

Up -

Case Description

Send close with valid close code 1000

-

Case Expectation

Clean close with normal or echoed code

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.7.2

+ +

Case 13.3.8

Up -

Case Description

Send close with valid close code 1001

-

Case Expectation

Clean close with normal or echoed code

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.7.3

+ +

Case 13.3.9

Up -

Case Description

Send close with valid close code 1002

-

Case Expectation

Clean close with normal or echoed code

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.7.4

+ +

Case 13.3.10

Up -

Case Description

Send close with valid close code 1003

-

Case Expectation

Clean close with normal or echoed code

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.7.5

+ +

Case 13.3.11

Up -

Case Description

Send close with valid close code 1007

-

Case Expectation

Clean close with normal or echoed code

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.7.6

+ +

Case 13.3.12

Up -

Case Description

Send close with valid close code 1008

-

Case Expectation

Clean close with normal or echoed code

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.7.7

+ +

Case 13.3.13

Up -

Case Description

Send close with valid close code 1009

-

Case Expectation

Clean close with normal or echoed code

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.7.8

+ +

Case 13.3.14

Up -

Case Description

Send close with valid close code 1010

-

Case Expectation

Clean close with normal or echoed code

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.7.9

+ +

Case 13.3.15

Up -

Case Description

Send close with valid close code 1011

-

Case Expectation

Clean close with normal or echoed code

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.7.10

+ +

Case 13.3.16

Up -

Case Description

Send close with valid close code 3000

-

Case Expectation

Clean close with normal or echoed code

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 1024 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.7.11

+ +

Case 13.3.17

Up -

Case Description

Send close with valid close code 3999

-

Case Expectation

Clean close with normal or echoed code

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 4096 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.7.12

+ +

Case 13.3.18

Up -

Case Description

Send close with valid close code 4000

-

Case Expectation

Clean close with normal or echoed code

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 32768 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.7.13

+ +

Case 13.4.1

Up -

Case Description

Send close with valid close code 4999

-

Case Expectation

Clean close with normal or echoed code

+

Case Description

Send 1000 compressed messages each of payload size 16, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 7.9.1

+ +

Case 13.4.2

Up -

Case Description

Send close with invalid close code 0

-

Case Expectation

Clean close with protocol error code or drop TCP

+

Case Description

Send 1000 compressed messages each of payload size 64, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 7.9.2

+ +

Case 13.4.3

Up -

Case Description

Send close with invalid close code 999

-

Case Expectation

Clean close with protocol error code or drop TCP

+

Case Description

Send 1000 compressed messages each of payload size 256, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 120 secs.


- -

Case 7.9.3

+ +

Case 13.4.4

Up -

Case Description

Send close with invalid close code 1004

-

Case Expectation

Clean close with protocol error code or drop TCP

+

Case Description

Send 1000 compressed messages each of payload size 1024, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 240 secs.


- -

Case 7.9.4

+ +

Case 13.4.5

Up -

Case Description

Send close with invalid close code 1005

-

Case Expectation

Clean close with protocol error code or drop TCP

+

Case Description

Send 1000 compressed messages each of payload size 4096, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.9.5

+ +

Case 13.4.6

+ Up +

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.

+
+ +

Case 13.4.7

Up -

Case Description

Send close with invalid close code 1006

-

Case Expectation

Clean close with protocol error code or drop TCP

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.9.6

+ +

Case 13.4.8

Up -

Case Description

Send close with invalid close code 1012

-

Case Expectation

Clean close with protocol error code or drop TCP

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.9.7

+ +

Case 13.4.9

Up -

Case Description

Send close with invalid close code 1013

-

Case Expectation

Clean close with protocol error code or drop TCP

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.9.8

+ +

Case 13.4.10

Up -

Case Description

Send close with invalid close code 1014

-

Case Expectation

Clean close with protocol error code or drop TCP

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.9.9

+ +

Case 13.4.11

Up -

Case Description

Send close with invalid close code 1015

-

Case Expectation

Clean close with protocol error code or drop TCP

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.9.10

+ +

Case 13.4.12

Up -

Case Description

Send close with invalid close code 1016

-

Case Expectation

Clean close with protocol error code or drop TCP

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.9.11

+ +

Case 13.4.13

Up -

Case Description

Send close with invalid close code 1100

-

Case Expectation

Clean close with protocol error code or drop TCP

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.9.12

+ +

Case 13.4.14

Up -

Case Description

Send close with invalid close code 2000

-

Case Expectation

Clean close with protocol error code or drop TCP

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.9.13

+ +

Case 13.4.15

Up -

Case Description

Send close with invalid close code 2999

-

Case Expectation

Clean close with protocol error code or drop TCP

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.13.1

+ +

Case 13.4.16

Up -

Case Description

Send close with close code 5000

-

Case Expectation

Actual events are undefined by the spec.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 1024 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 7.13.2

+ +

Case 13.4.17

Up -

Case Description

Send close with close code 65536

-

Case Expectation

Actual events are undefined by the spec.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 4096 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.1.1

+ +

Case 13.4.18

Up -

Case Description

Send text message message with payload of length 64 * 2**10 (64k).

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 32768 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(False, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.1.2

+ +

Case 13.5.1

Up -

Case Description

Send text message message with payload of length 256 * 2**10 (256k).

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 16, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 9.1.3

+ +

Case 13.5.2

Up -

Case Description

Send text message message with payload of length 1 * 2**20 (1M).

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 64, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 9.1.4

+ +

Case 13.5.3

Up -

Case Description

Send text message message with payload of length 4 * 2**20 (4M).

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 256, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 120 secs.


- -

Case 9.1.5

+ +

Case 13.5.4

Up -

Case Description

Send text message message with payload of length 8 * 2**20 (8M).

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 1024, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 240 secs.


- -

Case 9.1.6

+ +

Case 13.5.5

Up -

Case Description

Send text message message with payload of length 16 * 2**20 (16M).

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 4096, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.2.1

+ +

Case 13.5.6

Up -

Case Description

Send binary message message with payload of length 64 * 2**10 (64k).

-

Case Expectation

Receive echo'ed binary message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.2.2

+ +

Case 13.5.7

Up -

Case Description

Send binary message message with payload of length 256 * 2**10 (256k).

-

Case Expectation

Receive echo'ed binary message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.2.3

+ +

Case 13.5.8

Up -

Case Description

Send binary message message with payload of length 1 * 2**20 (1M).

-

Case Expectation

Receive echo'ed binary message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.2.4

+ +

Case 13.5.9

Up -

Case Description

Send binary message message with payload of length 4 * 2**20 (4M).

-

Case Expectation

Receive echo'ed binary message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.2.5

+ +

Case 13.5.10

Up -

Case Description

Send binary message message with payload of length 8 * 2**20 (16M).

-

Case Expectation

Receive echo'ed binary message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.2.6

+ +

Case 13.5.11

Up -

Case Description

Send binary message message with payload of length 16 * 2**20 (16M).

-

Case Expectation

Receive echo'ed binary message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.3.1

+ +

Case 13.5.12

Up -

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.3.2

+ +

Case 13.5.13

Up -

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.3.3

+ +

Case 13.5.14

Up -

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1k.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.3.4

+ +

Case 13.5.15

Up -

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4k.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.3.5

+ +

Case 13.5.16

Up -

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 16k.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 1024 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.3.6

+ +

Case 13.5.17

Up -

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64k.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 4096 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.3.7

+ +

Case 13.5.18

Up -

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256k.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 32768 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.3.8

+ +

Case 13.6.1

Up -

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1M.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 16, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 9.3.9

+ +

Case 13.6.2

Up -

Case Description

Send fragmented text message message with message payload of length 4 * 2**20 (8M). Sent out in fragments of 4M.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 64, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 9.4.1

+ +

Case 13.6.3

Up -

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64.

-

Case Expectation

Receive echo'ed binary message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 256, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 120 secs.


- -

Case 9.4.2

+ +

Case 13.6.4

Up -

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256.

-

Case Expectation

Receive echo'ed binary message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 1024, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 240 secs.


- -

Case 9.4.3

+ +

Case 13.6.5

Up -

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1k.

-

Case Expectation

Receive echo'ed binary message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 4096, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.4.4

+ +

Case 13.6.6

Up -

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4k.

-

Case Expectation

Receive echo'ed binary message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.4.5

+ +

Case 13.6.7

Up -

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 16k.

-

Case Expectation

Receive echo'ed binary message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.4.6

+ +

Case 13.6.8

Up -

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 64k.

-

Case Expectation

Receive echo'ed binary message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.4.7

+ +

Case 13.6.9

Up -

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 256k.

-

Case Expectation

Receive echo'ed binary message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.4.8

+ +

Case 13.6.10

Up -

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 1M.

-

Case Expectation

Receive echo'ed binary message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.4.9

+ +

Case 13.6.11

Up -

Case Description

Send fragmented binary message message with message payload of length 4 * 2**20 (4M). Sent out in fragments of 4M.

-

Case Expectation

Receive echo'ed binary message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.5.1

+ +

Case 13.6.12

Up -

Case Description

Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 64 octets.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.5.2

+ +

Case 13.6.13

Up -

Case Description

Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 128 octets.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.5.3

+ +

Case 13.6.14

Up -

Case Description

Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 256 octets.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.5.4

+ +

Case 13.6.15

Up -

Case Description

Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 512 octets.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.5.5

+ +

Case 13.6.16

Up -

Case Description

Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 1024 octets.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 1024 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.5.6

+ +

Case 13.6.17

Up -

Case Description

Send text message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 2048 octets.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 4096 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.6.1

+ +

Case 13.6.18

Up -

Case Description

Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 64 octets.

-

Case Expectation

Receive echo'ed binary message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 32768 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 15)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.6.2

+ +

Case 13.7.1

Up -

Case Description

Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 128 octets.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 16, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 9.6.3

+ +

Case 13.7.2

Up -

Case Description

Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 256 octets.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 64, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 60 secs.


- -

Case 9.6.4

+ +

Case 13.7.3

Up -

Case Description

Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 512 octets.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 256, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 120 secs.


- -

Case 9.6.5

+ +

Case 13.7.4

Up -

Case Description

Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 1024 octets.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 1024, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 240 secs.


- -

Case 9.6.6

+ +

Case 13.7.5

Up -

Case Description

Send binary message message with payload of length 1 * 2**20 (1M). Sent out data in chops of 2048 octets.

-

Case Expectation

Receive echo'ed text message (with payload as sent).

+

Case Description

Send 1000 compressed messages each of payload size 4096, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.7.1

+ +

Case 13.7.6

Up -

Case Description

Send 1000 text messages of payload size 0 to measure implementation/network RTT (round trip time) / latency.

-

Case Expectation

Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.7.2

+ +

Case 13.7.7

Up -

Case Description

Send 1000 text messages of payload size 16 to measure implementation/network RTT (round trip time) / latency.

-

Case Expectation

Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.7.3

+ +

Case 13.7.8

Up -

Case Description

Send 1000 text messages of payload size 64 to measure implementation/network RTT (round trip time) / latency.

-

Case Expectation

Receive echo'ed text messages (with payload as sent). Timeout case after 60 secs.

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.7.4

+ +

Case 13.7.9

Up -

Case Description

Send 1000 text messages of payload size 256 to measure implementation/network RTT (round trip time) / latency.

-

Case Expectation

Receive echo'ed text messages (with payload as sent). Timeout case after 120 secs.

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.7.5

+ +

Case 13.7.10

Up -

Case Description

Send 1000 text messages of payload size 1024 to measure implementation/network RTT (round trip time) / latency.

-

Case Expectation

Receive echo'ed text messages (with payload as sent). Timeout case after 240 secs.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 0 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.7.6

+ +

Case 13.7.11

Up -

Case Description

Send 1000 text messages of payload size 4096 to measure implementation/network RTT (round trip time) / latency.

-

Case Expectation

Receive echo'ed text messages (with payload as sent). Timeout case after 480 secs.

+

Case Description

Send 1000 compressed messages each of payload size 8192, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.8.1

+ +

Case 13.7.12

Up -

Case Description

Send 1000 binary messages of payload size 0 to measure implementation/network RTT (round trip time) / latency.

-

Case Expectation

Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.

+

Case Description

Send 1000 compressed messages each of payload size 16384, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.8.2

+ +

Case 13.7.13

Up -

Case Description

Send 1000 binary messages of payload size 16 to measure implementation/network RTT (round trip time) / latency.

-

Case Expectation

Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.

+

Case Description

Send 1000 compressed messages each of payload size 32768, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.8.3

+ +

Case 13.7.14

Up -

Case Description

Send 1000 binary messages of payload size 64 to measure implementation/network RTT (round trip time) / latency.

-

Case Expectation

Receive echo'ed binary messages (with payload as sent). Timeout case after 60 secs.

+

Case Description

Send 1000 compressed messages each of payload size 65536, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.8.4

+ +

Case 13.7.15

Up -

Case Description

Send 1000 binary messages of payload size 256 to measure implementation/network RTT (round trip time) / latency.

-

Case Expectation

Receive echo'ed binary messages (with payload as sent). Timeout case after 120 secs.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 256 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.8.5

+ +

Case 13.7.16

Up -

Case Description

Send 1000 binary messages of payload size 1024 to measure implementation/network RTT (round trip time) / latency.

-

Case Expectation

Receive echo'ed binary messages (with payload as sent). Timeout case after 240 secs.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 1024 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 9.8.6

+ +

Case 13.7.17

Up -

Case Description

Send 1000 binary messages of payload size 4096 to measure implementation/network RTT (round trip time) / latency.

-

Case Expectation

Receive echo'ed binary messages (with payload as sent). Timeout case after 480 secs.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 4096 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.


- -

Case 10.1.1

+ +

Case 13.7.18

Up -

Case Description

Send text message with payload of length 65536 and autoFragmentSize = 1300.

-

Case Expectation

Receive echo'ed text message (with payload as sent and transmitted frame counts as expected). Clean close with normal code.

+

Case Description

Send 1000 compressed messages each of payload size 131072, auto-fragment to 32768 octets. Use permessage-deflate client offers (requestNoContextTakeover, requestMaxWindowBits): [(True, 8), (True, 0), (False, 0)]

+

Case Expectation

Receive echo'ed messages (with payload as sent). Timeout case after 480 secs.



diff --git a/autobahn reports/servers/index.json b/autobahn reports/servers/index.json new file mode 100644 index 000000000..01e32bf74 --- /dev/null +++ b/autobahn reports/servers/index.json @@ -0,0 +1,3637 @@ +{ + "TooTallNateWebsocket": { + "1.1.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 4, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_1_1_1.json" + }, + "1.1.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_1_1_2.json" + }, + "1.1.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_1_1_3.json" + }, + "1.1.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_1_1_4.json" + }, + "1.1.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 18, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_1_1_5.json" + }, + "1.1.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 12, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_1_1_6.json" + }, + "1.1.7": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 6, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_1_1_7.json" + }, + "1.1.8": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 70, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_1_1_8.json" + }, + "1.2.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_1_2_1.json" + }, + "1.2.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_1_2_2.json" + }, + "1.2.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_1_2_3.json" + }, + "1.2.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_1_2_4.json" + }, + "1.2.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_1_2_5.json" + }, + "1.2.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 32, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_1_2_6.json" + }, + "1.2.7": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 35, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_1_2_7.json" + }, + "1.2.8": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 97, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_1_2_8.json" + }, + "10.1.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 6, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_10_1_1.json" + }, + "12.1.1": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_1.json" + }, + "12.1.10": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_10.json" + }, + "12.1.11": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_11.json" + }, + "12.1.12": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_12.json" + }, + "12.1.13": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_13.json" + }, + "12.1.14": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_14.json" + }, + "12.1.15": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_15.json" + }, + "12.1.16": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_16.json" + }, + "12.1.17": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_17.json" + }, + "12.1.18": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_18.json" + }, + "12.1.2": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_2.json" + }, + "12.1.3": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_3.json" + }, + "12.1.4": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_4.json" + }, + "12.1.5": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_5.json" + }, + "12.1.6": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_6.json" + }, + "12.1.7": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_7.json" + }, + "12.1.8": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_8.json" + }, + "12.1.9": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_1_9.json" + }, + "12.2.1": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_1.json" + }, + "12.2.10": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_10.json" + }, + "12.2.11": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_11.json" + }, + "12.2.12": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_12.json" + }, + "12.2.13": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_13.json" + }, + "12.2.14": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_14.json" + }, + "12.2.15": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_15.json" + }, + "12.2.16": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_16.json" + }, + "12.2.17": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_17.json" + }, + "12.2.18": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_18.json" + }, + "12.2.2": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_2.json" + }, + "12.2.3": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_3.json" + }, + "12.2.4": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_4.json" + }, + "12.2.5": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_5.json" + }, + "12.2.6": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_6.json" + }, + "12.2.7": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_7.json" + }, + "12.2.8": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_8.json" + }, + "12.2.9": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_2_9.json" + }, + "12.3.1": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_1.json" + }, + "12.3.10": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_10.json" + }, + "12.3.11": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_11.json" + }, + "12.3.12": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_12.json" + }, + "12.3.13": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_13.json" + }, + "12.3.14": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_14.json" + }, + "12.3.15": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_15.json" + }, + "12.3.16": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_16.json" + }, + "12.3.17": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_17.json" + }, + "12.3.18": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_18.json" + }, + "12.3.2": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_2.json" + }, + "12.3.3": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_3.json" + }, + "12.3.4": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_4.json" + }, + "12.3.5": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_5.json" + }, + "12.3.6": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_6.json" + }, + "12.3.7": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_7.json" + }, + "12.3.8": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_8.json" + }, + "12.3.9": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_3_9.json" + }, + "12.4.1": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_1.json" + }, + "12.4.10": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_10.json" + }, + "12.4.11": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_11.json" + }, + "12.4.12": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_12.json" + }, + "12.4.13": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_13.json" + }, + "12.4.14": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_14.json" + }, + "12.4.15": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_15.json" + }, + "12.4.16": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_16.json" + }, + "12.4.17": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_17.json" + }, + "12.4.18": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_18.json" + }, + "12.4.2": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_2.json" + }, + "12.4.3": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_3.json" + }, + "12.4.4": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_4.json" + }, + "12.4.5": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_5.json" + }, + "12.4.6": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_6.json" + }, + "12.4.7": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_7.json" + }, + "12.4.8": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_8.json" + }, + "12.4.9": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_4_9.json" + }, + "12.5.1": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_1.json" + }, + "12.5.10": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_10.json" + }, + "12.5.11": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_11.json" + }, + "12.5.12": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_12.json" + }, + "12.5.13": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_13.json" + }, + "12.5.14": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_14.json" + }, + "12.5.15": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_15.json" + }, + "12.5.16": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_16.json" + }, + "12.5.17": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_17.json" + }, + "12.5.18": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_18.json" + }, + "12.5.2": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_2.json" + }, + "12.5.3": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_3.json" + }, + "12.5.4": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_4.json" + }, + "12.5.5": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_5.json" + }, + "12.5.6": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_6.json" + }, + "12.5.7": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_7.json" + }, + "12.5.8": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_8.json" + }, + "12.5.9": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_12_5_9.json" + }, + "13.1.1": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_1.json" + }, + "13.1.10": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_10.json" + }, + "13.1.11": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_11.json" + }, + "13.1.12": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_12.json" + }, + "13.1.13": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_13.json" + }, + "13.1.14": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_14.json" + }, + "13.1.15": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_15.json" + }, + "13.1.16": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_16.json" + }, + "13.1.17": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_17.json" + }, + "13.1.18": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_18.json" + }, + "13.1.2": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_2.json" + }, + "13.1.3": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_3.json" + }, + "13.1.4": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_4.json" + }, + "13.1.5": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_5.json" + }, + "13.1.6": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_6.json" + }, + "13.1.7": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_7.json" + }, + "13.1.8": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_8.json" + }, + "13.1.9": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_1_9.json" + }, + "13.2.1": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_1.json" + }, + "13.2.10": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_10.json" + }, + "13.2.11": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_11.json" + }, + "13.2.12": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_12.json" + }, + "13.2.13": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_13.json" + }, + "13.2.14": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_14.json" + }, + "13.2.15": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_15.json" + }, + "13.2.16": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_16.json" + }, + "13.2.17": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_17.json" + }, + "13.2.18": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_18.json" + }, + "13.2.2": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_2.json" + }, + "13.2.3": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_3.json" + }, + "13.2.4": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_4.json" + }, + "13.2.5": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_5.json" + }, + "13.2.6": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_6.json" + }, + "13.2.7": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_7.json" + }, + "13.2.8": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_8.json" + }, + "13.2.9": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_2_9.json" + }, + "13.3.1": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_1.json" + }, + "13.3.10": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_10.json" + }, + "13.3.11": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_11.json" + }, + "13.3.12": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_12.json" + }, + "13.3.13": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_13.json" + }, + "13.3.14": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_14.json" + }, + "13.3.15": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_15.json" + }, + "13.3.16": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_16.json" + }, + "13.3.17": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_17.json" + }, + "13.3.18": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_18.json" + }, + "13.3.2": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_2.json" + }, + "13.3.3": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_3.json" + }, + "13.3.4": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_4.json" + }, + "13.3.5": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_5.json" + }, + "13.3.6": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_6.json" + }, + "13.3.7": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_7.json" + }, + "13.3.8": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_8.json" + }, + "13.3.9": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_3_9.json" + }, + "13.4.1": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_1.json" + }, + "13.4.10": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_10.json" + }, + "13.4.11": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_11.json" + }, + "13.4.12": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_12.json" + }, + "13.4.13": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_13.json" + }, + "13.4.14": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_14.json" + }, + "13.4.15": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_15.json" + }, + "13.4.16": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_16.json" + }, + "13.4.17": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_17.json" + }, + "13.4.18": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_18.json" + }, + "13.4.2": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_2.json" + }, + "13.4.3": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_3.json" + }, + "13.4.4": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_4.json" + }, + "13.4.5": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_5.json" + }, + "13.4.6": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_6.json" + }, + "13.4.7": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_7.json" + }, + "13.4.8": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_8.json" + }, + "13.4.9": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_4_9.json" + }, + "13.5.1": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_1.json" + }, + "13.5.10": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_10.json" + }, + "13.5.11": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_11.json" + }, + "13.5.12": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_12.json" + }, + "13.5.13": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_13.json" + }, + "13.5.14": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_14.json" + }, + "13.5.15": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_15.json" + }, + "13.5.16": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_16.json" + }, + "13.5.17": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_17.json" + }, + "13.5.18": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_18.json" + }, + "13.5.2": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_2.json" + }, + "13.5.3": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_3.json" + }, + "13.5.4": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_4.json" + }, + "13.5.5": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_5.json" + }, + "13.5.6": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_6.json" + }, + "13.5.7": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_7.json" + }, + "13.5.8": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_8.json" + }, + "13.5.9": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_5_9.json" + }, + "13.6.1": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_1.json" + }, + "13.6.10": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_10.json" + }, + "13.6.11": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_11.json" + }, + "13.6.12": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_12.json" + }, + "13.6.13": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_13.json" + }, + "13.6.14": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_14.json" + }, + "13.6.15": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_15.json" + }, + "13.6.16": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_16.json" + }, + "13.6.17": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_17.json" + }, + "13.6.18": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_18.json" + }, + "13.6.2": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_2.json" + }, + "13.6.3": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_3.json" + }, + "13.6.4": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_4.json" + }, + "13.6.5": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_5.json" + }, + "13.6.6": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_6.json" + }, + "13.6.7": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_7.json" + }, + "13.6.8": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_8.json" + }, + "13.6.9": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_6_9.json" + }, + "13.7.1": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_1.json" + }, + "13.7.10": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_10.json" + }, + "13.7.11": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_11.json" + }, + "13.7.12": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_12.json" + }, + "13.7.13": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_13.json" + }, + "13.7.14": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_14.json" + }, + "13.7.15": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_15.json" + }, + "13.7.16": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_16.json" + }, + "13.7.17": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_17.json" + }, + "13.7.18": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_18.json" + }, + "13.7.2": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_2.json" + }, + "13.7.3": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_3.json" + }, + "13.7.4": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_4.json" + }, + "13.7.5": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_5.json" + }, + "13.7.6": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_6.json" + }, + "13.7.7": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_7.json" + }, + "13.7.8": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_8.json" + }, + "13.7.9": { + "behavior": "UNIMPLEMENTED", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_13_7_9.json" + }, + "2.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_2_1.json" + }, + "2.10": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 6, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_2_10.json" + }, + "2.11": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 150, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_2_11.json" + }, + "2.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_2_2.json" + }, + "2.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_2_3.json" + }, + "2.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_2_4.json" + }, + "2.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_2_5.json" + }, + "2.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 132, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_2_6.json" + }, + "2.7": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_2_7.json" + }, + "2.8": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_2_8.json" + }, + "2.9": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_2_9.json" + }, + "3.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_3_1.json" + }, + "3.2": { + "behavior": "NON-STRICT", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_3_2.json" + }, + "3.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_3_3.json" + }, + "3.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 22, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_3_4.json" + }, + "3.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_3_5.json" + }, + "3.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_3_6.json" + }, + "3.7": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 4, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_3_7.json" + }, + "4.1.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_4_1_1.json" + }, + "4.1.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_4_1_2.json" + }, + "4.1.3": { + "behavior": "NON-STRICT", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_4_1_3.json" + }, + "4.1.4": { + "behavior": "NON-STRICT", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_4_1_4.json" + }, + "4.1.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 21, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_4_1_5.json" + }, + "4.2.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_4_2_1.json" + }, + "4.2.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_4_2_2.json" + }, + "4.2.3": { + "behavior": "NON-STRICT", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_4_2_3.json" + }, + "4.2.4": { + "behavior": "NON-STRICT", + "behaviorClose": "OK", + "duration": 3, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_4_2_4.json" + }, + "4.2.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 20, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_4_2_5.json" + }, + "5.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_5_1.json" + }, + "5.10": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_5_10.json" + }, + "5.11": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 30, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_5_11.json" + }, + "5.12": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_5_12.json" + }, + "5.13": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_5_13.json" + }, + "5.14": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 30, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_5_14.json" + }, + "5.15": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_5_15.json" + }, + "5.16": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_5_16.json" + }, + "5.17": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_5_17.json" + }, + "5.18": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_5_18.json" + }, + "5.19": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1001, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_5_19.json" + }, + "5.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_5_2.json" + }, + "5.20": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1005, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_5_20.json" + }, + "5.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_5_3.json" + }, + "5.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_5_4.json" + }, + "5.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 30, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_5_5.json" + }, + "5.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_5_6.json" + }, + "5.7": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 3, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_5_7.json" + }, + "5.8": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 49, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_5_8.json" + }, + "5.9": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 17, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_5_9.json" + }, + "6.1.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_1_1.json" + }, + "6.1.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_1_2.json" + }, + "6.1.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_1_3.json" + }, + "6.10.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_10_1.json" + }, + "6.10.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_10_2.json" + }, + "6.10.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_10_3.json" + }, + "6.11.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_11_1.json" + }, + "6.11.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_11_2.json" + }, + "6.11.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_11_3.json" + }, + "6.11.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_11_4.json" + }, + "6.11.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_11_5.json" + }, + "6.12.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_12_1.json" + }, + "6.12.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_12_2.json" + }, + "6.12.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_12_3.json" + }, + "6.12.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_12_4.json" + }, + "6.12.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_12_5.json" + }, + "6.12.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_12_6.json" + }, + "6.12.7": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_12_7.json" + }, + "6.12.8": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_12_8.json" + }, + "6.13.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_13_1.json" + }, + "6.13.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_13_2.json" + }, + "6.13.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_13_3.json" + }, + "6.13.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_13_4.json" + }, + "6.13.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_13_5.json" + }, + "6.14.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_14_1.json" + }, + "6.14.10": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_14_10.json" + }, + "6.14.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_14_2.json" + }, + "6.14.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_14_3.json" + }, + "6.14.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_14_4.json" + }, + "6.14.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_14_5.json" + }, + "6.14.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_14_6.json" + }, + "6.14.7": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_14_7.json" + }, + "6.14.8": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_14_8.json" + }, + "6.14.9": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_14_9.json" + }, + "6.15.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_15_1.json" + }, + "6.16.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_16_1.json" + }, + "6.16.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_16_2.json" + }, + "6.16.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_16_3.json" + }, + "6.17.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_17_1.json" + }, + "6.17.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_17_2.json" + }, + "6.17.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_17_3.json" + }, + "6.17.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_17_4.json" + }, + "6.17.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_17_5.json" + }, + "6.18.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_18_1.json" + }, + "6.18.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_18_2.json" + }, + "6.18.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_18_3.json" + }, + "6.18.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_18_4.json" + }, + "6.18.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_18_5.json" + }, + "6.19.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_19_1.json" + }, + "6.19.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_19_2.json" + }, + "6.19.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_19_3.json" + }, + "6.19.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_19_4.json" + }, + "6.19.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_19_5.json" + }, + "6.2.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_2_1.json" + }, + "6.2.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_2_2.json" + }, + "6.2.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 3, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_2_3.json" + }, + "6.2.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_2_4.json" + }, + "6.20.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_20_1.json" + }, + "6.20.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_20_2.json" + }, + "6.20.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_20_3.json" + }, + "6.20.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_20_4.json" + }, + "6.20.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_20_5.json" + }, + "6.20.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_20_6.json" + }, + "6.20.7": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_20_7.json" + }, + "6.21.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_21_1.json" + }, + "6.21.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_21_2.json" + }, + "6.21.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_21_3.json" + }, + "6.21.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_21_4.json" + }, + "6.21.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_21_5.json" + }, + "6.21.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_21_6.json" + }, + "6.21.7": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_21_7.json" + }, + "6.21.8": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_21_8.json" + }, + "6.22.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_1.json" + }, + "6.22.10": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_10.json" + }, + "6.22.11": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_11.json" + }, + "6.22.12": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_12.json" + }, + "6.22.13": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_13.json" + }, + "6.22.14": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_14.json" + }, + "6.22.15": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 7, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_15.json" + }, + "6.22.16": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_16.json" + }, + "6.22.17": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_17.json" + }, + "6.22.18": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_18.json" + }, + "6.22.19": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_19.json" + }, + "6.22.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_2.json" + }, + "6.22.20": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_20.json" + }, + "6.22.21": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_21.json" + }, + "6.22.22": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_22.json" + }, + "6.22.23": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_23.json" + }, + "6.22.24": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_24.json" + }, + "6.22.25": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_25.json" + }, + "6.22.26": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_26.json" + }, + "6.22.27": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_27.json" + }, + "6.22.28": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_28.json" + }, + "6.22.29": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_29.json" + }, + "6.22.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_3.json" + }, + "6.22.30": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_30.json" + }, + "6.22.31": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_31.json" + }, + "6.22.32": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_32.json" + }, + "6.22.33": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_33.json" + }, + "6.22.34": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_34.json" + }, + "6.22.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_4.json" + }, + "6.22.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_5.json" + }, + "6.22.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_6.json" + }, + "6.22.7": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 4, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_7.json" + }, + "6.22.8": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_8.json" + }, + "6.22.9": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_22_9.json" + }, + "6.23.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_23_1.json" + }, + "6.23.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_23_2.json" + }, + "6.23.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_23_3.json" + }, + "6.23.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_23_4.json" + }, + "6.23.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_23_5.json" + }, + "6.23.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_23_6.json" + }, + "6.23.7": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_23_7.json" + }, + "6.3.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_3_1.json" + }, + "6.3.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_3_2.json" + }, + "6.4.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1001, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_4_1.json" + }, + "6.4.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1000, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_4_2.json" + }, + "6.4.3": { + "behavior": "NON-STRICT", + "behaviorClose": "OK", + "duration": 2001, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_4_3.json" + }, + "6.4.4": { + "behavior": "NON-STRICT", + "behaviorClose": "OK", + "duration": 2001, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_4_4.json" + }, + "6.5.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_5_1.json" + }, + "6.5.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_5_2.json" + }, + "6.5.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_5_3.json" + }, + "6.5.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_5_4.json" + }, + "6.5.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_5_5.json" + }, + "6.6.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_6_1.json" + }, + "6.6.10": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_6_10.json" + }, + "6.6.11": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_6_11.json" + }, + "6.6.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_6_2.json" + }, + "6.6.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_6_3.json" + }, + "6.6.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_6_4.json" + }, + "6.6.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_6_5.json" + }, + "6.6.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_6_6.json" + }, + "6.6.7": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_6_7.json" + }, + "6.6.8": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_6_8.json" + }, + "6.6.9": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_6_9.json" + }, + "6.7.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_7_1.json" + }, + "6.7.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_7_2.json" + }, + "6.7.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_7_3.json" + }, + "6.7.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_7_4.json" + }, + "6.8.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_8_1.json" + }, + "6.8.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_6_8_2.json" + }, + "6.9.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_9_1.json" + }, + "6.9.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_9_2.json" + }, + "6.9.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_9_3.json" + }, + "6.9.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_6_9_4.json" + }, + "7.1.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_7_1_1.json" + }, + "7.1.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_7_1_2.json" + }, + "7.1.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_7_1_3.json" + }, + "7.1.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_7_1_4.json" + }, + "7.1.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_7_1_5.json" + }, + "7.1.6": { + "behavior": "INFORMATIONAL", + "behaviorClose": "INFORMATIONAL", + "duration": 16, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_7_1_6.json" + }, + "7.13.1": { + "behavior": "INFORMATIONAL", + "behaviorClose": "INFORMATIONAL", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_7_13_1.json" + }, + "7.13.2": { + "behavior": "INFORMATIONAL", + "behaviorClose": "INFORMATIONAL", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_7_13_2.json" + }, + "7.3.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": null, + "reportfile": "tootallnatewebsocket_case_7_3_1.json" + }, + "7.3.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": null, + "reportfile": "tootallnatewebsocket_case_7_3_2.json" + }, + "7.3.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_7_3_3.json" + }, + "7.3.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_7_3_4.json" + }, + "7.3.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_7_3_5.json" + }, + "7.3.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_7_3_6.json" + }, + "7.5.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_7_5_1.json" + }, + "7.7.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_7_7_1.json" + }, + "7.7.10": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 3000, + "reportfile": "tootallnatewebsocket_case_7_7_10.json" + }, + "7.7.11": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 3999, + "reportfile": "tootallnatewebsocket_case_7_7_11.json" + }, + "7.7.12": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 4000, + "reportfile": "tootallnatewebsocket_case_7_7_12.json" + }, + "7.7.13": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 4999, + "reportfile": "tootallnatewebsocket_case_7_7_13.json" + }, + "7.7.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1001, + "reportfile": "tootallnatewebsocket_case_7_7_2.json" + }, + "7.7.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_7_7_3.json" + }, + "7.7.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1003, + "reportfile": "tootallnatewebsocket_case_7_7_4.json" + }, + "7.7.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1007, + "reportfile": "tootallnatewebsocket_case_7_7_5.json" + }, + "7.7.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1008, + "reportfile": "tootallnatewebsocket_case_7_7_6.json" + }, + "7.7.7": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1009, + "reportfile": "tootallnatewebsocket_case_7_7_7.json" + }, + "7.7.8": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1010, + "reportfile": "tootallnatewebsocket_case_7_7_8.json" + }, + "7.7.9": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1011, + "reportfile": "tootallnatewebsocket_case_7_7_9.json" + }, + "7.9.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_7_9_1.json" + }, + "7.9.10": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": null, + "reportfile": "tootallnatewebsocket_case_7_9_10.json" + }, + "7.9.11": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": null, + "reportfile": "tootallnatewebsocket_case_7_9_11.json" + }, + "7.9.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_7_9_2.json" + }, + "7.9.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_7_9_3.json" + }, + "7.9.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_7_9_4.json" + }, + "7.9.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_7_9_5.json" + }, + "7.9.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": null, + "reportfile": "tootallnatewebsocket_case_7_9_6.json" + }, + "7.9.7": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": 1002, + "reportfile": "tootallnatewebsocket_case_7_9_7.json" + }, + "7.9.8": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1, + "remoteCloseCode": null, + "reportfile": "tootallnatewebsocket_case_7_9_8.json" + }, + "7.9.9": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 0, + "remoteCloseCode": null, + "reportfile": "tootallnatewebsocket_case_7_9_9.json" + }, + "9.1.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 4, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_1_1.json" + }, + "9.1.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 10, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_1_2.json" + }, + "9.1.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 31, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_1_3.json" + }, + "9.1.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 149, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_1_4.json" + }, + "9.1.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 233, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_1_5.json" + }, + "9.1.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 521, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_1_6.json" + }, + "9.2.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 3, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_2_1.json" + }, + "9.2.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 7, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_2_2.json" + }, + "9.2.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 28, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_2_3.json" + }, + "9.2.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 103, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_2_4.json" + }, + "9.2.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 231, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_2_5.json" + }, + "9.2.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 408, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_2_6.json" + }, + "9.3.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 32384, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_3_1.json" + }, + "9.3.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 8061, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_3_2.json" + }, + "9.3.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2199, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_3_3.json" + }, + "9.3.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 564, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_3_4.json" + }, + "9.3.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 179, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_3_5.json" + }, + "9.3.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 86, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_3_6.json" + }, + "9.3.7": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 71, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_3_7.json" + }, + "9.3.8": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 69, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_3_8.json" + }, + "9.3.9": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 73, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_3_9.json" + }, + "9.4.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2445, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_4_1.json" + }, + "9.4.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 654, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_4_2.json" + }, + "9.4.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 205, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_4_3.json" + }, + "9.4.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 68, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_4_4.json" + }, + "9.4.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 42, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_4_5.json" + }, + "9.4.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 39, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_4_6.json" + }, + "9.4.7": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 55, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_4_7.json" + }, + "9.4.8": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 70, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_4_8.json" + }, + "9.4.9": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 50, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_4_9.json" + }, + "9.5.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 16426, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_5_1.json" + }, + "9.5.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 8233, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_5_2.json" + }, + "9.5.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 4126, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_5_3.json" + }, + "9.5.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2072, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_5_4.json" + }, + "9.5.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1049, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_5_5.json" + }, + "9.5.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 534, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_5_6.json" + }, + "9.6.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 16426, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_6_1.json" + }, + "9.6.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 8227, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_6_2.json" + }, + "9.6.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 4122, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_6_3.json" + }, + "9.6.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 2074, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_6_4.json" + }, + "9.6.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 1048, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_6_5.json" + }, + "9.6.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 533, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_6_6.json" + }, + "9.7.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 160, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_7_1.json" + }, + "9.7.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 164, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_7_2.json" + }, + "9.7.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 169, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_7_3.json" + }, + "9.7.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 186, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_7_4.json" + }, + "9.7.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 239, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_7_5.json" + }, + "9.7.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 439, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_7_6.json" + }, + "9.8.1": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 154, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_8_1.json" + }, + "9.8.2": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 165, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_8_2.json" + }, + "9.8.3": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 162, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_8_3.json" + }, + "9.8.4": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 177, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_8_4.json" + }, + "9.8.5": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 220, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_8_5.json" + }, + "9.8.6": { + "behavior": "OK", + "behaviorClose": "OK", + "duration": 395, + "remoteCloseCode": 1000, + "reportfile": "tootallnatewebsocket_case_9_8_6.json" + } + } +} \ No newline at end of file diff --git a/autobahn reports/servers/tootallnate_websocket_case_3_2.html b/autobahn reports/servers/tootallnate_websocket_case_3_2.html deleted file mode 100644 index 81b697dde..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_3_2.html +++ /dev/null @@ -1,305 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 3.2 : Non-Strict - 2 ms @ 2012-02-04T15:41:06Z

-

Case Description

Send small text message, then send again with RSV = 2, then send Ping.

-

Case Expectation

Echo for first message is received, but then connection is failed immediately, since RSV must be 0, when no extension defining RSV meaning has been negoiated. The Pong is not received.

- -

- Case Outcome

Actual events match at least one expected.

- Expected:
{'NON-STRICT': [], 'OK': [('message', 'Hello, world!', False)]}

- Observed:
[] -

-

Case Closing Behavior

Connection was properly closed (OK)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: naQpeQXM16oTsQGgFhzkCw==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: GWrmDJGkZekAGDtxltB9SFScv/0=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeFalseTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeFalseTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1000The close code I sent in close frame (if any).
localCloseReasonNoneThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - -
Chop SizeCountOctets
212
1291129
Total2131
-

Octets Transmitted by Chop Size

- - - - - - - -
Chop SizeCountOctets
616
818
19238
2011201
Total5253
-

Frames Received by Opcode

- - - - -
OpcodeCount
81
Total1
-

Frames Transmitted by Opcode

- - - - - - -
OpcodeCount
12
81
91
Total4
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a206e615170
-
               6551584d31366f547351476746687a6b43773d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20475772
-
               6d444a476b5a656b41474474786c74423953465363762f303d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=c8e7cd39, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Hello, world!
-
003 TX OCTETS: 818dc8e7cd398082a155a7cbed4ea795a15de9
-
004 TX FRAME : OPCODE=1, FIN=True, RSV=2, MASK=48cf9b10, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Hello, world!
-
005 TX OCTETS: a18d48cf9b1000aaf77c27e3bb6727bdf77469
-
006 TX FRAME : OPCODE=9, FIN=True, RSV=0, MASK=ba7b6257, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
007 TX OCTETS: 8980ba7b6257
-
008 FAIL CONNECTION AFTER 1.000000 sec
-
009 RX OCTETS: 8800
-
010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
011 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=c2125d8a, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
012 TX OCTETS: 8882c2125d8ac1fa
-
013 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_4_1_3.html b/autobahn reports/servers/tootallnate_websocket_case_4_1_3.html deleted file mode 100644 index ed037118d..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_4_1_3.html +++ /dev/null @@ -1,305 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 4.1.3 : Non-Strict - 2 ms @ 2012-02-04T15:41:06Z

-

Case Description

Send small text message, then send frame with reserved non-control Opcode = 5, then send Ping.

-

Case Expectation

Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.

- -

- Case Outcome

Actual events match at least one expected.

- Expected:
{'NON-STRICT': [], 'OK': [('message', 'Hello, world!', False)]}

- Observed:
[] -

-

Case Closing Behavior

Connection was properly closed (OK)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: xsPC4lQvjb4PhpJeBNWHdQ==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: ZT0DfgjT9h2dF3tlZwQbiVc8r5s=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeFalseTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeFalseTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1000The close code I sent in close frame (if any).
localCloseReasonNoneThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - -
Chop SizeCountOctets
212
1291129
Total2131
-

Octets Transmitted by Chop Size

- - - - - - - -
Chop SizeCountOctets
6212
818
19119
2011201
Total5240
-

Frames Received by Opcode

- - - - -
OpcodeCount
81
Total1
-

Frames Transmitted by Opcode

- - - - - - - -
OpcodeCount
11
51
81
91
Total4
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2078735043
-
               346c51766a62345068704a65424e574864513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a205a5430
-
               4466676a54396832644633746c5a775162695663387235733d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=c591fc05, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Hello, world!
-
003 TX OCTETS: 818dc591fc058df49069aabddc72aae39061e4
-
004 TX FRAME : OPCODE=5, FIN=True, RSV=0, MASK=245c9b85, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
005 TX OCTETS: 8580245c9b85
-
006 TX FRAME : OPCODE=9, FIN=True, RSV=0, MASK=235abec4, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
007 TX OCTETS: 8980235abec4
-
008 FAIL CONNECTION AFTER 1.000000 sec
-
009 RX OCTETS: 8800
-
010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
011 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=3b961406, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
012 TX OCTETS: 88823b961406387e
-
013 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_4_1_4.html b/autobahn reports/servers/tootallnate_websocket_case_4_1_4.html deleted file mode 100644 index 534afc19b..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_4_1_4.html +++ /dev/null @@ -1,306 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 4.1.4 : Non-Strict - 2 ms @ 2012-02-04T15:41:06Z

-

Case Description

Send small text message, then send frame with reserved non-control Opcode = 6 and non-empty payload, then send Ping.

-

Case Expectation

Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.

- -

- Case Outcome

Actual events match at least one expected.

- Expected:
{'NON-STRICT': [], 'OK': [('message', 'Hello, world!', False)]}

- Observed:
[] -

-

Case Closing Behavior

Connection was properly closed (OK)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: lWjk0P7OM1q+JBUPL0HfZA==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: d7k1A19qGfNKwuf0DJJ9pJTFpII=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeFalseTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeFalseTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1000The close code I sent in close frame (if any).
localCloseReasonNoneThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - -
Chop SizeCountOctets
212
1291129
Total2131
-

Octets Transmitted by Chop Size

- - - - - - - -
Chop SizeCountOctets
616
818
19238
2011201
Total5253
-

Frames Received by Opcode

- - - - -
OpcodeCount
81
Total1
-

Frames Transmitted by Opcode

- - - - - - - -
OpcodeCount
11
61
81
91
Total4
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a206c576a6b
-
               3050374f4d31712b4a4255504c3048665a413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a2064376b
-
               314131397147664e4b77756630444a4a39704a54467049493d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=45843e64, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Hello, world!
-
003 TX OCTETS: 818d45843e640de152082aa81e132af6520064
-
004 TX FRAME : OPCODE=6, FIN=True, RSV=0, MASK=fa380a4b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Hello, world!
-
005 TX OCTETS: 868dfa380a4bb25d662795142a3c954a662fdb
-
006 TX FRAME : OPCODE=9, FIN=True, RSV=0, MASK=cfe1af5c, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
007 TX OCTETS: 8980cfe1af5c
-
008 FAIL CONNECTION AFTER 1.000000 sec
-
009 RX OCTETS: 8800
-
010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
011 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=4f85959a, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
012 TX OCTETS: 88824f85959a4c6d
-
013 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_4_2_3.html b/autobahn reports/servers/tootallnate_websocket_case_4_2_3.html deleted file mode 100644 index 815e80255..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_4_2_3.html +++ /dev/null @@ -1,305 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 4.2.3 : Non-Strict - 3 ms @ 2012-02-04T15:41:06Z

-

Case Description

Send small text message, then send frame with reserved control Opcode = 13, then send Ping.

-

Case Expectation

Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.

- -

- Case Outcome

Actual events match at least one expected.

- Expected:
{'NON-STRICT': [], 'OK': [('message', 'Hello, world!', False)]}

- Observed:
[] -

-

Case Closing Behavior

Connection was properly closed (OK)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: QWLCYu7pAj1LS5HIqlz7mg==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: ctI9BeFJRgdrhJO7B0QsoaPVOkI=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeFalseTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeFalseTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1000The close code I sent in close frame (if any).
localCloseReasonNoneThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - -
Chop SizeCountOctets
212
1291129
Total2131
-

Octets Transmitted by Chop Size

- - - - - - - -
Chop SizeCountOctets
6212
818
19119
2011201
Total5240
-

Frames Received by Opcode

- - - - -
OpcodeCount
81
Total1
-

Frames Transmitted by Opcode

- - - - - - - -
OpcodeCount
11
81
91
131
Total4
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2051574c43
-
               59753770416a314c53354849716c7a376d673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20637449
-
               394265464a52676472684a4f37423051736f6150564f6b493d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=24435521, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Hello, world!
-
003 TX OCTETS: 818d244355216c26394d4b6f75564b31394505
-
004 TX FRAME : OPCODE=13, FIN=True, RSV=0, MASK=b81daa6e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
005 TX OCTETS: 8d80b81daa6e
-
006 TX FRAME : OPCODE=9, FIN=True, RSV=0, MASK=a52d50b5, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
007 TX OCTETS: 8980a52d50b5
-
008 FAIL CONNECTION AFTER 1.000000 sec
-
009 RX OCTETS: 8800
-
010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
011 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=1564e876, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
012 TX OCTETS: 88821564e876168c
-
013 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_4_2_4.html b/autobahn reports/servers/tootallnate_websocket_case_4_2_4.html deleted file mode 100644 index 212d2a924..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_4_2_4.html +++ /dev/null @@ -1,306 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 4.2.4 : Non-Strict - 3 ms @ 2012-02-04T15:41:06Z

-

Case Description

Send small text message, then send frame with reserved control Opcode = 14 and non-empty payload, then send Ping.

-

Case Expectation

Echo for first message is received, but then connection is failed immediately, since reserved opcode frame is used. A Pong is not received.

- -

- Case Outcome

Actual events match at least one expected.

- Expected:
{'NON-STRICT': [], 'OK': [('message', 'Hello, world!', False)]}

- Observed:
[] -

-

Case Closing Behavior

Connection was properly closed (OK)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: Kh3b3ltcf0ADlsGo2EnkTg==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: 1v44L9ZzUJ6KHZJ5yJYvz+wLNC4=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeFalseTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeFalseTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1000The close code I sent in close frame (if any).
localCloseReasonNoneThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - -
Chop SizeCountOctets
212
1291129
Total2131
-

Octets Transmitted by Chop Size

- - - - - - - -
Chop SizeCountOctets
616
818
19238
2011201
Total5253
-

Frames Received by Opcode

- - - - -
OpcodeCount
81
Total1
-

Frames Transmitted by Opcode

- - - - - - - -
OpcodeCount
11
81
91
141
Total4
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a204b683362
-
               336c7463663041446c73476f32456e6b54673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20317634
-
               344c395a7a554a364b485a4a35794a59767a2b774c4e43343d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=4a1e0f77, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Hello, world!
-
003 TX OCTETS: 818d4a1e0f77027b631b25322f00256c63136b
-
004 TX FRAME : OPCODE=14, FIN=True, RSV=0, MASK=8a729523, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Hello, world!
-
005 TX OCTETS: 8e8d8a729523c217f94fe55eb554e500f947ab
-
006 TX FRAME : OPCODE=9, FIN=True, RSV=0, MASK=158f23ed, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
007 TX OCTETS: 8980158f23ed
-
008 FAIL CONNECTION AFTER 1.000000 sec
-
009 RX OCTETS: 8800
-
010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
011 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=9ff83713, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
012 TX OCTETS: 88829ff837139c10
-
013 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_20_1.html b/autobahn reports/servers/tootallnate_websocket_case_6_20_1.html deleted file mode 100644 index b368498d2..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_20_1.html +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.20.1 : Fail - 501 ms @ 2012-02-04T15:41:19Z

-

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

eda080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

- -

- Case Outcome

Actual events differ from any expected.

- Expected:
{'OK': []}

- Observed:
[('message', '?', False)] -

-

Case Closing Behavior

The connection was failed by the wrong endpoint (FAILED)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: 8hQi20KtwNAl024fziMiZA==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: 2vQQWjYeEC/rYsn25QkOUP1J64E=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeTrueTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1001The close code I sent in close frame (if any).
localCloseReasonGoing AwayThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
313
1291129
Total3134
-

Octets Transmitted by Chop Size

- - - - - - -
Chop SizeCountOctets
919
18118
2011201
Total3228
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2038685169
-
               32304b74774e416c303234667a694d695a413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20327651
-
               51576a596545432f7259736e3235516b4f5550314a3634453d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=09392f03, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
003 TX OCTETS: 818309392f03e499af
-
004 FAIL CONNECTION AFTER 0.500000 sec
-
005 RX OCTETS: 81013f
-
006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               ?
-
007 FAILING CONNECTION
-
008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=1a762475, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Going Away
-
009 TX OCTETS: 888c1a762475199f631a731843555b01450c
-
010 RX OCTETS: 8800
-
011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
012 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_20_2.html b/autobahn reports/servers/tootallnate_websocket_case_6_20_2.html deleted file mode 100644 index 85a012542..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_20_2.html +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.20.2 : Fail - 501 ms @ 2012-02-04T15:41:19Z

-

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edadbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

- -

- Case Outcome

Actual events differ from any expected.

- Expected:
{'OK': []}

- Observed:
[('message', '?', False)] -

-

Case Closing Behavior

The connection was failed by the wrong endpoint (FAILED)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: TIQj7T90U3MBd7UDV/Tkeg==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: mKV6+d9BjSDvtA484tge96+03HE=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeTrueTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1001The close code I sent in close frame (if any).
localCloseReasonGoing AwayThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
313
1291129
Total3134
-

Octets Transmitted by Chop Size

- - - - - - -
Chop SizeCountOctets
919
18118
2011201
Total3228
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a205449516a
-
               3754393055334d4264375544562f546b65673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a206d4b56
-
               362b6439426a534476744134383474676539362b303348453d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=5711da46, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
003 TX OCTETS: 81835711da46babc65
-
004 FAIL CONNECTION AFTER 0.500000 sec
-
005 RX OCTETS: 81013f
-
006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               ?
-
007 FAILING CONNECTION
-
008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=d65c7a61, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Going Away
-
009 TX OCTETS: 888cd65c7a61d5b53d0ebf321d41972b1b18
-
010 RX OCTETS: 8800
-
011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
012 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_20_3.html b/autobahn reports/servers/tootallnate_websocket_case_6_20_3.html deleted file mode 100644 index 4ae15cf28..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_20_3.html +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.20.3 : Fail - 501 ms @ 2012-02-04T15:41:20Z

-

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edae80

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

- -

- Case Outcome

Actual events differ from any expected.

- Expected:
{'OK': []}

- Observed:
[('message', '?', False)] -

-

Case Closing Behavior

The connection was failed by the wrong endpoint (FAILED)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: 0CmjM4vLTi5KG77RquVjKQ==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: rsX/MnJlDY6UxbWeADBpCfev0IQ=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeTrueTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1001The close code I sent in close frame (if any).
localCloseReasonGoing AwayThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
313
1291129
Total3134
-

Octets Transmitted by Chop Size

- - - - - - -
Chop SizeCountOctets
919
18118
2011201
Total3228
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2030436d6a
-
               4d34764c5469354b473737527175566a4b513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20727358
-
               2f4d6e4a6c445936557862576541444270436665763049513d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=ccfdb4e5, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
003 TX OCTETS: 8183ccfdb4e5215334
-
004 FAIL CONNECTION AFTER 0.500000 sec
-
005 RX OCTETS: 81013f
-
006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               ?
-
007 FAILING CONNECTION
-
008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=459bc411, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Going Away
-
009 TX OCTETS: 888c459bc4114672837e2cf5a33104eca568
-
010 RX OCTETS: 8800
-
011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
012 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_20_4.html b/autobahn reports/servers/tootallnate_websocket_case_6_20_4.html deleted file mode 100644 index f555f5ce8..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_20_4.html +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.20.4 : Fail - 501 ms @ 2012-02-04T15:41:20Z

-

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edafbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

- -

- Case Outcome

Actual events differ from any expected.

- Expected:
{'OK': []}

- Observed:
[('message', '?', False)] -

-

Case Closing Behavior

The connection was failed by the wrong endpoint (FAILED)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: 9Xx5xE9r30LfukV3JZQxMg==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: IsjbPBLW/m9/EBoONc42PJANrK0=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeTrueTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1001The close code I sent in close frame (if any).
localCloseReasonGoing AwayThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
313
1291129
Total3134
-

Octets Transmitted by Chop Size

- - - - - - -
Chop SizeCountOctets
919
18118
2011201
Total3228
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2039587835
-
               7845397233304c66756b56334a5a51784d673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a2049736a
-
               6250424c572f6d392f45426f4f4e633432504a414e724b303d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=a58665e4, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
003 TX OCTETS: 8183a58665e44829da
-
004 FAIL CONNECTION AFTER 0.500000 sec
-
005 RX OCTETS: 81013f
-
006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               ?
-
007 FAILING CONNECTION
-
008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=3ec8dc0c, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Going Away
-
009 TX OCTETS: 888c3ec8dc0c3d219b6357a6bb2c7fbfbd75
-
010 RX OCTETS: 8800
-
011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
012 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_20_5.html b/autobahn reports/servers/tootallnate_websocket_case_6_20_5.html deleted file mode 100644 index bdea9156b..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_20_5.html +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.20.5 : Fail - 501 ms @ 2012-02-04T15:41:21Z

-

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edb080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

- -

- Case Outcome

Actual events differ from any expected.

- Expected:
{'OK': []}

- Observed:
[('message', '?', False)] -

-

Case Closing Behavior

The connection was failed by the wrong endpoint (FAILED)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: kaPj4eOJvtYO/p1+SUWjFA==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: gjAUZ7QN+QSE+ENjYD8MxlmR9fg=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeTrueTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1001The close code I sent in close frame (if any).
localCloseReasonGoing AwayThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
313
1291129
Total3134
-

Octets Transmitted by Chop Size

- - - - - - -
Chop SizeCountOctets
919
18118
2011201
Total3228
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a206b61506a
-
               34654f4a7674594f2f70312b5355576a46413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20676a41
-
               555a37514e2b5153452b454e6a5944384d786c6d523966673d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=a90dd396, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
003 TX OCTETS: 8183a90dd39644bd53
-
004 FAIL CONNECTION AFTER 0.500000 sec
-
005 RX OCTETS: 81013f
-
006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               ?
-
007 FAILING CONNECTION
-
008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=e0ae2699, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Going Away
-
009 TX OCTETS: 888ce0ae2699e34761f689c041b9a1d947e0
-
010 RX OCTETS: 8800
-
011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
012 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_20_6.html b/autobahn reports/servers/tootallnate_websocket_case_6_20_6.html deleted file mode 100644 index 9b219d476..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_20_6.html +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.20.6 : Fail - 501 ms @ 2012-02-04T15:41:21Z

-

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edbe80

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

- -

- Case Outcome

Actual events differ from any expected.

- Expected:
{'OK': []}

- Observed:
[('message', '?', False)] -

-

Case Closing Behavior

The connection was failed by the wrong endpoint (FAILED)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: D3AO5HGDbIPBkfq4kMmnpA==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: hv3czFZ5FFyETGd0ABBqDf9iiMs=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeTrueTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1001The close code I sent in close frame (if any).
localCloseReasonGoing AwayThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
313
1291129
Total3134
-

Octets Transmitted by Chop Size

- - - - - - -
Chop SizeCountOctets
919
18118
2011201
Total3228
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a204433414f
-
               35484744624950426b6671346b4d6d6e70413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20687633
-
               637a465a3546467945544764304142427144663969694d733d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=5bd63f0e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
003 TX OCTETS: 81835bd63f0eb668bf
-
004 FAIL CONNECTION AFTER 0.500000 sec
-
005 RX OCTETS: 81013f
-
006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               ?
-
007 FAILING CONNECTION
-
008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=022b8ad0, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Going Away
-
009 TX OCTETS: 888c022b8ad001c2cdbf6b45edf0435ceba9
-
010 RX OCTETS: 8800
-
011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
012 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_20_7.html b/autobahn reports/servers/tootallnate_websocket_case_6_20_7.html deleted file mode 100644 index f36f88589..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_20_7.html +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.20.7 : Fail - 501 ms @ 2012-02-04T15:41:22Z

-

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

- -

- Case Outcome

Actual events differ from any expected.

- Expected:
{'OK': []}

- Observed:
[('message', '?', False)] -

-

Case Closing Behavior

The connection was failed by the wrong endpoint (FAILED)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: W6Be3rM7Egj4hNIscD5zEA==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: G36lsKHfg04ueUxN92iO/nJvKO8=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeTrueTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1001The close code I sent in close frame (if any).
localCloseReasonGoing AwayThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
313
1291129
Total3134
-

Octets Transmitted by Chop Size

- - - - - - -
Chop SizeCountOctets
919
18118
2011201
Total3228
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2057364265
-
               33724d3745676a34684e49736344357a45413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20473336
-
               6c734b4866673034756555784e3932694f2f6e4a764b4f383d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=af65e8a8, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
003 TX OCTETS: 8183af65e8a842da57
-
004 FAIL CONNECTION AFTER 0.500000 sec
-
005 RX OCTETS: 81013f
-
006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               ?
-
007 FAILING CONNECTION
-
008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=87dcfe76, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Going Away
-
009 TX OCTETS: 888c87dcfe768435b919eeb29956c6ab9f0f
-
010 RX OCTETS: 8800
-
011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
012 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_21_1.html b/autobahn reports/servers/tootallnate_websocket_case_6_21_1.html deleted file mode 100644 index b23b374b9..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_21_1.html +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.21.1 : Fail - 501 ms @ 2012-02-04T15:41:22Z

-

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

eda080edb080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

- -

- Case Outcome

Actual events differ from any expected.

- Expected:
{'OK': []}

- Observed:
[('message', '\xf0\x90\x80\x80', False)] -

-

Case Closing Behavior

The connection was failed by the wrong endpoint (FAILED)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: 4+A0RTBdoXG1pq8BzP6v7A==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: hOGfIW+mXTQpFxEAAzhJQZBq3D0=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeTrueTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1001The close code I sent in close frame (if any).
localCloseReasonGoing AwayThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
616
1291129
Total3137
-

Octets Transmitted by Chop Size

- - - - - - -
Chop SizeCountOctets
12112
18118
2011201
Total3231
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a20342b4130
-
               525442646f584731707138427a50367637413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20684f47
-
               6649572b6d5854517046784541417a684a515a42713344303d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=fa43fe25, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
003 TX OCTETS: 8186fa43fe2517e37ec84ac3
-
004 FAIL CONNECTION AFTER 0.500000 sec
-
005 RX OCTETS: 8104f0908080
-
006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               𐀀
-
007 FAILING CONNECTION
-
008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=9d22e55b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Going Away
-
009 TX OCTETS: 888c9d22e55b9ecba234f44c827bdc558422
-
010 RX OCTETS: 8800
-
011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
012 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_21_2.html b/autobahn reports/servers/tootallnate_websocket_case_6_21_2.html deleted file mode 100644 index 1df9c5686..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_21_2.html +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.21.2 : Fail - 501 ms @ 2012-02-04T15:41:23Z

-

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

eda080edbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

- -

- Case Outcome

Actual events differ from any expected.

- Expected:
{'OK': []}

- Observed:
[('message', '\xf0\x90\x8f\xbf', False)] -

-

Case Closing Behavior

The connection was failed by the wrong endpoint (FAILED)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: iPuOK+BgWhbmEfm05SYBXA==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: nFtyv6PfaFkNa7HnveKzhe3IZ30=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeTrueTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1001The close code I sent in close frame (if any).
localCloseReasonGoing AwayThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
616
1291129
Total3137
-

Octets Transmitted by Chop Size

- - - - - - -
Chop SizeCountOctets
12112
18118
2011201
Total3231
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a206950754f
-
               4b2b42675768626d45666d303553594258413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a206e4674
-
               797636506661466b4e6137486e76654b7a686533495a33303d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=3cf02dbc, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
003 TX OCTETS: 81863cf02dbcd150ad51834f
-
004 FAIL CONNECTION AFTER 0.500000 sec
-
005 RX OCTETS: 8104f0908fbf
-
006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               𐏿
-
007 FAILING CONNECTION
-
008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=28c01a4b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Going Away
-
009 TX OCTETS: 888c28c01a4b2b295d2441ae7d6b69b77b32
-
010 RX OCTETS: 8800
-
011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
012 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_21_3.html b/autobahn reports/servers/tootallnate_websocket_case_6_21_3.html deleted file mode 100644 index 604ab44f1..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_21_3.html +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.21.3 : Fail - 501 ms @ 2012-02-04T15:41:23Z

-

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edadbfedb080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

- -

- Case Outcome

Actual events differ from any expected.

- Expected:
{'OK': []}

- Observed:
[('message', '\xf3\xaf\xb0\x80', False)] -

-

Case Closing Behavior

The connection was failed by the wrong endpoint (FAILED)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: IxlOTYbZXdgzf0DqE51BTQ==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: /BBFWqSURVgKaSzwpkh/xBflPdw=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeTrueTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1001The close code I sent in close frame (if any).
localCloseReasonGoing AwayThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
616
1291129
Total3137
-

Octets Transmitted by Chop Size

- - - - - - -
Chop SizeCountOctets
12112
18118
2011201
Total3231
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2049786c4f
-
               5459625a5864677a663044714535314254513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a202f4242
-
               46577153555256674b61537a77706b682f7842666c5064773d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=826ee366, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
003 TX OCTETS: 8186826ee3666fc35c8b32ee
-
004 FAIL CONNECTION AFTER 0.500000 sec
-
005 RX OCTETS: 8104f3afb080
-
006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               󯰀
-
007 FAILING CONNECTION
-
008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=bebc7f7d, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Going Away
-
009 TX OCTETS: 888cbebc7f7dbd553812d7d2185dffcb1e04
-
010 RX OCTETS: 8800
-
011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
012 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_21_4.html b/autobahn reports/servers/tootallnate_websocket_case_6_21_4.html deleted file mode 100644 index 4e24ef1ac..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_21_4.html +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.21.4 : Fail - 501 ms @ 2012-02-04T15:41:24Z

-

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edadbfedbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

- -

- Case Outcome

Actual events differ from any expected.

- Expected:
{'OK': []}

- Observed:
[('message', '\xf3\xaf\xbf\xbf', False)] -

-

Case Closing Behavior

The connection was failed by the wrong endpoint (FAILED)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: 2dVkikWX/+l/pkvKSrgYMg==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: FZu8BS6bWe/A9bdToW/oxTah8fI=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeTrueTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1001The close code I sent in close frame (if any).
localCloseReasonGoing AwayThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
616
1291129
Total3137
-

Octets Transmitted by Chop Size

- - - - - - -
Chop SizeCountOctets
12112
18118
2011201
Total3231
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a203264566b
-
               696b57582f2b6c2f706b764b537267594d673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20465a75
-
               384253366257652f41396264546f572f6f785461683866493d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=0a2a18c5, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
003 TX OCTETS: 81860a2a18c5e787a728b595
-
004 FAIL CONNECTION AFTER 0.500000 sec
-
005 RX OCTETS: 8104f3afbfbf
-
006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               󯿿
-
007 FAILING CONNECTION
-
008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=16433ff0, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Going Away
-
009 TX OCTETS: 888c16433ff015aa789f7f2d58d057345e89
-
010 RX OCTETS: 8800
-
011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
012 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_21_5.html b/autobahn reports/servers/tootallnate_websocket_case_6_21_5.html deleted file mode 100644 index 1dde15cf6..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_21_5.html +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.21.5 : Fail - 502 ms @ 2012-02-04T15:41:24Z

-

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edae80edb080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

- -

- Case Outcome

Actual events differ from any expected.

- Expected:
{'OK': []}

- Observed:
[('message', '\xf3\xb0\x80\x80', False)] -

-

Case Closing Behavior

The connection was failed by the wrong endpoint (FAILED)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: PM0Ykka0trj5Ks54acrZRw==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: 0rB4RXL5G/swTTfmBlmWgyUkAZQ=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeTrueTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1001The close code I sent in close frame (if any).
localCloseReasonGoing AwayThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
616
1291129
Total3137
-

Octets Transmitted by Chop Size

- - - - - - -
Chop SizeCountOctets
12112
18118
2011201
Total3231
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a20504d3059
-
               6b6b613074726a354b7335346163725a52773d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20307242
-
               3452584c35472f73775454666d426c6d576779556b415a513d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=a4035bc2, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
003 TX OCTETS: 8186a4035bc249addb2f1483
-
004 FAIL CONNECTION AFTER 0.500000 sec
-
005 RX OCTETS: 8104f3b08080
-
006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               󰀀
-
007 FAILING CONNECTION
-
008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=04cc48c4, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Going Away
-
009 TX OCTETS: 888c04cc48c407250fab6da22fe445bb29bd
-
010 RX OCTETS: 8800
-
011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
012 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_21_6.html b/autobahn reports/servers/tootallnate_websocket_case_6_21_6.html deleted file mode 100644 index 50ce3e2d0..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_21_6.html +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.21.6 : Fail - 501 ms @ 2012-02-04T15:41:25Z

-

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edae80edbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

- -

- Case Outcome

Actual events differ from any expected.

- Expected:
{'OK': []}

- Observed:
[('message', '\xf3\xb0\x8f\xbf', False)] -

-

Case Closing Behavior

The connection was failed by the wrong endpoint (FAILED)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: 3HjHLxTtDIUsMmTglsvCEQ==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: jlA618sXEEsPiOOzZdRjjkUAls8=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeTrueTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1001The close code I sent in close frame (if any).
localCloseReasonGoing AwayThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
616
1291129
Total3137
-

Octets Transmitted by Chop Size

- - - - - - -
Chop SizeCountOctets
12112
18118
2011201
Total3231
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2033486a48
-
               4c785474444955734d6d54676c73764345513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a206a6c41
-
               363138735845457350694f4f7a5a64526a6a6b55416c73383d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=76c7ad31, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
003 TX OCTETS: 818676c7ad319b692ddcc978
-
004 FAIL CONNECTION AFTER 0.500000 sec
-
005 RX OCTETS: 8104f3b08fbf
-
006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               󰏿
-
007 FAILING CONNECTION
-
008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=e1eb5d93, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Going Away
-
009 TX OCTETS: 888ce1eb5d93e2021afc88853ab3a09c3cea
-
010 RX OCTETS: 8800
-
011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
012 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_21_7.html b/autobahn reports/servers/tootallnate_websocket_case_6_21_7.html deleted file mode 100644 index ddde6cbfb..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_21_7.html +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.21.7 : Fail - 501 ms @ 2012-02-04T15:41:25Z

-

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edafbfedb080

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

- -

- Case Outcome

Actual events differ from any expected.

- Expected:
{'OK': []}

- Observed:
[('message', '\xf4\x8f\xb0\x80', False)] -

-

Case Closing Behavior

The connection was failed by the wrong endpoint (FAILED)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: ZIp0mbBtxEyXUFyOCJ+3+Q==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: DwOv/co2P4JILRERO9r1utPeUz4=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeTrueTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1001The close code I sent in close frame (if any).
localCloseReasonGoing AwayThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
616
1291129
Total3137
-

Octets Transmitted by Chop Size

- - - - - - -
Chop SizeCountOctets
12112
18118
2011201
Total3231
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a205a497030
-
               6d624274784579585546794f434a2b332b513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a2044774f
-
               762f636f3250344a494c5245524f39723175745065557a343d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=2ae4c89f, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
003 TX OCTETS: 81862ae4c89fc74b77729a64
-
004 FAIL CONNECTION AFTER 0.500000 sec
-
005 RX OCTETS: 8104f48fb080
-
006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               􏰀
-
007 FAILING CONNECTION
-
008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=367f674f, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Going Away
-
009 TX OCTETS: 888c367f674f359620205f11006f77080636
-
010 RX OCTETS: 8800
-
011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
012 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_21_8.html b/autobahn reports/servers/tootallnate_websocket_case_6_21_8.html deleted file mode 100644 index dee22d7cd..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_21_8.html +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.21.8 : Fail - 501 ms @ 2012-02-04T15:41:26Z

-

Case Description

Send a text message with payload which is not valid UTF-8 in one fragment.

MESSAGE:

edafbfedbfbf

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

- -

- Case Outcome

Actual events differ from any expected.

- Expected:
{'OK': []}

- Observed:
[('message', '\xf4\x8f\xbf\xbf', False)] -

-

Case Closing Behavior

The connection was failed by the wrong endpoint (FAILED)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: OaUcFV4MSdP7nxBXK+S38Q==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: j0hDBbytdKrxduaM2lyuEGp5b+M=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeTrueTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1001The close code I sent in close frame (if any).
localCloseReasonGoing AwayThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
616
1291129
Total3137
-

Octets Transmitted by Chop Size

- - - - - - -
Chop SizeCountOctets
12112
18118
2011201
Total3231
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a204f615563
-
               4656344d536450376e7842584b2b533338513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a206a3068
-
               4442627974644b72786475614d326c797545477035622b4d3d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=7c269b4e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
003 TX OCTETS: 81867c269b4e918924a3c399
-
004 FAIL CONNECTION AFTER 0.500000 sec
-
005 RX OCTETS: 8104f48fbfbf
-
006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               􏿿
-
007 FAILING CONNECTION
-
008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=9603c3bf, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Going Away
-
009 TX OCTETS: 888c9603c3bf95ea84d0ff6da49fd774a2c6
-
010 RX OCTETS: 8800
-
011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
012 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_3_1.html b/autobahn reports/servers/tootallnate_websocket_case_6_3_1.html deleted file mode 100644 index d7a1a8064..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_3_1.html +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.3.1 : Fail - 1001 ms @ 2012-02-04T15:41:08Z

-

Case Description

Send invalid UTF-8 text message unfragmented.

MESSAGE:
κόσμεedited
cebae1bdb9cf83cebcceb5eda080656469746564

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

- -

- Case Outcome

Actual events differ from any expected.

- Expected:
{'OK': []}

- Observed:
[('message', '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5?edited', False)] -

-

Case Closing Behavior

The connection was failed by the wrong endpoint (FAILED)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: giRxaADMiudnciYu2X3Ntw==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: VbnTiF6FgXdncLIxXEj+z/bK0y8=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeTrueTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1001The close code I sent in close frame (if any).
localCloseReasonGoing AwayThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
20120
1291129
Total3151
-

Octets Transmitted by Chop Size

- - - - - - -
Chop SizeCountOctets
18118
26126
2011201
Total3245
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2067695278
-
               6141444d6975646e636959753258334e74773d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a2056626e
-
               54694636466758646e634c497858456a2b7a2f624b3079383d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=35445993, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               κόσμεedited
-
003 TX OCTETS: 819435445993fbfeb82e8c8bda5d898aec7e95c43cf75c303cf7
-
004 FAIL CONNECTION AFTER 1.000000 sec
-
005 RX OCTETS: 8112cebae1bdb9cf83cebcceb53f656469746564
-
006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               κόσμε?edited
-
007 FAILING CONNECTION
-
008 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=79a473a8, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Going Away
-
009 TX OCTETS: 888c79a473a87a4d34c710ca148838d312d1
-
010 RX OCTETS: 8800
-
011 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
012 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_3_2.html b/autobahn reports/servers/tootallnate_websocket_case_6_3_2.html deleted file mode 100644 index ff7dfa5f6..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_3_2.html +++ /dev/null @@ -1,365 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.3.2 : Fail - 1002 ms @ 2012-02-04T15:41:09Z

-

Case Description

Send invalid UTF-8 text message in fragments of 1 octet, resulting in frames ending on positions which are not code point ends.

MESSAGE:
κόσμεedited
cebae1bdb9cf83cebcceb5eda080656469746564

-

Case Expectation

The connection is failed immediately, since the payload is not valid UTF-8.

- -

- Case Outcome

Actual events differ from any expected.

- Expected:
{'OK': []}

- Observed:
[('message', '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5?edited', False)] -

-

Case Closing Behavior

The connection was failed by the wrong endpoint (FAILED)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: +0fzXkyvkSjPhQlQQX66GA==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: LgO0/Am1g9xEfcTU2myHtnWvixQ=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeTrueTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1001The close code I sent in close frame (if any).
localCloseReasonGoing AwayThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
20120
1291129
Total3151
-

Octets Transmitted by Chop Size

- - - - - - - -
Chop SizeCountOctets
616
720140
18118
2011201
Total23365
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - - -
OpcodeCount
020
11
81
Total22
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a202b30667a
-
               586b79766b536a5068516c515158363647413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a204c674f
-
               302f416d316739784566635455326d7948746e57766978513d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=False, RSV=0, MASK=b2f15302, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
003 TX OCTETS: 0181b2f153027c
-
004 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=c90d6517, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
005 TX OCTETS: 0081c90d651773
-
006 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=cd667264, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
007 TX OCTETS: 0081cd6672642c
-
008 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=024afe11, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
009 TX OCTETS: 0081024afe11bf
-
010 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=80b6c518, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
011 TX OCTETS: 008180b6c51839
-
012 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=62a7724e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
013 TX OCTETS: 008162a7724ead
-
014 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=42f77b2b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
015 TX OCTETS: 008142f77b2bc1
-
016 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=c1f0a0f1, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
017 TX OCTETS: 0081c1f0a0f10f
-
018 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=7f9e2838, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
019 TX OCTETS: 00817f9e2838c3
-
020 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=06f47c2c, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
021 TX OCTETS: 008106f47c2cc8
-
022 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=e139a1e1, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
023 TX OCTETS: 0081e139a1e154
-
024 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=d00820e1, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
025 TX OCTETS: 0081d00820e13d
-
026 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=a93ed31b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
027 TX OCTETS: 0081a93ed31b09
-
028 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=86fe9fa7, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
029 TX OCTETS: 008186fe9fa706
-
030 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=da4f747e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               e
-
031 TX OCTETS: 0081da4f747ebf
-
032 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=0906c2df, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               d
-
033 TX OCTETS: 00810906c2df6d
-
034 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=9c8128e5, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               i
-
035 TX OCTETS: 00819c8128e5f5
-
036 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=c0b043ed, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               t
-
037 TX OCTETS: 0081c0b043edb4
-
038 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=1723209b, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               e
-
039 TX OCTETS: 00811723209b72
-
040 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=a1194c70, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               d
-
041 TX OCTETS: 0081a1194c70c5
-
042 TX FRAME : OPCODE=0, FIN=True, RSV=0, MASK=73e98929, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
043 TX OCTETS: 808073e98929
-
044 FAIL CONNECTION AFTER 1.000000 sec
-
045 RX OCTETS: 8112cebae1bdb9cf83cebcceb53f656469746564
-
046 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               κόσμε?edited
-
047 FAILING CONNECTION
-
048 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=52a40872, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               Going Away
-
049 TX OCTETS: 888c52a40872514d4f1d3bca6f5213d3690b
-
050 RX OCTETS: 8800
-
051 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
052 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_4_1.html b/autobahn reports/servers/tootallnate_websocket_case_6_4_1.html deleted file mode 100644 index a7168b714..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_4_1.html +++ /dev/null @@ -1,318 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.4.1 : Non-Strict - 2004 ms @ 2012-02-04T15:41:10Z

-

Case Description

Send invalid UTF-8 text message in 3 fragments (frames). -First frame payload is valid, then wait, then 2nd frame which contains the payload making the sequence invalid, then wait, then 3rd frame with rest. -Note that PART1 and PART3 are valid UTF-8 in themselves, PART2 is a 0x11000 encoded as in the UTF-8 integer encoding scheme, but the codepoint is invalid (out of range). -

MESSAGE PARTS:
-PART1 = κόσμε (cebae1bdb9cf83cebcceb5)
-PART2 = (f4908080)
-PART3 = edited (656469746564)
-

-

Case Expectation

The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.

- -

- Case Outcome

Actual events match at least one expected.

- Expected:
{'NON-STRICT': [('timeout', 'A'), ('timeout', 'B')], 'OK': [('timeout', 'A')]}

- Observed:
[('timeout', 'A'), ('timeout', 'B')] -

-

Case Closing Behavior

Connection was properly closed (OK)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: UpAkZ//RWLrHfexyOMj3mw==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: HUMfuAwHoYZqlf78+bKZQrSw5t0=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeFalseTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeFalseTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1000The close code I sent in close frame (if any).
localCloseReasonNoneThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - -
Chop SizeCountOctets
212
1291129
Total2131
-

Octets Transmitted by Chop Size

- - - - - - - - -
Chop SizeCountOctets
818
10110
12112
17117
2011201
Total5248
-

Frames Received by Opcode

- - - - -
OpcodeCount
81
Total1
-

Frames Transmitted by Opcode

- - - - - - -
OpcodeCount
02
11
81
Total4
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a205570416b
-
               5a2f2f52574c7248666578794f4d6a336d773d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a2048554d
-
               66754177486f595a716c6637382b624b5a517253773574303d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=False, RSV=0, MASK=7ae1bfb5, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               κόσμε
-
003 TX OCTETS: 018b7ae1bfb5b45b5e08c32e3c7bc62f0a
-
004 DELAY 1.000000 sec for TAG A
-
005 DELAY TIMEOUT on TAG A
-
006 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=98cbbec9, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
007 TX OCTETS: 008498cbbec96c5b3e49
-
008 DELAY 1.000000 sec for TAG B
-
009 DELAY TIMEOUT on TAG B
-
010 TX FRAME : OPCODE=0, FIN=True, RSV=0, MASK=61ebbee3, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               edited
-
011 TX OCTETS: 808661ebbee3048fd797048f
-
012 FAIL CONNECTION AFTER 1.000000 sec
-
013 RX OCTETS: 8800
-
014 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
015 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=6bde347c, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
016 TX OCTETS: 88826bde347c6836
-
017 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_4_2.html b/autobahn reports/servers/tootallnate_websocket_case_6_4_2.html deleted file mode 100644 index 8f4a7f7b7..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_4_2.html +++ /dev/null @@ -1,316 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.4.2 : Non-Strict - 2002 ms @ 2012-02-04T15:41:12Z

-

Case Description

Same as Case 6.4.1, but in 2nd frame, we send only up to and including the octet making the complete payload invalid. -

MESSAGE PARTS:
-PART1 = κόσμε (cebae1bdb9cf83cebcceb5f4)
-PART2 = (90)
-PART3 = edited (8080656469746564)
-

-

Case Expectation

The first frame is accepted, we expect to timeout on the first wait. The 2nd frame should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.

- -

- Case Outcome

Actual events match at least one expected.

- Expected:
{'NON-STRICT': [('timeout', 'A'), ('timeout', 'B')], 'OK': [('timeout', 'A')]}

- Observed:
[('timeout', 'A'), ('timeout', 'B')] -

-

Case Closing Behavior

Connection was properly closed (OK)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: Otomp9wsvzxBkXQi+T/5PQ==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: ATAg9VNP6aeymyNC0M0XC/eIdaU=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeFalseTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeFalseTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1000The close code I sent in close frame (if any).
localCloseReasonNoneThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - -
Chop SizeCountOctets
212
1291129
Total2131
-

Octets Transmitted by Chop Size

- - - - - - - - -
Chop SizeCountOctets
717
818
14114
18118
2011201
Total5248
-

Frames Received by Opcode

- - - - -
OpcodeCount
81
Total1
-

Frames Transmitted by Opcode

- - - - - - -
OpcodeCount
02
11
81
Total4
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a204f746f6d
-
               70397773767a78426b5851692b542f3550513d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20415441
-
               6739564e50366165796d794e43304d3058432f65496461553d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=False, RSV=0, MASK=a71060dd, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               κόσμε
-
003 TX OCTETS: 018ca71060dd69aa81601edfe3131bded529
-
004 DELAY 1.000000 sec for TAG A
-
005 DELAY TIMEOUT on TAG A
-
006 TX FRAME : OPCODE=0, FIN=False, RSV=0, MASK=0dd9f64a, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
007 TX OCTETS: 00810dd9f64a9d
-
008 DELAY 1.000000 sec for TAG B
-
009 DELAY TIMEOUT on TAG B
-
010 TX FRAME : OPCODE=0, FIN=True, RSV=0, MASK=ac34c7f6, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               edited
-
011 TX OCTETS: 8088ac34c7f62cb4a292c540a292
-
012 FAIL CONNECTION AFTER 1.000000 sec
-
013 RX OCTETS: 8800
-
014 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
015 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=a38e00e8, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
016 TX OCTETS: 8882a38e00e8a066
-
017 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_4_3.html b/autobahn reports/servers/tootallnate_websocket_case_6_4_3.html deleted file mode 100644 index 9ffeeeb2b..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_4_3.html +++ /dev/null @@ -1,312 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.4.3 : Non-Strict - 2003 ms @ 2012-02-04T15:41:14Z

-

Case Description

Same as Case 6.4.1, but we send message not in 3 frames, but in 3 chops of the same message frame. -

MESSAGE PARTS:
-PART1 = κόσμε (cebae1bdb9cf83cebcceb5)
-PART2 = (f4908080)
-PART3 = edited (656469746564)
-

-

Case Expectation

The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.

- -

- Case Outcome

Actual events match at least one expected.

- Expected:
{'NON-STRICT': [('timeout', 'A'), ('timeout', 'B')], 'OK': [('timeout', 'A')]}

- Observed:
[('timeout', 'A'), ('timeout', 'B')] -

-

Case Closing Behavior

Connection was properly closed (OK)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: m117Mgw+RaqCkrbu2OKKcg==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: gsC/JxGAgNUyZuw4sE8CAkazaoY=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeFalseTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeFalseTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1000The close code I sent in close frame (if any).
localCloseReasonNoneThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - -
Chop SizeCountOctets
212
1291129
Total2131
-

Octets Transmitted by Chop Size

- - - - - - - - -
Chop SizeCountOctets
414
6318
818
11111
2011201
Total7242
-

Frames Received by Opcode

- - - - -
OpcodeCount
81
Total1
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
01
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a206d313137
-
               4d67772b526171436b726275324f4b4b63673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20677343
-
               2f4a784741674e55795a75773473453843416b617a616f593d0d0a0d0a
-
002 TX OCTETS: 0195fffcbd31
-
003 TX OCTETS: 31465c8c46333eff433208
-
004 DELAY 1.000000 sec for TAG A
-
005 DELAY TIMEOUT on TAG A
-
006 TX OCTETS: c56f7c3d
-
007 DELAY 1.000000 sec for TAG B
-
008 DELAY TIMEOUT on TAG B
-
009 TX OCTETS: 549b95c9549b
-
010 TX FRAME : OPCODE=0, FIN=True, RSV=0, MASK=3da3495d, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
011 TX OCTETS: 80803da3495d
-
012 FAIL CONNECTION AFTER 1.000000 sec
-
013 RX OCTETS: 8800
-
014 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
015 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=c041f88e, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
016 TX OCTETS: 8882c041f88ec3a9
-
017 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_4_4.html b/autobahn reports/servers/tootallnate_websocket_case_6_4_4.html deleted file mode 100644 index e5344a36a..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_4_4.html +++ /dev/null @@ -1,312 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.4.4 : Non-Strict - 2003 ms @ 2012-02-04T15:41:16Z

-

Case Description

Same as Case 6.4.2, but we send message not in 3 frames, but in 3 chops of the same message frame. -

MESSAGE PARTS:
-PART1 = κόσμε (cebae1bdb9cf83cebcceb5f4)
-PART2 = (90)
-PART3 = ()
-

-

Case Expectation

The first chop is accepted, we expect to timeout on the first wait. The 2nd chop should be rejected immediately (fail fast on UTF-8). If we timeout, we expect the connection is failed at least then, since the complete message payload is not valid UTF-8.

- -

- Case Outcome

Actual events match at least one expected.

- Expected:
{'NON-STRICT': [('timeout', 'A'), ('timeout', 'B')], 'OK': [('timeout', 'A')]}

- Observed:
[('timeout', 'A'), ('timeout', 'B')] -

-

Case Closing Behavior

Connection was properly closed (OK)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: XEU6Kj9j1CXrVXerZqPZug==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: m+kDAJTxrTpxSNSgA+UFnbv9xr8=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeFalseTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeFalseTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1000The close code I sent in close frame (if any).
localCloseReasonNoneThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - -
Chop SizeCountOctets
212
1291129
Total2131
-

Octets Transmitted by Chop Size

- - - - - - - - -
Chop SizeCountOctets
111
6212
8216
12112
2011201
Total7242
-

Frames Received by Opcode

- - - - -
OpcodeCount
81
Total1
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
01
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a2058455536
-
               4b6a396a31435872565865725a71505a75673d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a206d2b6b
-
               44414a547872547078534e5367412b55466e6276397872383d0d0a0d0a
-
002 TX OCTETS: 019564bd3c28
-
003 TX OCTETS: aa07dd95dd72bfe6d87389dc
-
004 DELAY 1.000000 sec for TAG A
-
005 DELAY TIMEOUT on TAG A
-
006 TX OCTETS: f4
-
007 DELAY 1.000000 sec for TAG B
-
008 DELAY TIMEOUT on TAG B
-
009 TX OCTETS: 3dbc4d00d4484d00
-
010 TX FRAME : OPCODE=0, FIN=True, RSV=0, MASK=3a12500f, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
011 TX OCTETS: 80803a12500f
-
012 FAIL CONNECTION AFTER 1.000000 sec
-
013 RX OCTETS: 8800
-
014 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
015 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=b230610d, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
016 TX OCTETS: 8882b230610db1d8
-
017 TCP DROPPED BY PEER
-
-

- - diff --git a/autobahn reports/servers/tootallnate_websocket_case_6_5_1.html b/autobahn reports/servers/tootallnate_websocket_case_6_5_1.html deleted file mode 100644 index a75d90b7f..000000000 --- a/autobahn reports/servers/tootallnate_websocket_case_6_5_1.html +++ /dev/null @@ -1,303 +0,0 @@ - - - - - - - - - -
-
WebSockets Protocol Test Report
-
Autobahn WebSockets
-
-

tootallnate/websocket - Case 6.5.1 : Pass - 2 ms @ 2012-02-04T15:41:18Z

-

Case Description

Send a text message with payload which is valid UTF-8 in one fragment.

MESSAGE:
κόσμε
cebae1bdb9cf83cebcceb5

-

Case Expectation

The message is echo'ed back to us.

- -

- Case Outcome

Actual events match at least one expected.

- Expected:
{'OK': [('message', '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5', False)]}

- Observed:
[('message', '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5', False)] -

-

Case Closing Behavior

Connection was properly closed (OK)

-

-

Opening Handshake

-
GET / HTTP/1.1

-User-Agent: AutobahnWebSocketsTestSuite/0.4.10

-Host: localhost:9003

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Key: Ntl0ifPncXw3kcnMyqYI6A==

-Sec-WebSocket-Version: 13
-
HTTP/1.1 101 Switching Protocols

-Upgrade: websocket

-Connection: Upgrade

-Sec-WebSocket-Accept: EOTzOwYD8bhNnxS8IX/Rcx+8Ess=
-

-

Closing Behavior

- - - - - - - - - - - - - - -
KeyValueDescription
isServerFalseTrue, iff I (the fuzzer) am a server, and the peer is a client.
closedByMeTrueTrue, iff I have initiated closing handshake (that is, did send close first).
failedByMeFalseTrue, iff I have failed the WS connection (i.e. due to protocol error). Failing can be either by initiating closing handshake or brutal drop TCP.
droppedByMeFalseTrue, iff I dropped the TCP connection.
wasCleanTrueTrue, iff full WebSockets closing handshake was performed (close frame sent and received) _and_ the server dropped the TCP (which is its responsibility).
wasNotCleanReasonNoneWhen wasClean == False, the reason what happened.
wasServerConnectionDropTimeoutFalseWhen we are a client, and we expected the server to drop the TCP, but that didn't happen in time, this gets True.
wasCloseHandshakeTimeoutFalseWhen we initiated a closing handshake, but the peer did not respond in time, this gets True.
localCloseCode1000The close code I sent in close frame (if any).
localCloseReasonNoneThe close reason I sent in close frame (if any).
remoteCloseCodeNoneThe close code the peer sent me in close frame (if any).
remoteCloseReasonNoneThe close reason the peer sent me in close frame (if any).


-

Wire Statistics

-

Octets Received by Chop Size

- - - - - - -
Chop SizeCountOctets
212
13113
1291129
Total3144
-

Octets Transmitted by Chop Size

- - - - - - -
Chop SizeCountOctets
818
17117
2011201
Total3226
-

Frames Received by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

Frames Transmitted by Opcode

- - - - - -
OpcodeCount
11
81
Total2
-

-

Wire Log

-
-
000 TX OCTETS: 474554202f20485454502f312e310d0a557365722d4167656e743a204175746f6261686e576562536f636b65747354657374
-
               53756974652f302e342e31300d0a486f73743a206c6f63616c686f73743a393030330d0a557067726164653a20776562736f
-
               636b65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4b65793a204e746c30
-
               6966506e635877336b636e4d7971594936413d3d0d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a0d
-
               0a
-
001 RX OCTETS: 485454502f312e312031303120537769746368696e672050726f746f636f6c730d0a557067726164653a20776562736f636b
-
               65740d0a436f6e6e656374696f6e3a20557067726164650d0a5365632d576562536f636b65742d4163636570743a20454f54
-
               7a4f7759443862684e6e78533849582f5263782b384573733d0d0a0d0a
-
002 TX FRAME : OPCODE=1, FIN=True, RSV=0, MASK=a3eccd2d, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               κόσμε
-
003 TX OCTETS: 818ba3eccd2d6d562c901a234ee31f2278
-
004 FAIL CONNECTION AFTER 0.500000 sec
-
005 RX OCTETS: 810bcebae1bdb9cf83cebcceb5
-
006 RX FRAME : OPCODE=1, FIN=True, RSV=0, MASKED=False, MASK=None
-
               κόσμε
-
007 TX FRAME : OPCODE=8, FIN=True, RSV=0, MASK=77c74274, PAYLOAD-REPEAT-LEN=None, CHOPSIZE=None, SYNC=False
-
               
-
008 TX OCTETS: 888277c74274742f
-
009 RX OCTETS: 8800
-
010 RX FRAME : OPCODE=8, FIN=True, RSV=0, MASKED=False, MASK=None
-
011 TCP DROPPED BY PEER
-
-

- - diff --git a/build.gradle b/build.gradle index a87d144f7..e6891cf02 100644 --- a/build.gradle +++ b/build.gradle @@ -1,44 +1,45 @@ -apply plugin: 'java' -apply plugin: 'idea' -apply plugin: 'maven' - +plugins { + id 'java' + id 'idea' + id 'maven-publish' +} repositories { mavenLocal() mavenCentral() } -group = 'org.java_websocket' -version = '1.2.1-SNAPSHOT' -sourceCompatibility = 1.6 -targetCompatibility = 1.6 - -configurations { - deployerJars -} +group = 'org.java-websocket' +version = '1.6.1-SNAPSHOT' +sourceCompatibility = 1.8 +targetCompatibility = 1.8 -configure(install.repositories.mavenInstaller) { - pom.version = project.version - pom.groupId = "org.java_websocket" - pom.artifactId = 'Java-WebSocket' +compileJava { + options.compilerArgs += ['-encoding', 'UTF-8'] } -dependencies { - deployerJars "org.apache.maven.wagon:wagon-webdav:1.0-beta-2" +javadoc { + options.encoding = 'UTF-8' } +publishing { + publications { + maven(MavenPublication) { + groupId = project.group + artifactId = 'Java-WebSocket' + version = project.version -//deploy to maven repository -//uploadArchives { -// repositories.mavenDeployer { -// configuration = configurations.deployerJars -// repository(url: repositoryUrl) { -// authentication(userName: repositoryUsername, password: repositoryPassword) -// } -// } -//} + from components.java + } + } +} -task wrapper(type: Wrapper) { - gradleVersion = '1.4' +dependencies { + implementation group: 'org.slf4j', name: 'slf4j-api', version: '2.0.15' + testImplementation group: 'org.slf4j', name: 'slf4j-simple', version: '2.0.15' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.11.4' } +test { + useJUnitPlatform() // This is required for JUnit 5 +} diff --git a/build.xml b/build.xml deleted file mode 100644 index 3d33c2745..000000000 --- a/build.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml new file mode 100644 index 000000000..afa6e1544 --- /dev/null +++ b/checkstyle-suppressions.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/dist/java_websocket.jar b/dist/java_websocket.jar deleted file mode 100644 index bb5caeb4e..000000000 Binary files a/dist/java_websocket.jar and /dev/null differ diff --git a/CodeFormatterProfile.xml b/eclipse-java-google-style.xml similarity index 72% rename from CodeFormatterProfile.xml rename to eclipse-java-google-style.xml index 73325dedd..7bb6804eb 100644 --- a/CodeFormatterProfile.xml +++ b/eclipse-java-google-style.xml @@ -1,35 +1,41 @@ - - + + - + - + + - - + - - + + + - + + + + - - + + + + @@ -38,165 +44,204 @@ - + + + + - + - + + + - + + - - - + + + + - + - - - + + + - - - + + + + - + + - - - + + + + - + + - + - + + + - + - - + + + - - + + - + + + + + + + + - - - + + + + + + - + - + - + - - + + + + - + + - + + - + + - - + + - - + - + + + + - + + + + - + - - + + - - + + - + + + + - + - - - - + + + + + + @@ -205,73 +250,86 @@ + - - + + - - + - + - + + - + - + + - - + + + - - - - - + + + + - + - + - + + - + + - + - - + + + + + + - - + + + + + - + + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 0087cd3b1..000000000 Binary files a/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 7dfc31e8e..000000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Wed Jul 02 20:43:27 CEST 2014 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-1.4-bin.zip diff --git a/gradlew b/gradlew deleted file mode 100755 index 91a7e269e..000000000 --- a/gradlew +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env bash - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index aec99730b..000000000 --- a/gradlew.bat +++ /dev/null @@ -1,90 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/index.html b/index.html deleted file mode 100644 index 7e46cbb1e..000000000 --- a/index.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - TooTallNate/Java-WebSocket @ GitHub - - - - - - Fork me on GitHub - -
- -
- - - - -
- -

Java-WebSocket - by TooTallNate

- -
- A barebones WebSocket client and server implementation written in 100% Java. -
- - - - - - - - - - -

Authors

-

Davidiusdadi (DavidRohmer@web.de)
Bob Corsaro (bob@embed.ly)
Don Park (don@donpark.org)
David Rohmer (firstlastname@web.de)
swax (john.m.marshall@gmail.com)
Jarrod Ribble (jribble@netiq.com)
Julian Gautier (julian.gautier@alumni.neumont.edu)
Kristijan Sedlak (k.sedlak@mandatela.si)
morkai (lukasz@walukiewicz.eu)
Nathaniel Michael (natecmichael@gmail.com)
Nathan Rajlich (nathan@tootallnate.net)
sippykup (nobody@nowhere.com)

- - - -

Contact

-

Nathan Rajlich (nathan@tootallnate.net)

- - -

Download

-

- You can download this project in either - zip or - tar formats. -

-

You can also clone the project with Git - by running: -

$ git clone git://github.com/TooTallNate/Java-WebSocket
-

- - - -
- - - diff --git a/intellij-java-google-style.xml b/intellij-java-google-style.xml new file mode 100644 index 000000000..f3a6743ef --- /dev/null +++ b/intellij-java-google-style.xml @@ -0,0 +1,598 @@ + + + + + + diff --git a/pom.xml b/pom.xml index 0316b921c..fa2caa283 100644 --- a/pom.xml +++ b/pom.xml @@ -1,86 +1,298 @@ - - - org.sonatype.oss - oss-parent - 7 - - - scm:git:git@github.com:TooTallNate/Java-WebSocket.git - scm:git:git@github.com:TooTallNate/Java-WebSocket.git - git@github.com:TooTallNate/Java-WebSocket.git - + 4.0.0 org.java-websocket Java-WebSocket - 1.3.1-SNAPSHOT jar - Java WebSocket - http://java-websocket.org/ - A barebones WebSocket client and server implementation written in 100% Java + 1.6.1-SNAPSHOT + Java-WebSocket + A barebones WebSocket client and server implementation written 100% in Java + https://github.com/TooTallNate/Java-WebSocket + 1.8 + 1.8 UTF-8 - 1.6 - 1.0.0.RC24 + 2.0.16 + + + 5.11.4 + + + 6.4.0 + 3.1.1 + 3.10.1 + 1.6 + 3.3.0 + 3.5.0 + 3.4.1 + 3.2.1 + 1.6.13 + org.java-websocket:Java-WebSocket + marci4-github + https://sonarcloud.io + + + MIT License + https://github.com/TooTallNate/Java-WebSocket/blob/master/LICENSE + + + + https://github.com/TooTallNate/Java-WebSocket + + + https://github.com/TooTallNate/Java-WebSocket/issues + GitHub Issues + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + test + + + org.junit + junit-bom + ${junit.version} + pom + import + + + + + + + + biz.aQute.bnd + bnd-maven-plugin + ${bnd.maven.plugin.version} + + + + bnd-process + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.plugin.version} + + + default-compile + + compile + + + 8 + + + + + module-compile + compile + + compile + + + 9 + + ${project.basedir}/src/main/java9 + + true + + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven.gpg.plugin.version} + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven.jar.plugin.version} + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven.javadoc.plugin.version} + + src/main/java + -Xdoclint:none + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-shade-plugin + ${maven.shade.plugin.version} + + + org.apache.maven.plugins + maven-source-plugin + ${maven.source.plugin.version} + + + attach-sources + + jar-no-fork + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus.staging.maven.plugin.version} + + + + src/main/java + src/test/java - org.apache.maven.plugins - maven-compiler-plugin - 2.5.1 - - ${java.version} - ${java.version} - + biz.aQute.bnd + bnd-maven-plugin org.apache.maven.plugins - maven-source-plugin - 2.2.1 - - - attach-sources - - jar - - - + maven-compiler-plugin org.apache.maven.plugins - maven-javadoc-plugin - 2.9 + maven-checkstyle-plugin + ${maven.checkstyle.plugin.version} + + google_checks.xml + **/module-info.java + warning + checkstyle-suppressions.xml + checkstyle.suppressions.file + true + false + - attach-javadocs + check - jar + check + + + ossrh + + + performRelease + true + + + + + + biz.aQute.bnd + bnd-maven-plugin + + + org.apache.maven.plugins + maven-gpg-plugin + + + org.sonatype.plugins + nexus-staging-maven-plugin + true + + ossrh + false + https://oss.sonatype.org/ + + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + -Xdoclint:none + + + + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + - - junit - junit - 4.8.2 - test + org.slf4j + slf4j-api - - info.cukes - cucumber-junit - 1.0.0 + org.slf4j + slf4j-simple test - info.cukes - cucumber-java - ${cucumber.version} + org.junit.jupiter + junit-jupiter test @@ -88,55 +300,16 @@ TooTallNate Nathan Rajlich - nathan@tootallnate.net https://github.com/TooTallNate - - founder - + nathan@tootallnate.net - Davidiusdadi - David Rohmer - rohmer.david@gmail.com - https://github.com/Davidiusdadi - - maintainer - + marci4 + Marcel Prestel + https://github.com/marci4 + admin@marci4.de - - - MIT License - http://github.com/TooTallNate/Java-WebSocket/blob/master/LICENSE - - - - - release-sign-artifacts - - performReleasetrue - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.1 - - - sign-artifacts - verify - - sign - - - - - rohmer.david@gmail.com - - - - - - + + diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 000000000..63a992eba --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,6 @@ +sonar.organization=marci4-github +sonar.projectKey=org.java-websocket:Java-WebSocket + +# relative paths to source directories. More details and properties are described +# in https://sonarcloud.io/documentation/project-administration/narrowing-the-focus/ +sonar.sources=src/main/java/org/java_websocket diff --git a/src/main/example/ChatClient.java b/src/main/example/ChatClient.java index 96a19100d..6ff01d9d5 100644 --- a/src/main/example/ChatClient.java +++ b/src/main/example/ChatClient.java @@ -1,3 +1,28 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + import java.awt.Container; import java.awt.GridLayout; import java.awt.event.ActionEvent; @@ -5,159 +30,155 @@ import java.awt.event.WindowEvent; import java.net.URI; import java.net.URISyntaxException; - import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; - -import org.java_websocket.WebSocketImpl; import org.java_websocket.client.WebSocketClient; import org.java_websocket.drafts.Draft; -import org.java_websocket.drafts.Draft_10; -import org.java_websocket.drafts.Draft_17; -import org.java_websocket.drafts.Draft_75; -import org.java_websocket.drafts.Draft_76; +import org.java_websocket.drafts.Draft_6455; import org.java_websocket.handshake.ServerHandshake; public class ChatClient extends JFrame implements ActionListener { - private static final long serialVersionUID = -6056260699202978657L; - - private final JTextField uriField; - private final JButton connect; - private final JButton close; - private final JTextArea ta; - private final JTextField chatField; - private final JComboBox draft; - private WebSocketClient cc; - - public ChatClient( String defaultlocation ) { - super( "WebSocket Chat Client" ); - Container c = getContentPane(); - GridLayout layout = new GridLayout(); - layout.setColumns( 1 ); - layout.setRows( 6 ); - c.setLayout( layout ); - - Draft[] drafts = { new Draft_17(), new Draft_10(), new Draft_76(), new Draft_75() }; - draft = new JComboBox( drafts ); - c.add( draft ); - - uriField = new JTextField(); - uriField.setText( defaultlocation ); - c.add( uriField ); - - connect = new JButton( "Connect" ); - connect.addActionListener( this ); - c.add( connect ); - - close = new JButton( "Close" ); - close.addActionListener( this ); - close.setEnabled( false ); - c.add( close ); - - JScrollPane scroll = new JScrollPane(); - ta = new JTextArea(); - scroll.setViewportView( ta ); - c.add( scroll ); - - chatField = new JTextField(); - chatField.setText( "" ); - chatField.addActionListener( this ); - c.add( chatField ); - - java.awt.Dimension d = new java.awt.Dimension( 300, 400 ); - setPreferredSize( d ); - setSize( d ); - - addWindowListener( new java.awt.event.WindowAdapter() { - @Override - public void windowClosing( WindowEvent e ) { - if( cc != null ) { - cc.close(); - } - dispose(); - } - } ); - - setLocationRelativeTo( null ); - setVisible( true ); - } - - public void actionPerformed( ActionEvent e ) { - - if( e.getSource() == chatField ) { - if( cc != null ) { - cc.send( chatField.getText() ); - chatField.setText( "" ); - chatField.requestFocus(); - } - - } else if( e.getSource() == connect ) { - try { - // cc = new ChatClient(new URI(uriField.getText()), area, ( Draft ) draft.getSelectedItem() ); - cc = new WebSocketClient( new URI( uriField.getText() ), (Draft) draft.getSelectedItem() ) { - - @Override - public void onMessage( String message ) { - ta.append( "got: " + message + "\n" ); - ta.setCaretPosition( ta.getDocument().getLength() ); - } - - @Override - public void onOpen( ServerHandshake handshake ) { - ta.append( "You are connected to ChatServer: " + getURI() + "\n" ); - ta.setCaretPosition( ta.getDocument().getLength() ); - } - - @Override - public void onClose( int code, String reason, boolean remote ) { - ta.append( "You have been disconnected from: " + getURI() + "; Code: " + code + " " + reason + "\n" ); - ta.setCaretPosition( ta.getDocument().getLength() ); - connect.setEnabled( true ); - uriField.setEditable( true ); - draft.setEditable( true ); - close.setEnabled( false ); - } - - @Override - public void onError( Exception ex ) { - ta.append( "Exception occured ...\n" + ex + "\n" ); - ta.setCaretPosition( ta.getDocument().getLength() ); - ex.printStackTrace(); - connect.setEnabled( true ); - uriField.setEditable( true ); - draft.setEditable( true ); - close.setEnabled( false ); - } - }; - - close.setEnabled( true ); - connect.setEnabled( false ); - uriField.setEditable( false ); - draft.setEditable( false ); - cc.connect(); - } catch ( URISyntaxException ex ) { - ta.append( uriField.getText() + " is not a valid WebSocket URI\n" ); - } - } else if( e.getSource() == close ) { - cc.close(); - } - } - - public static void main( String[] args ) { - WebSocketImpl.DEBUG = true; - String location; - if( args.length != 0 ) { - location = args[ 0 ]; - System.out.println( "Default server url specified: \'" + location + "\'" ); - } else { - location = "ws://localhost:8887"; - System.out.println( "Default server url not specified: defaulting to \'" + location + "\'" ); - } - new ChatClient( location ); - } + + private static final long serialVersionUID = -6056260699202978657L; + + private final JTextField uriField; + private final JButton connect; + private final JButton close; + private final JTextArea ta; + private final JTextField chatField; + private final JComboBox draft; + private WebSocketClient cc; + + public ChatClient(String defaultlocation) { + super("WebSocket Chat Client"); + Container c = getContentPane(); + GridLayout layout = new GridLayout(); + layout.setColumns(1); + layout.setRows(6); + c.setLayout(layout); + + Draft[] drafts = {new Draft_6455()}; + draft = new JComboBox(drafts); + c.add(draft); + + uriField = new JTextField(); + uriField.setText(defaultlocation); + c.add(uriField); + + connect = new JButton("Connect"); + connect.addActionListener(this); + c.add(connect); + + close = new JButton("Close"); + close.addActionListener(this); + close.setEnabled(false); + c.add(close); + + JScrollPane scroll = new JScrollPane(); + ta = new JTextArea(); + scroll.setViewportView(ta); + c.add(scroll); + + chatField = new JTextField(); + chatField.setText(""); + chatField.addActionListener(this); + c.add(chatField); + + java.awt.Dimension d = new java.awt.Dimension(300, 400); + setPreferredSize(d); + setSize(d); + + addWindowListener(new java.awt.event.WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + if (cc != null) { + cc.close(); + } + dispose(); + } + }); + + setLocationRelativeTo(null); + setVisible(true); + } + + public void actionPerformed(ActionEvent e) { + + if (e.getSource() == chatField) { + if (cc != null) { + cc.send(chatField.getText()); + chatField.setText(""); + chatField.requestFocus(); + } + + } else if (e.getSource() == connect) { + try { + // cc = new ChatClient(new URI(uriField.getText()), area, ( Draft ) draft.getSelectedItem() ); + cc = new WebSocketClient(new URI(uriField.getText()), (Draft) draft.getSelectedItem()) { + + @Override + public void onMessage(String message) { + ta.append("got: " + message + "\n"); + ta.setCaretPosition(ta.getDocument().getLength()); + } + + @Override + public void onOpen(ServerHandshake handshake) { + ta.append("You are connected to ChatServer: " + getURI() + "\n"); + ta.setCaretPosition(ta.getDocument().getLength()); + } + + @Override + public void onClose(int code, String reason, boolean remote) { + ta.append( + "You have been disconnected from: " + getURI() + "; Code: " + code + " " + reason + + "\n"); + ta.setCaretPosition(ta.getDocument().getLength()); + connect.setEnabled(true); + uriField.setEditable(true); + draft.setEditable(true); + close.setEnabled(false); + } + + @Override + public void onError(Exception ex) { + ta.append("Exception occurred ...\n" + ex + "\n"); + ta.setCaretPosition(ta.getDocument().getLength()); + ex.printStackTrace(); + connect.setEnabled(true); + uriField.setEditable(true); + draft.setEditable(true); + close.setEnabled(false); + } + }; + + close.setEnabled(true); + connect.setEnabled(false); + uriField.setEditable(false); + draft.setEditable(false); + cc.connect(); + } catch (URISyntaxException ex) { + ta.append(uriField.getText() + " is not a valid WebSocket URI\n"); + } + } else if (e.getSource() == close) { + cc.close(); + } + } + + public static void main(String[] args) { + String location; + if (args.length != 0) { + location = args[0]; + System.out.println("Default server url specified: \'" + location + "\'"); + } else { + location = "ws://localhost:8887"; + System.out.println("Default server url not specified: defaulting to \'" + location + "\'"); + } + new ChatClient(location); + } } diff --git a/src/main/example/ChatServer.java b/src/main/example/ChatServer.java index a27cb0ee0..a9441ca3c 100644 --- a/src/main/example/ChatServer.java +++ b/src/main/example/ChatServer.java @@ -1,13 +1,38 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.InetSocketAddress; import java.net.UnknownHostException; -import java.util.Collection; - +import java.nio.ByteBuffer; +import java.util.Collections; import org.java_websocket.WebSocket; -import org.java_websocket.WebSocketImpl; -import org.java_websocket.framing.Framedata; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_6455; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.server.WebSocketServer; @@ -16,84 +41,80 @@ */ public class ChatServer extends WebSocketServer { - public ChatServer( int port ) throws UnknownHostException { - super( new InetSocketAddress( port ) ); - } + public ChatServer(int port) throws UnknownHostException { + super(new InetSocketAddress(port)); + } + + public ChatServer(InetSocketAddress address) { + super(address); + } + + public ChatServer(int port, Draft_6455 draft) { + super(new InetSocketAddress(port), Collections.singletonList(draft)); + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + conn.send("Welcome to the server!"); //This method sends a message to the new client + broadcast("new connection: " + handshake + .getResourceDescriptor()); //This method sends a message to all clients connected + System.out.println( + conn.getRemoteSocketAddress().getAddress().getHostAddress() + " entered the room!"); + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + broadcast(conn + " has left the room!"); + System.out.println(conn + " has left the room!"); + } - public ChatServer( InetSocketAddress address ) { - super( address ); - } + @Override + public void onMessage(WebSocket conn, String message) { + broadcast(message); + System.out.println(conn + ": " + message); + } - @Override - public void onOpen( WebSocket conn, ClientHandshake handshake ) { - this.sendToAll( "new connection: " + handshake.getResourceDescriptor() ); - System.out.println( conn.getRemoteSocketAddress().getAddress().getHostAddress() + " entered the room!" ); - } + @Override + public void onMessage(WebSocket conn, ByteBuffer message) { + broadcast(message.array()); + System.out.println(conn + ": " + message); + } - @Override - public void onClose( WebSocket conn, int code, String reason, boolean remote ) { - this.sendToAll( conn + " has left the room!" ); - System.out.println( conn + " has left the room!" ); - } - @Override - public void onMessage( WebSocket conn, String message ) { - this.sendToAll( message ); - System.out.println( conn + ": " + message ); - } + public static void main(String[] args) throws InterruptedException, IOException { + int port = 8887; // 843 flash policy port + try { + port = Integer.parseInt(args[0]); + } catch (Exception ex) { + } + ChatServer s = new ChatServer(port); + s.start(); + System.out.println("ChatServer started on port: " + s.getPort()); - @Override - public void onFragment( WebSocket conn, Framedata fragment ) { - System.out.println( "received fragment: " + fragment ); - } + BufferedReader sysin = new BufferedReader(new InputStreamReader(System.in)); + while (true) { + String in = sysin.readLine(); + s.broadcast(in); + if (in.equals("exit")) { + s.stop(1000); + break; + } + } + } - public static void main( String[] args ) throws InterruptedException , IOException { - WebSocketImpl.DEBUG = true; - int port = 8887; // 843 flash policy port - try { - port = Integer.parseInt( args[ 0 ] ); - } catch ( Exception ex ) { - } - ChatServer s = new ChatServer( port ); - s.start(); - System.out.println( "ChatServer started on port: " + s.getPort() ); + @Override + public void onError(WebSocket conn, Exception ex) { + ex.printStackTrace(); + if (conn != null) { + // some errors like port binding failed may not be assignable to a specific websocket + } + } - BufferedReader sysin = new BufferedReader( new InputStreamReader( System.in ) ); - while ( true ) { - String in = sysin.readLine(); - s.sendToAll( in ); - if( in.equals( "exit" ) ) { - s.stop(); - break; - } else if( in.equals( "restart" ) ) { - s.stop(); - s.start(); - break; - } - } - } - @Override - public void onError( WebSocket conn, Exception ex ) { - ex.printStackTrace(); - if( conn != null ) { - // some errors like port binding failed may not be assignable to a specific websocket - } - } + @Override + public void onStart() { + System.out.println("Server started!"); + setConnectionLostTimeout(0); + setConnectionLostTimeout(100); + } - /** - * Sends text to all currently connected WebSocket clients. - * - * @param text - * The String to send across the network. - * @throws InterruptedException - * When socket related I/O errors occur. - */ - public void sendToAll( String text ) { - Collection con = connections(); - synchronized ( con ) { - for( WebSocket c : con ) { - c.send( text ); - } - } - } } diff --git a/src/main/example/ChatServerAttachmentExample.java b/src/main/example/ChatServerAttachmentExample.java new file mode 100644 index 000000000..d05a7781d --- /dev/null +++ b/src/main/example/ChatServerAttachmentExample.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import org.java_websocket.WebSocket; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; + +/** + * A simple WebSocketServer implementation. Keeps track of a "chatroom". + *

+ * Shows how to use the attachment for a WebSocket. This example just uses a simple integer as ID. + * Setting an attachment also works in the WebSocketClient + */ +public class ChatServerAttachmentExample extends WebSocketServer { + + Integer index = 0; + + public ChatServerAttachmentExample(int port) throws UnknownHostException { + super(new InetSocketAddress(port)); + } + + public ChatServerAttachmentExample(InetSocketAddress address) { + super(address); + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + conn.setAttachment(index); //Set the attachment to the current index + index++; + // Get the attachment of this connection as Integer + System.out.println( + conn.getRemoteSocketAddress().getAddress().getHostAddress() + " entered the room! ID: " + + conn.getAttachment()); + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + // Get the attachment of this connection as Integer + System.out.println(conn + " has left the room! ID: " + conn.getAttachment()); + } + + @Override + public void onMessage(WebSocket conn, String message) { + System.out.println(conn + ": " + message); + } + + @Override + public void onMessage(WebSocket conn, ByteBuffer message) { + System.out.println(conn + ": " + message); + } + + public static void main(String[] args) throws InterruptedException, IOException { + int port = 8887; // 843 flash policy port + try { + port = Integer.parseInt(args[0]); + } catch (Exception ex) { + } + ChatServerAttachmentExample s = new ChatServerAttachmentExample(port); + s.start(); + System.out.println("ChatServer started on port: " + s.getPort()); + + BufferedReader sysin = new BufferedReader(new InputStreamReader(System.in)); + while (true) { + String in = sysin.readLine(); + s.broadcast(in); + if (in.equals("exit")) { + s.stop(1000); + break; + } + } + } + + @Override + public void onError(WebSocket conn, Exception ex) { + ex.printStackTrace(); + if (conn != null) { + // some errors like port binding failed may not be assignable to a specific websocket + } + } + + @Override + public void onStart() { + System.out.println("Server started!"); + } + +} diff --git a/src/main/example/CustomHeaderClientExample.java b/src/main/example/CustomHeaderClientExample.java new file mode 100644 index 000000000..e35012968 --- /dev/null +++ b/src/main/example/CustomHeaderClientExample.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; + +/** + * This class shows how to add additional http header like "Origin" or "Cookie". + *

+ * To see it working, start ServerRejectHandshakeExample and then start this example. + */ +public class CustomHeaderClientExample { + + public static void main(String[] args) throws URISyntaxException, InterruptedException { + Map httpHeaders = new HashMap(); + httpHeaders.put("Cookie", "test"); + ExampleClient c = new ExampleClient(new URI("ws://localhost:8887"), httpHeaders); + //We expect no successful connection + c.connectBlocking(); + httpHeaders.put("Cookie", "username=nemo"); + c = new ExampleClient(new URI("ws://localhost:8887"), httpHeaders); + //Wer expect a successful connection + c.connectBlocking(); + c.closeBlocking(); + httpHeaders.put("Access-Control-Allow-Origin", "*"); + c = new ExampleClient(new URI("ws://localhost:8887"), httpHeaders); + //We expect no successful connection + c.connectBlocking(); + c.closeBlocking(); + httpHeaders.clear(); + httpHeaders.put("Origin", "localhost:8887"); + httpHeaders.put("Cookie", "username=nemo"); + c = new ExampleClient(new URI("ws://localhost:8887"), httpHeaders); + //We expect a successful connection + c.connectBlocking(); + c.closeBlocking(); + httpHeaders.clear(); + httpHeaders.put("Origin", "localhost"); + httpHeaders.put("cookie", "username=nemo"); + c = new ExampleClient(new URI("ws://localhost:8887"), httpHeaders); + //We expect no successful connection + c.connectBlocking(); + } +} diff --git a/src/main/example/ExampleClient.java b/src/main/example/ExampleClient.java index 3cf68377e..73b832bea 100644 --- a/src/main/example/ExampleClient.java +++ b/src/main/example/ExampleClient.java @@ -1,54 +1,83 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + import java.net.URI; import java.net.URISyntaxException; - +import java.util.Map; import org.java_websocket.client.WebSocketClient; import org.java_websocket.drafts.Draft; -import org.java_websocket.drafts.Draft_10; -import org.java_websocket.framing.Framedata; import org.java_websocket.handshake.ServerHandshake; -/** This example demonstrates how to create a websocket connection to a server. Only the most important callbacks are overloaded. */ +/** + * This example demonstrates how to create a websocket connection to a server. Only the most + * important callbacks are overloaded. + */ public class ExampleClient extends WebSocketClient { - public ExampleClient( URI serverUri , Draft draft ) { - super( serverUri, draft ); - } - - public ExampleClient( URI serverURI ) { - super( serverURI ); - } - - @Override - public void onOpen( ServerHandshake handshakedata ) { - System.out.println( "opened connection" ); - // if you plan to refuse connection based on ip or httpfields overload: onWebsocketHandshakeReceivedAsClient - } - - @Override - public void onMessage( String message ) { - System.out.println( "received: " + message ); - } - - @Override - public void onFragment( Framedata fragment ) { - System.out.println( "received fragment: " + new String( fragment.getPayloadData().array() ) ); - } - - @Override - public void onClose( int code, String reason, boolean remote ) { - // The codecodes are documented in class org.java_websocket.framing.CloseFrame - System.out.println( "Connection closed by " + ( remote ? "remote peer" : "us" ) ); - } - - @Override - public void onError( Exception ex ) { - ex.printStackTrace(); - // if the error is fatal then onClose will be called additionally - } - - public static void main( String[] args ) throws URISyntaxException { - ExampleClient c = new ExampleClient( new URI( "ws://localhost:8887" ), new Draft_10() ); // more about drafts here: http://github.com/TooTallNate/Java-WebSocket/wiki/Drafts - c.connect(); - } - -} \ No newline at end of file + public ExampleClient(URI serverUri, Draft draft) { + super(serverUri, draft); + } + + public ExampleClient(URI serverURI) { + super(serverURI); + } + + public ExampleClient(URI serverUri, Map httpHeaders) { + super(serverUri, httpHeaders); + } + + @Override + public void onOpen(ServerHandshake handshakedata) { + send("Hello, it is me. Mario :)"); + System.out.println("opened connection"); + // if you plan to refuse connection based on ip or httpfields overload: onWebsocketHandshakeReceivedAsClient + } + + @Override + public void onMessage(String message) { + System.out.println("received: " + message); + } + + @Override + public void onClose(int code, String reason, boolean remote) { + // The close codes are documented in class org.java_websocket.framing.CloseFrame + System.out.println( + "Connection closed by " + (remote ? "remote peer" : "us") + " Code: " + code + " Reason: " + + reason); + } + + @Override + public void onError(Exception ex) { + ex.printStackTrace(); + // if the error is fatal then onClose will be called additionally + } + + public static void main(String[] args) throws URISyntaxException { + ExampleClient c = new ExampleClient(new URI( + "ws://localhost:8887")); // more about drafts here: http://github.com/TooTallNate/Java-WebSocket/wiki/Drafts + c.connect(); + } + +} diff --git a/src/main/example/FragmentedFramesExample.java b/src/main/example/FragmentedFramesExample.java index b5a03e1aa..dcf658553 100644 --- a/src/main/example/FragmentedFramesExample.java +++ b/src/main/example/FragmentedFramesExample.java @@ -1,57 +1,84 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.URI; import java.net.URISyntaxException; import java.nio.ByteBuffer; - import org.java_websocket.WebSocket; import org.java_websocket.client.WebSocketClient; -import org.java_websocket.drafts.Draft_17; -import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.enums.Opcode; /** - * This example shows how to send fragmented frames.
- * For information on when to used fragmented frames see http://tools.ietf.org/html/rfc6455#section-5.4
- * Fragmented and normal messages can not be mixed. - * One is however allowed to mix them with control messages like ping/pong. - * + * This example shows how to send fragmented frames.
For information on when to used fragmented + * frames see http://tools.ietf.org/html/rfc6455#section-5.4
Fragmented and normal messages can + * not be mixed. One is however allowed to mix them with control messages like ping/pong. + * * @see WebSocket#sendFragmentedFrame(Opcode, ByteBuffer, boolean) **/ public class FragmentedFramesExample { - public static void main( String[] args ) throws URISyntaxException , IOException , InterruptedException { - // WebSocketImpl.DEBUG = true; // will give extra output - - WebSocketClient websocket = new ExampleClient( new URI( "ws://localhost:8887" ), new Draft_17() ); // Draft_17 is implementation of rfc6455 - if( !websocket.connectBlocking() ) { - System.err.println( "Could not connect to the server." ); - return; - } - - System.out.println( "This example shows how to send fragmented(continuous) messages." ); - - BufferedReader stdin = new BufferedReader( new InputStreamReader( System.in ) ); - while ( websocket.isOpen() ) { - System.out.println( "Please type in a loooooong line(which then will be send in 2 byte fragments):" ); - String longline = stdin.readLine(); - ByteBuffer longelinebuffer = ByteBuffer.wrap( longline.getBytes() ); - longelinebuffer.rewind(); - - for( int position = 2 ; ; position += 2 ) { - if( position < longelinebuffer.capacity() ) { - longelinebuffer.limit( position ); - websocket.sendFragmentedFrame( Opcode.TEXT, longelinebuffer, false );// when sending binary data one should use Opcode.BINARY - assert ( longelinebuffer.remaining() == 0 ); - // after calling sendFragmentedFrame one may reuse the buffer given to the method immediately - } else { - longelinebuffer.limit( longelinebuffer.capacity() ); - websocket.sendFragmentedFrame( Opcode.TEXT, longelinebuffer, true );// sending the last frame - break; - } - - } - System.out.println( "You can not type in the next long message or press Ctr-C to exit." ); - } - System.out.println( "FragmentedFramesExample terminated" ); - } + + public static void main(String[] args) + throws URISyntaxException, IOException, InterruptedException { + // WebSocketImpl.DEBUG = true; // will give extra output + + WebSocketClient websocket = new ExampleClient(new URI("ws://localhost:8887")); + if (!websocket.connectBlocking()) { + System.err.println("Could not connect to the server."); + return; + } + + System.out.println("This example shows how to send fragmented(continuous) messages."); + + BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); + while (websocket.isOpen()) { + System.out + .println("Please type in a loooooong line(which then will be send in 2 byte fragments):"); + String longline = stdin.readLine(); + ByteBuffer longelinebuffer = ByteBuffer.wrap(longline.getBytes()); + longelinebuffer.rewind(); + + for (int position = 2; ; position += 2) { + if (position < longelinebuffer.capacity()) { + longelinebuffer.limit(position); + websocket.sendFragmentedFrame(Opcode.TEXT, longelinebuffer, + false);// when sending binary data one should use Opcode.BINARY + assert (longelinebuffer.remaining() == 0); + // after calling sendFragmentedFrame one may reuse the buffer given to the method immediately + } else { + longelinebuffer.limit(longelinebuffer.capacity()); + websocket + .sendFragmentedFrame(Opcode.TEXT, longelinebuffer, true);// sending the last frame + break; + } + + } + System.out.println("You can not type in the next long message or press Ctr-C to exit."); + } + System.out.println("FragmentedFramesExample terminated"); + } } diff --git a/src/main/example/PerMessageDeflateExample.java b/src/main/example/PerMessageDeflateExample.java new file mode 100644 index 000000000..49cd64eca --- /dev/null +++ b/src/main/example/PerMessageDeflateExample.java @@ -0,0 +1,82 @@ +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collections; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.extensions.permessage_deflate.PerMessageDeflateExtension; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.WebSocketServer; + +/** + * This class only serves the purpose of showing how to enable PerMessageDeflateExtension for both + * server and client sockets.
Extensions are required to be registered in + * + * @see Draft objects and both + * @see WebSocketClient and + * @see WebSocketServer accept a + * @see Draft object in their constructors. This example shows how to achieve it for both server and + * client sockets. Once the connection has been established, PerMessageDeflateExtension will be + * enabled and any messages (binary or text) will be compressed/decompressed automatically.
+ * Since no additional code is required when sending or receiving messages, this example skips those + * parts. + */ +public class PerMessageDeflateExample { + + private static final Draft perMessageDeflateDraft = new Draft_6455( + new PerMessageDeflateExtension()); + private static final int PORT = 8887; + + private static class DeflateClient extends WebSocketClient { + + public DeflateClient() throws URISyntaxException { + super(new URI("ws://localhost:" + PORT), perMessageDeflateDraft); + } + + @Override + public void onOpen(ServerHandshake handshakedata) { + } + + @Override + public void onMessage(String message) { + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onError(Exception ex) { + } + } + + private static class DeflateServer extends WebSocketServer { + + public DeflateServer() { + super(new InetSocketAddress(PORT), Collections.singletonList(perMessageDeflateDraft)); + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, String message) { + } + + @Override + public void onError(WebSocket conn, Exception ex) { + } + + @Override + public void onStart() { + } + } +} diff --git a/src/main/example/ProxyClientExample.java b/src/main/example/ProxyClientExample.java index ddff0ea0d..4241359ed 100644 --- a/src/main/example/ProxyClientExample.java +++ b/src/main/example/ProxyClientExample.java @@ -1,12 +1,38 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + import java.net.InetSocketAddress; import java.net.Proxy; import java.net.URI; import java.net.URISyntaxException; public class ProxyClientExample { - public static void main( String[] args ) throws URISyntaxException { - ExampleClient c = new ExampleClient( new URI( "ws://echo.websocket.org" ) ); - c.setProxy( new Proxy( Proxy.Type.HTTP, new InetSocketAddress( "proxyaddress", 80 ) ) ); - c.connect(); - } + + public static void main(String[] args) throws URISyntaxException { + ExampleClient c = new ExampleClient(new URI("ws://echo.websocket.org")); + c.setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxyaddress", 80))); + c.connect(); + } } diff --git a/src/main/example/ReconnectClientExample.java b/src/main/example/ReconnectClientExample.java new file mode 100644 index 000000000..efe958d54 --- /dev/null +++ b/src/main/example/ReconnectClientExample.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +import java.net.URI; +import java.net.URISyntaxException; + +/** + * Simple example to reconnect blocking and non-blocking. + */ +public class ReconnectClientExample { + + public static void main(String[] args) throws URISyntaxException, InterruptedException { + ExampleClient c = new ExampleClient(new URI("ws://localhost:8887")); + //Connect to a server normally + c.connectBlocking(); + c.send("hi"); + Thread.sleep(10); + c.closeBlocking(); + //Disconnect manually and reconnect blocking + c.reconnectBlocking(); + c.send("it's"); + Thread.sleep(10000); + c.closeBlocking(); + //Disconnect manually and reconnect + c.reconnect(); + Thread.sleep(100); + c.send("me"); + Thread.sleep(100); + c.closeBlocking(); + } +} diff --git a/src/main/example/SSLClientExample.java b/src/main/example/SSLClientExample.java index e740c9c54..52790caff 100644 --- a/src/main/example/SSLClientExample.java +++ b/src/main/example/SSLClientExample.java @@ -1,99 +1,123 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.net.URI; +import java.nio.file.Paths; import java.security.KeyStore; - import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; - -import org.java_websocket.WebSocketImpl; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; class WebSocketChatClient extends WebSocketClient { - public WebSocketChatClient( URI serverUri ) { - super( serverUri ); - } + public WebSocketChatClient(URI serverUri) { + super(serverUri); + } - @Override - public void onOpen( ServerHandshake handshakedata ) { - System.out.println( "Connected" ); + @Override + public void onOpen(ServerHandshake handshakedata) { + System.out.println("Connected"); - } + } - @Override - public void onMessage( String message ) { - System.out.println( "got: " + message ); + @Override + public void onMessage(String message) { + System.out.println("got: " + message); - } + } - @Override - public void onClose( int code, String reason, boolean remote ) { - System.out.println( "Disconnected" ); - System.exit( 0 ); + @Override + public void onClose(int code, String reason, boolean remote) { + System.out.println("Disconnected"); - } + } - @Override - public void onError( Exception ex ) { - ex.printStackTrace(); + @Override + public void onError(Exception ex) { + ex.printStackTrace(); - } + } } public class SSLClientExample { - /* - * Keystore with certificate created like so (in JKS format): - * - *keytool -genkey -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry" - */ - public static void main( String[] args ) throws Exception { - WebSocketImpl.DEBUG = true; - - WebSocketChatClient chatclient = new WebSocketChatClient( new URI( "wss://localhost:8887" ) ); - - // load up the key store - String STORETYPE = "JKS"; - String KEYSTORE = "keystore.jks"; - String STOREPASSWORD = "storepassword"; - String KEYPASSWORD = "keypassword"; - - KeyStore ks = KeyStore.getInstance( STORETYPE ); - File kf = new File( KEYSTORE ); - ks.load( new FileInputStream( kf ), STOREPASSWORD.toCharArray() ); - - KeyManagerFactory kmf = KeyManagerFactory.getInstance( "SunX509" ); - kmf.init( ks, KEYPASSWORD.toCharArray() ); - TrustManagerFactory tmf = TrustManagerFactory.getInstance( "SunX509" ); - tmf.init( ks ); - - SSLContext sslContext = null; - sslContext = SSLContext.getInstance( "TLS" ); - sslContext.init( kmf.getKeyManagers(), tmf.getTrustManagers(), null ); - // sslContext.init( null, null, null ); // will use java's default key and trust store which is sufficient unless you deal with self-signed certificates - - SSLSocketFactory factory = sslContext.getSocketFactory();// (SSLSocketFactory) SSLSocketFactory.getDefault(); - - chatclient.setSocket( factory.createSocket() ); - - chatclient.connectBlocking(); - - BufferedReader reader = new BufferedReader( new InputStreamReader( System.in ) ); - while ( true ) { - String line = reader.readLine(); - if( line.equals( "close" ) ) { - chatclient.close(); - } else { - chatclient.send( line ); - } - } - - } + /* + * Keystore with certificate created like so (in JKS format): + * + *keytool -genkey -keyalg RSA -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry" + */ + public static void main(String[] args) throws Exception { + WebSocketChatClient chatclient = new WebSocketChatClient(new URI("wss://localhost:8887")); + + // load up the key store + String STORETYPE = "JKS"; + String KEYSTORE = Paths.get("src", "test", "java", "org", "java_websocket", "keystore.jks") + .toString(); + String STOREPASSWORD = "storepassword"; + String KEYPASSWORD = "keypassword"; + + KeyStore ks = KeyStore.getInstance(STORETYPE); + File kf = new File(KEYSTORE); + ks.load(new FileInputStream(kf), STOREPASSWORD.toCharArray()); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, KEYPASSWORD.toCharArray()); + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(ks); + + SSLContext sslContext = null; + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + // sslContext.init( null, null, null ); // will use java's default key and trust store which is sufficient unless you deal with self-signed certificates + + SSLSocketFactory factory = sslContext + .getSocketFactory();// (SSLSocketFactory) SSLSocketFactory.getDefault(); + + chatclient.setSocketFactory(factory); + + chatclient.connectBlocking(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + while (true) { + String line = reader.readLine(); + if (line.equals("close")) { + chatclient.closeBlocking(); + } else if (line.equals("open")) { + chatclient.reconnect(); + } else { + chatclient.send(line); + } + } + + } } diff --git a/src/main/example/SSLServerCustomWebsocketFactoryExample.java b/src/main/example/SSLServerCustomWebsocketFactoryExample.java new file mode 100644 index 000000000..e9c5d7c1e --- /dev/null +++ b/src/main/example/SSLServerCustomWebsocketFactoryExample.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +import java.io.File; +import java.io.FileInputStream; +import java.nio.file.Paths; +import java.security.KeyStore; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManagerFactory; +import org.java_websocket.server.CustomSSLWebSocketServerFactory; + +/** + * Example for using the CustomSSLWebSocketServerFactory to allow just specific cipher suites + */ +public class SSLServerCustomWebsocketFactoryExample { + + /* + * Keystore with certificate created like so (in JKS format): + * + *keytool -genkey -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry" + */ + public static void main(String[] args) throws Exception { + ChatServer chatserver = new ChatServer( + 8887); // Firefox does allow multible ssl connection only via port 443 //tested on FF16 + + // load up the key store + String STORETYPE = "JKS"; + String KEYSTORE = Paths.get("src", "test", "java", "org", "java_websocket", "keystore.jks") + .toString(); + String STOREPASSWORD = "storepassword"; + String KEYPASSWORD = "keypassword"; + + KeyStore ks = KeyStore.getInstance(STORETYPE); + File kf = new File(KEYSTORE); + ks.load(new FileInputStream(kf), STOREPASSWORD.toCharArray()); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, KEYPASSWORD.toCharArray()); + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(ks); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + //Lets remove some ciphers and protocols + SSLEngine engine = sslContext.createSSLEngine(); + List ciphers = new ArrayList(Arrays.asList(engine.getEnabledCipherSuites())); + ciphers.remove("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); + List protocols = new ArrayList(Arrays.asList(engine.getEnabledProtocols())); + protocols.remove("SSLv3"); + CustomSSLWebSocketServerFactory factory = new CustomSSLWebSocketServerFactory(sslContext, + protocols.toArray(new String[]{}), ciphers.toArray(new String[]{})); + + // Different example just using specific ciphers and protocols + /* + String[] enabledProtocols = {"TLSv1.2"}; + String[] enabledCipherSuites = {"TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA"}; + CustomSSLWebSocketServerFactory factory = new CustomSSLWebSocketServerFactory(sslContext, enabledProtocols,enabledCipherSuites); + */ + chatserver.setWebSocketFactory(factory); + + chatserver.start(); + + } +} diff --git a/src/main/example/SSLServerExample.java b/src/main/example/SSLServerExample.java index 563395403..ca599f703 100644 --- a/src/main/example/SSLServerExample.java +++ b/src/main/example/SSLServerExample.java @@ -1,50 +1,73 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + import java.io.File; import java.io.FileInputStream; +import java.nio.file.Paths; import java.security.KeyStore; - import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; - -import org.java_websocket.WebSocketImpl; import org.java_websocket.server.DefaultSSLWebSocketServerFactory; public class SSLServerExample { - /* - * Keystore with certificate created like so (in JKS format): - * - *keytool -genkey -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry" - */ - public static void main( String[] args ) throws Exception { - WebSocketImpl.DEBUG = true; - - ChatServer chatserver = new ChatServer( 8887 ); // Firefox does allow multible ssl connection only via port 443 //tested on FF16 + /* + * Keystore with certificate created like so (in JKS format): + * + *keytool -genkey -keyalg RSA -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry" + */ + public static void main(String[] args) throws Exception { + ChatServer chatserver = new ChatServer( + 8887); // Firefox does allow multible ssl connection only via port 443 //tested on FF16 - // load up the key store - String STORETYPE = "JKS"; - String KEYSTORE = "keystore.jks"; - String STOREPASSWORD = "storepassword"; - String KEYPASSWORD = "keypassword"; + // load up the key store + String STORETYPE = "JKS"; + String KEYSTORE = Paths.get("src", "test", "java", "org", "java_websocket", "keystore.jks") + .toString(); + String STOREPASSWORD = "storepassword"; + String KEYPASSWORD = "keypassword"; - KeyStore ks = KeyStore.getInstance( STORETYPE ); - File kf = new File( KEYSTORE ); - ks.load( new FileInputStream( kf ), STOREPASSWORD.toCharArray() ); + KeyStore ks = KeyStore.getInstance(STORETYPE); + File kf = new File(KEYSTORE); + ks.load(new FileInputStream(kf), STOREPASSWORD.toCharArray()); - KeyManagerFactory kmf = KeyManagerFactory.getInstance( "SunX509" ); - kmf.init( ks, KEYPASSWORD.toCharArray() ); - TrustManagerFactory tmf = TrustManagerFactory.getInstance( "SunX509" ); - tmf.init( ks ); + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, KEYPASSWORD.toCharArray()); + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(ks); - SSLContext sslContext = null; - sslContext = SSLContext.getInstance( "TLS" ); - sslContext.init( kmf.getKeyManagers(), tmf.getTrustManagers(), null ); + SSLContext sslContext = null; + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - chatserver.setWebSocketFactory( new DefaultSSLWebSocketServerFactory( sslContext ) ); + chatserver.setWebSocketFactory(new DefaultSSLWebSocketServerFactory(sslContext)); - chatserver.start(); + chatserver.start(); - } + } } diff --git a/src/main/example/SSLServerLetsEncryptExample.java b/src/main/example/SSLServerLetsEncryptExample.java new file mode 100644 index 000000000..95308aa99 --- /dev/null +++ b/src/main/example/SSLServerLetsEncryptExample.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import org.java_websocket.server.DefaultSSLWebSocketServerFactory; + + +/** + * SSL Example using the LetsEncrypt certificate See https://github.com/TooTallNate/Java-WebSocket/wiki/Getting-a-SSLContext-from-different-sources#getting-a-sslcontext-using-a-lets-encrypt-certificate + */ +public class SSLServerLetsEncryptExample { + + public static void main(String[] args) throws Exception { + ChatServer chatserver = new ChatServer(8887); + + SSLContext context = getContext(); + if (context != null) { + chatserver.setWebSocketFactory(new DefaultSSLWebSocketServerFactory(getContext())); + } + chatserver.setConnectionLostTimeout(30); + chatserver.start(); + + } + + private static SSLContext getContext() { + SSLContext context; + String password = "CHANGEIT"; + String pathname = "pem"; + try { + context = SSLContext.getInstance("TLS"); + + byte[] certBytes = parseDERFromPEM(getBytes(new File(pathname + File.separator + "cert.pem")), + "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----"); + byte[] keyBytes = parseDERFromPEM( + getBytes(new File(pathname + File.separator + "privkey.pem")), + "-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----"); + + X509Certificate cert = generateCertificateFromDER(certBytes); + RSAPrivateKey key = generatePrivateKeyFromDER(keyBytes); + + KeyStore keystore = KeyStore.getInstance("JKS"); + keystore.load(null); + keystore.setCertificateEntry("cert-alias", cert); + keystore.setKeyEntry("key-alias", key, password.toCharArray(), new Certificate[]{cert}); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(keystore, password.toCharArray()); + + KeyManager[] km = kmf.getKeyManagers(); + + context.init(km, null, null); + } catch (Exception e) { + context = null; + } + return context; + } + + private static byte[] parseDERFromPEM(byte[] pem, String beginDelimiter, String endDelimiter) { + String data = new String(pem); + String[] tokens = data.split(beginDelimiter); + tokens = tokens[1].split(endDelimiter); + // return DatatypeConverter.parseBase64Binary(tokens[0]); + return null; + } + + private static RSAPrivateKey generatePrivateKeyFromDER(byte[] keyBytes) + throws InvalidKeySpecException, NoSuchAlgorithmException { + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); + + KeyFactory factory = KeyFactory.getInstance("RSA"); + + return (RSAPrivateKey) factory.generatePrivate(spec); + } + + private static X509Certificate generateCertificateFromDER(byte[] certBytes) + throws CertificateException { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + + return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(certBytes)); + } + + private static byte[] getBytes(File file) { + byte[] bytesArray = new byte[(int) file.length()]; + + FileInputStream fis = null; + try { + fis = new FileInputStream(file); + fis.read(bytesArray); //read file into bytes[] + fis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return bytesArray; + } +} diff --git a/src/main/example/SecWebSocketProtocolClientExample.java b/src/main/example/SecWebSocketProtocolClientExample.java new file mode 100644 index 000000000..d9645f800 --- /dev/null +++ b/src/main/example/SecWebSocketProtocolClientExample.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.extensions.IExtension; +import org.java_websocket.protocols.IProtocol; +import org.java_websocket.protocols.Protocol; + +/** + * This example demonstrates how to use a specific Sec-WebSocket-Protocol for your connection. + */ +public class SecWebSocketProtocolClientExample { + + public static void main(String[] args) throws URISyntaxException { + // This draft only allows you to use the specific Sec-WebSocket-Protocol without a fallback. + Draft_6455 draft_ocppOnly = new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("ocpp2.0"))); + + // This draft allows the specific Sec-WebSocket-Protocol and also provides a fallback, if the other endpoint does not accept the specific Sec-WebSocket-Protocol + ArrayList protocols = new ArrayList(); + protocols.add(new Protocol("ocpp2.0")); + protocols.add(new Protocol("")); + Draft_6455 draft_ocppAndFallBack = new Draft_6455(Collections.emptyList(), + protocols); + + ExampleClient c = new ExampleClient(new URI("ws://echo.websocket.org"), draft_ocppAndFallBack); + c.connect(); + } +} diff --git a/src/main/example/SecWebSocketProtocolServerExample.java b/src/main/example/SecWebSocketProtocolServerExample.java new file mode 100644 index 000000000..01ba44b02 --- /dev/null +++ b/src/main/example/SecWebSocketProtocolServerExample.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.extensions.IExtension; +import org.java_websocket.protocols.IProtocol; +import org.java_websocket.protocols.Protocol; + +/** + * This example demonstrates how to use a specific Sec-WebSocket-Protocol for your connection. + */ +public class SecWebSocketProtocolServerExample { + + public static void main(String[] args) throws URISyntaxException { + // This draft only allows you to use the specific Sec-WebSocket-Protocol without a fallback. + Draft_6455 draft_ocppOnly = new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("ocpp2.0"))); + + // This draft allows the specific Sec-WebSocket-Protocol and also provides a fallback, if the other endpoint does not accept the specific Sec-WebSocket-Protocol + ArrayList protocols = new ArrayList(); + protocols.add(new Protocol("ocpp2.0")); + protocols.add(new Protocol("")); + Draft_6455 draft_ocppAndFallBack = new Draft_6455(Collections.emptyList(), + protocols); + + ChatServer chatServer = new ChatServer(8887, draft_ocppOnly); + chatServer.start(); + } +} diff --git a/src/main/example/ServerAdditionalHeaderExample.java b/src/main/example/ServerAdditionalHeaderExample.java new file mode 100644 index 000000000..205c77f67 --- /dev/null +++ b/src/main/example/ServerAdditionalHeaderExample.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetSocketAddress; +import org.java_websocket.WebSocket; +import org.java_websocket.drafts.Draft; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; + +/** + * This example shows how to add additional headers to your server handshake response + *

+ * For this you have to override onWebsocketHandshakeReceivedAsServer in your WebSocketServer class + *

+ * We are simple adding the additional header "Access-Control-Allow-Origin" to our server response + */ +public class ServerAdditionalHeaderExample extends ChatServer { + + public ServerAdditionalHeaderExample(int port) { + super(new InetSocketAddress(port)); + } + + @Override + public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer(WebSocket conn, Draft draft, + ClientHandshake request) throws InvalidDataException { + ServerHandshakeBuilder builder = super + .onWebsocketHandshakeReceivedAsServer(conn, draft, request); + builder.put("Access-Control-Allow-Origin", "*"); + return builder; + } + + + public static void main(String[] args) throws InterruptedException, IOException { + int port = 8887; // 843 flash policy port + try { + port = Integer.parseInt(args[0]); + } catch (Exception ex) { + } + ServerAdditionalHeaderExample s = new ServerAdditionalHeaderExample(port); + s.start(); + System.out.println("Server started on port: " + s.getPort()); + + BufferedReader sysin = new BufferedReader(new InputStreamReader(System.in)); + while (true) { + String in = sysin.readLine(); + s.broadcast(in); + if (in.equals("exit")) { + s.stop(1000); + break; + } + } + } +} diff --git a/src/main/example/ServerRejectHandshakeExample.java b/src/main/example/ServerRejectHandshakeExample.java new file mode 100644 index 000000000..ee7081f15 --- /dev/null +++ b/src/main/example/ServerRejectHandshakeExample.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetSocketAddress; +import org.java_websocket.WebSocket; +import org.java_websocket.drafts.Draft; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; + +/** + * This example shows how to reject a handshake as a server from a client. + *

+ * For this you have to override onWebsocketHandshakeReceivedAsServer in your WebSocketServer class + */ +public class ServerRejectHandshakeExample extends ChatServer { + + public ServerRejectHandshakeExample(int port) { + super(new InetSocketAddress(port)); + } + + @Override + public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer(WebSocket conn, Draft draft, + ClientHandshake request) throws InvalidDataException { + ServerHandshakeBuilder builder = super + .onWebsocketHandshakeReceivedAsServer(conn, draft, request); + //In this example we don't allow any resource descriptor ( "ws://localhost:8887/?roomid=1 will be rejected but ws://localhost:8887 is fine) + if (!request.getResourceDescriptor().equals("/")) { + throw new InvalidDataException(CloseFrame.POLICY_VALIDATION, "Not accepted!"); + } + //If there are no cookies set reject it as well. + if (!request.hasFieldValue("Cookie")) { + throw new InvalidDataException(CloseFrame.POLICY_VALIDATION, "Not accepted!"); + } + //If the cookie does not contain a specific value + if (!request.getFieldValue("Cookie").equals("username=nemo")) { + throw new InvalidDataException(CloseFrame.POLICY_VALIDATION, "Not accepted!"); + } + //If there is a Origin Field, it has to be localhost:8887 + if (request.hasFieldValue("Origin")) { + if (!request.getFieldValue("Origin").equals("localhost:8887")) { + throw new InvalidDataException(CloseFrame.POLICY_VALIDATION, "Not accepted!"); + } + } + return builder; + } + + + public static void main(String[] args) throws InterruptedException, IOException { + int port = 8887; // 843 flash policy port + try { + port = Integer.parseInt(args[0]); + } catch (Exception ex) { + } + ServerRejectHandshakeExample s = new ServerRejectHandshakeExample(port); + s.start(); + System.out.println("Server started on port: " + s.getPort()); + + BufferedReader sysin = new BufferedReader(new InputStreamReader(System.in)); + while (true) { + String in = sysin.readLine(); + s.broadcast(in); + if (in.equals("exit")) { + s.stop(1000); + break; + } + } + } +} diff --git a/src/main/example/ServerStressTest.java b/src/main/example/ServerStressTest.java index 8fe2e97a4..bb973b63c 100644 --- a/src/main/example/ServerStressTest.java +++ b/src/main/example/ServerStressTest.java @@ -1,16 +1,39 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + import java.awt.FlowLayout; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.net.URI; import java.net.URISyntaxException; -import java.nio.channels.NotYetConnectedException; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Timer; import java.util.TimerTask; - import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; @@ -20,198 +43,206 @@ import javax.swing.JTextField; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; - import org.java_websocket.client.WebSocketClient; +import org.java_websocket.exceptions.WebsocketNotConnectedException; public class ServerStressTest extends JFrame { - private JSlider clients; - private JSlider interval; - private JSlider joinrate; - private JButton start, stop, reset; - private JLabel joinratelabel = new JLabel(); - private JLabel clientslabel = new JLabel(); - private JLabel intervallabel = new JLabel(); - private JTextField uriinput = new JTextField( "ws://localhost:8887" ); - private JTextArea text = new JTextArea( "payload" ); - private Timer timer = new Timer( true ); - private Thread adjustthread; - - private int notyetconnected = 0; - - public ServerStressTest() { - setTitle( "ServerStressTest" ); - setDefaultCloseOperation( EXIT_ON_CLOSE ); - start = new JButton( "Start" ); - start.addActionListener( new ActionListener() { - - @Override - public void actionPerformed( ActionEvent e ) { - start.setEnabled( false ); - stop.setEnabled( true ); - reset.setEnabled( false ); - interval.setEnabled( false ); - clients.setEnabled( false ); - - stopAdjust(); - adjustthread = new Thread( new Runnable() { - @Override - public void run() { - try { - adjust(); - } catch ( InterruptedException e ) { - System.out.println( "adjust chanced" ); - } - } - } ); - adjustthread.start(); - - } - } ); - stop = new JButton( "Stop" ); - stop.setEnabled( false ); - stop.addActionListener( new ActionListener() { - - @Override - public void actionPerformed( ActionEvent e ) { - timer.cancel(); - stopAdjust(); - start.setEnabled( true ); - stop.setEnabled( false ); - reset.setEnabled( true ); - joinrate.setEnabled( true ); - interval.setEnabled( true ); - clients.setEnabled( true ); - } - } ); - reset = new JButton( "reset" ); - reset.setEnabled( true ); - reset.addActionListener( new ActionListener() { - - @Override - public void actionPerformed( ActionEvent e ) { - while ( !websockets.isEmpty() ) - websockets.remove( 0 ).close(); - - } - } ); - joinrate = new JSlider( 0, 5000 ); - joinrate.addChangeListener( new ChangeListener() { - @Override - public void stateChanged( ChangeEvent e ) { - joinratelabel.setText( "Joinrate: " + joinrate.getValue() + " ms " ); - } - } ); - clients = new JSlider( 0, 10000 ); - clients.addChangeListener( new ChangeListener() { - - @Override - public void stateChanged( ChangeEvent e ) { - clientslabel.setText( "Clients: " + clients.getValue() ); - - } - } ); - interval = new JSlider( 0, 5000 ); - interval.addChangeListener( new ChangeListener() { - - @Override - public void stateChanged( ChangeEvent e ) { - intervallabel.setText( "Interval: " + interval.getValue() + " ms " ); - - } - } ); - - setSize( 300, 400 ); - setLayout( new GridLayout( 10, 1, 10, 10 ) ); - add( new JLabel( "URI" ) ); - add( uriinput ); - add( joinratelabel ); - add( joinrate ); - add( clientslabel ); - add( clients ); - add( intervallabel ); - add( interval ); - JPanel south = new JPanel( new FlowLayout( FlowLayout.CENTER ) ); - add( text ); - add( south ); - - south.add( start ); - south.add( stop ); - south.add( reset ); - - joinrate.setValue( 200 ); - interval.setValue( 1000 ); - clients.setValue( 1 ); - - } - - List websockets = Collections.synchronizedList( new LinkedList() ); - URI uri; - public void adjust() throws InterruptedException { - System.out.println( "Adjust" ); - try { - uri = new URI( uriinput.getText() ); - } catch ( URISyntaxException e ) { - e.printStackTrace(); - } - int totalclients = clients.getValue(); - while ( websockets.size() < totalclients ) { - WebSocketClient cl = new ExampleClient( uri ) { - @Override - public void onClose( int code, String reason, boolean remote ) { - System.out.println( "Closed duo " + code + " " + reason ); - clients.setValue( websockets.size() ); - websockets.remove( this ); - } - }; - - cl.connect(); - clients.setValue( websockets.size() ); - websockets.add( cl ); - Thread.sleep( joinrate.getValue() ); - } - while ( websockets.size() > clients.getValue() ) { - websockets.remove( 0 ).close(); - } - timer = new Timer( true ); - timer.scheduleAtFixedRate( new TimerTask() { - @Override - public void run() { - send(); - } - }, 0, interval.getValue() ); - - } - - public void stopAdjust() { - if( adjustthread != null ) { - adjustthread.interrupt(); - try { - adjustthread.join(); - } catch ( InterruptedException e ) { - e.printStackTrace(); - } - } - } - public void send() { - notyetconnected = 0; - String payload = text.getText(); - long time1 = System.currentTimeMillis(); - synchronized ( websockets ) { - for( WebSocketClient cl : websockets ) { - try { - cl.send( payload ); - } catch ( NotYetConnectedException e ) { - notyetconnected++; - } - } - } - System.out.println( websockets.size() + "/" + notyetconnected + " clients sent \"" + payload + "\"" + ( System.currentTimeMillis() - time1 ) ); - } - /** - * @param args - */ - public static void main( String[] args ) { - new ServerStressTest().setVisible( true ); - } + + private JSlider clients; + private JSlider interval; + private JSlider joinrate; + private JButton start, stop, reset; + private JLabel joinratelabel = new JLabel(); + private JLabel clientslabel = new JLabel(); + private JLabel intervallabel = new JLabel(); + private JTextField uriinput = new JTextField("ws://localhost:8887"); + private JTextArea text = new JTextArea("payload"); + private Timer timer = new Timer(true); + private Thread adjustthread; + + private int notyetconnected = 0; + + public ServerStressTest() { + setTitle("ServerStressTest"); + setDefaultCloseOperation(EXIT_ON_CLOSE); + start = new JButton("Start"); + start.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + start.setEnabled(false); + stop.setEnabled(true); + reset.setEnabled(false); + interval.setEnabled(false); + clients.setEnabled(false); + + stopAdjust(); + adjustthread = new Thread(new Runnable() { + @Override + public void run() { + try { + adjust(); + } catch (InterruptedException e) { + System.out.println("adjust chanced"); + } + } + }); + adjustthread.start(); + + } + }); + stop = new JButton("Stop"); + stop.setEnabled(false); + stop.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + timer.cancel(); + stopAdjust(); + start.setEnabled(true); + stop.setEnabled(false); + reset.setEnabled(true); + joinrate.setEnabled(true); + interval.setEnabled(true); + clients.setEnabled(true); + } + }); + reset = new JButton("reset"); + reset.setEnabled(true); + reset.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + while (!websockets.isEmpty()) { + websockets.remove(0).close(); + } + + } + }); + joinrate = new JSlider(0, 5000); + joinrate.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + joinratelabel.setText("Joinrate: " + joinrate.getValue() + " ms "); + } + }); + clients = new JSlider(0, 10000); + clients.addChangeListener(new ChangeListener() { + + @Override + public void stateChanged(ChangeEvent e) { + clientslabel.setText("Clients: " + clients.getValue()); + + } + }); + interval = new JSlider(0, 5000); + interval.addChangeListener(new ChangeListener() { + + @Override + public void stateChanged(ChangeEvent e) { + intervallabel.setText("Interval: " + interval.getValue() + " ms "); + + } + }); + + setSize(300, 400); + setLayout(new GridLayout(10, 1, 10, 10)); + add(new JLabel("URI")); + add(uriinput); + add(joinratelabel); + add(joinrate); + add(clientslabel); + add(clients); + add(intervallabel); + add(interval); + JPanel south = new JPanel(new FlowLayout(FlowLayout.CENTER)); + add(text); + add(south); + + south.add(start); + south.add(stop); + south.add(reset); + + joinrate.setValue(200); + interval.setValue(1000); + clients.setValue(1); + + } + + List websockets = Collections + .synchronizedList(new LinkedList()); + URI uri; + + public void adjust() throws InterruptedException { + System.out.println("Adjust"); + try { + uri = new URI(uriinput.getText()); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + int totalclients = clients.getValue(); + while (websockets.size() < totalclients) { + WebSocketClient cl = new ExampleClient(uri) { + @Override + public void onClose(int code, String reason, boolean remote) { + System.out.println("Closed duo " + code + " " + reason); + clients.setValue(websockets.size()); + websockets.remove(this); + } + }; + + cl.connect(); + clients.setValue(websockets.size()); + websockets.add(cl); + Thread.sleep(joinrate.getValue()); + } + while (websockets.size() > clients.getValue()) { + websockets.remove(0).close(); + } + timer = new Timer(true); + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + send(); + } + }, 0, interval.getValue()); + + } + + public void stopAdjust() { + if (adjustthread != null) { + adjustthread.interrupt(); + try { + adjustthread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public void send() { + notyetconnected = 0; + String payload = text.getText(); + long time1 = System.currentTimeMillis(); + synchronized (websockets) { + for (WebSocketClient cl : websockets) { + try { + cl.send(payload); + } catch (WebsocketNotConnectedException e) { + notyetconnected++; + } + } + } + System.out.println( + websockets.size() + "/" + notyetconnected + " clients sent \"" + payload + "\"" + ( + System.currentTimeMillis() - time1)); + } + + /** + * @param args + */ + public static void main(String[] args) { + new ServerStressTest().setVisible(true); + } } diff --git a/src/main/example/SocketActivation.java b/src/main/example/SocketActivation.java new file mode 100644 index 000000000..86b0da63a --- /dev/null +++ b/src/main/example/SocketActivation.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ServerSocketChannel; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; +import org.java_websocket.WebSocket; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; + +/** + * This is a "smart" chat server which will exit when no more clients are left, in order to demonstrate socket activation + */ +public class SocketActivation extends WebSocketServer { + + AtomicInteger clients = new AtomicInteger(0); + + public SocketActivation(ServerSocketChannel chan) { + super(chan); + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + conn.send("Welcome to the server!"); //This method sends a message to the new client + broadcast("new connection: " + handshake.getResourceDescriptor()); //This method sends a message to all clients connected + if(clients.get() == 0) { + broadcast("You are the first client to join"); + } + System.out.println(conn.getRemoteSocketAddress().getAddress().getHostAddress() + " entered the room!"); + clients.incrementAndGet(); + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + broadcast(conn + " has left the room!"); + System.out.println(conn + " has left the room!"); + if(clients.decrementAndGet() <= 0) { + System.out.println("No more clients left, exiting"); + System.exit(0); + } + } + + @Override + public void onMessage(WebSocket conn, String message) { + broadcast(message); + System.out.println(conn + ": " + message); + } + + @Override + public void onMessage(WebSocket conn, ByteBuffer message) { + broadcast(message.array()); + System.out.println(conn + ": " + message); + } + + + public static void main(String[] args) throws InterruptedException, IOException { + if(System.inheritedChannel() == null) { + System.err.println("System.inheritedChannel() is null, make sure this program is started with file descriptor zero being a listening socket"); + System.exit(1); + } + SocketActivation s = new SocketActivation((ServerSocketChannel)System.inheritedChannel()); + s.start(); + System.out.println(">>>> SocketActivation started on port: " + s.getPort() + " <<<<"); + } + + @Override + public void onError(WebSocket conn, Exception ex) { + ex.printStackTrace(); + } + + @Override + public void onStart() { + System.out.println("Server started!"); + } + +} diff --git a/src/main/example/TwoWaySSLServerExample.java b/src/main/example/TwoWaySSLServerExample.java new file mode 100644 index 000000000..2e52f7aa2 --- /dev/null +++ b/src/main/example/TwoWaySSLServerExample.java @@ -0,0 +1,82 @@ + + +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +import java.io.File; +import java.io.FileInputStream; +import java.nio.file.Paths; +import java.security.KeyStore; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.TrustManagerFactory; +import org.java_websocket.server.SSLParametersWebSocketServerFactory; + +/** + * Copy of SSLServerExample except we use @link SSLEngineWebSocketServerFactory to customize + * clientMode/ClientAuth to force client to present a cert. Example of Two-way + * ssl/MutualAuthentication/ClientAuthentication + */ +public class TwoWaySSLServerExample { + + /* + * Keystore with certificate created like so (in JKS format): + * + *keytool -genkey -keyalg RSA -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry" + */ + public static void main(String[] args) throws Exception { + ChatServer chatserver = new ChatServer( + 8887); // Firefox does allow multible ssl connection only via port 443 //tested on FF16 + + // load up the key store + String STORETYPE = "JKS"; + String KEYSTORE = Paths.get("src", "test", "java", "org", "java_websocket", "keystore.jks") + .toString(); + String STOREPASSWORD = "storepassword"; + String KEYPASSWORD = "keypassword"; + + KeyStore ks = KeyStore.getInstance(STORETYPE); + File kf = new File(KEYSTORE); + ks.load(new FileInputStream(kf), STOREPASSWORD.toCharArray()); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, KEYPASSWORD.toCharArray()); + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(ks); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + SSLParameters sslParameters = new SSLParameters(); + // This is all we need + sslParameters.setNeedClientAuth(true); + chatserver + .setWebSocketFactory(new SSLParametersWebSocketServerFactory(sslContext, sslParameters)); + + chatserver.start(); + + } +} diff --git a/src/main/example/chat.html b/src/main/example/chat.html deleted file mode 100644 index 4a1327e86..000000000 --- a/src/main/example/chat.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - WebSocket Chat Client - - - - - - - -


-

-

- - diff --git a/src/main/example/jws-activation.service b/src/main/example/jws-activation.service new file mode 100644 index 000000000..0ae3d091a --- /dev/null +++ b/src/main/example/jws-activation.service @@ -0,0 +1,17 @@ +[Unit] +Description=Java-WebSocket systemd activation demo service +After=network.target jws-activation.socket +Requires=jws-activation.socket + +[Service] +Type=simple +# Place the command for running SocketActivation.java in file "$HOME"/jws_activation_command: +ExecStart=/bin/sh %h/jws_activation_run +TimeoutStopSec=5 +StandardError=journal +StandardOutput=journal +# This is very important - systemd will pass the socket as file descriptor zero, which is what Java expects +StandardInput=socket + +[Install] +WantedBy=default.target diff --git a/src/main/example/jws-activation.socket b/src/main/example/jws-activation.socket new file mode 100644 index 000000000..db769c3e1 --- /dev/null +++ b/src/main/example/jws-activation.socket @@ -0,0 +1,9 @@ +[Unit] +Description=Java-WebSocket systemd activation demo socket +PartOf=jws-activation.service + +[Socket] +ListenStream=127.0.0.1:9999 + +[Install] +WantedBy=sockets.target diff --git a/src/main/example/prototype.js b/src/main/example/prototype.js deleted file mode 100644 index 9fe6e1243..000000000 --- a/src/main/example/prototype.js +++ /dev/null @@ -1,4874 +0,0 @@ -/* Prototype JavaScript framework, version 1.6.1 - * (c) 2005-2009 Sam Stephenson - * - * Prototype is freely distributable under the terms of an MIT-style license. - * For details, see the Prototype web site: http://www.prototypejs.org/ - * - *--------------------------------------------------------------------------*/ - -var Prototype = { - Version: '1.6.1', - - Browser: (function(){ - var ua = navigator.userAgent; - var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; - return { - IE: !!window.attachEvent && !isOpera, - Opera: isOpera, - WebKit: ua.indexOf('AppleWebKit/') > -1, - Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, - MobileSafari: /Apple.*Mobile.*Safari/.test(ua) - } - })(), - - BrowserFeatures: { - XPath: !!document.evaluate, - SelectorsAPI: !!document.querySelector, - ElementExtensions: (function() { - var constructor = window.Element || window.HTMLElement; - return !!(constructor && constructor.prototype); - })(), - SpecificElementExtensions: (function() { - if (typeof window.HTMLDivElement !== 'undefined') - return true; - - var div = document.createElement('div'); - var form = document.createElement('form'); - var isSupported = false; - - if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { - isSupported = true; - } - - div = form = null; - - return isSupported; - })() - }, - - ScriptFragment: ']*>([\\S\\s]*?)<\/script>', - JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, - - emptyFunction: function() { }, - K: function(x) { return x } -}; - -if (Prototype.Browser.MobileSafari) - Prototype.BrowserFeatures.SpecificElementExtensions = false; - - -var Abstract = { }; - - -var Try = { - these: function() { - var returnValue; - - for (var i = 0, length = arguments.length; i < length; i++) { - var lambda = arguments[i]; - try { - returnValue = lambda(); - break; - } catch (e) { } - } - - return returnValue; - } -}; - -/* Based on Alex Arnell's inheritance implementation. */ - -var Class = (function() { - function subclass() {}; - function create() { - var parent = null, properties = $A(arguments); - if (Object.isFunction(properties[0])) - parent = properties.shift(); - - function klass() { - this.initialize.apply(this, arguments); - } - - Object.extend(klass, Class.Methods); - klass.superclass = parent; - klass.subclasses = []; - - if (parent) { - subclass.prototype = parent.prototype; - klass.prototype = new subclass; - parent.subclasses.push(klass); - } - - for (var i = 0; i < properties.length; i++) - klass.addMethods(properties[i]); - - if (!klass.prototype.initialize) - klass.prototype.initialize = Prototype.emptyFunction; - - klass.prototype.constructor = klass; - return klass; - } - - function addMethods(source) { - var ancestor = this.superclass && this.superclass.prototype; - var properties = Object.keys(source); - - if (!Object.keys({ toString: true }).length) { - if (source.toString != Object.prototype.toString) - properties.push("toString"); - if (source.valueOf != Object.prototype.valueOf) - properties.push("valueOf"); - } - - for (var i = 0, length = properties.length; i < length; i++) { - var property = properties[i], value = source[property]; - if (ancestor && Object.isFunction(value) && - value.argumentNames().first() == "$super") { - var method = value; - value = (function(m) { - return function() { return ancestor[m].apply(this, arguments); }; - })(property).wrap(method); - - value.valueOf = method.valueOf.bind(method); - value.toString = method.toString.bind(method); - } - this.prototype[property] = value; - } - - return this; - } - - return { - create: create, - Methods: { - addMethods: addMethods - } - }; -})(); -(function() { - - var _toString = Object.prototype.toString; - - function extend(destination, source) { - for (var property in source) - destination[property] = source[property]; - return destination; - } - - function inspect(object) { - try { - if (isUndefined(object)) return 'undefined'; - if (object === null) return 'null'; - return object.inspect ? object.inspect() : String(object); - } catch (e) { - if (e instanceof RangeError) return '...'; - throw e; - } - } - - function toJSON(object) { - var type = typeof object; - switch (type) { - case 'undefined': - case 'function': - case 'unknown': return; - case 'boolean': return object.toString(); - } - - if (object === null) return 'null'; - if (object.toJSON) return object.toJSON(); - if (isElement(object)) return; - - var results = []; - for (var property in object) { - var value = toJSON(object[property]); - if (!isUndefined(value)) - results.push(property.toJSON() + ': ' + value); - } - - return '{' + results.join(', ') + '}'; - } - - function toQueryString(object) { - return $H(object).toQueryString(); - } - - function toHTML(object) { - return object && object.toHTML ? object.toHTML() : String.interpret(object); - } - - function keys(object) { - var results = []; - for (var property in object) - results.push(property); - return results; - } - - function values(object) { - var results = []; - for (var property in object) - results.push(object[property]); - return results; - } - - function clone(object) { - return extend({ }, object); - } - - function isElement(object) { - return !!(object && object.nodeType == 1); - } - - function isArray(object) { - return _toString.call(object) == "[object Array]"; - } - - - function isHash(object) { - return object instanceof Hash; - } - - function isFunction(object) { - return typeof object === "function"; - } - - function isString(object) { - return _toString.call(object) == "[object String]"; - } - - function isNumber(object) { - return _toString.call(object) == "[object Number]"; - } - - function isUndefined(object) { - return typeof object === "undefined"; - } - - extend(Object, { - extend: extend, - inspect: inspect, - toJSON: toJSON, - toQueryString: toQueryString, - toHTML: toHTML, - keys: keys, - values: values, - clone: clone, - isElement: isElement, - isArray: isArray, - isHash: isHash, - isFunction: isFunction, - isString: isString, - isNumber: isNumber, - isUndefined: isUndefined - }); -})(); -Object.extend(Function.prototype, (function() { - var slice = Array.prototype.slice; - - function update(array, args) { - var arrayLength = array.length, length = args.length; - while (length--) array[arrayLength + length] = args[length]; - return array; - } - - function merge(array, args) { - array = slice.call(array, 0); - return update(array, args); - } - - function argumentNames() { - var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] - .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') - .replace(/\s+/g, '').split(','); - return names.length == 1 && !names[0] ? [] : names; - } - - function bind(context) { - if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; - var __method = this, args = slice.call(arguments, 1); - return function() { - var a = merge(args, arguments); - return __method.apply(context, a); - } - } - - function bindAsEventListener(context) { - var __method = this, args = slice.call(arguments, 1); - return function(event) { - var a = update([event || window.event], args); - return __method.apply(context, a); - } - } - - function curry() { - if (!arguments.length) return this; - var __method = this, args = slice.call(arguments, 0); - return function() { - var a = merge(args, arguments); - return __method.apply(this, a); - } - } - - function delay(timeout) { - var __method = this, args = slice.call(arguments, 1); - timeout = timeout * 1000 - return window.setTimeout(function() { - return __method.apply(__method, args); - }, timeout); - } - - function defer() { - var args = update([0.01], arguments); - return this.delay.apply(this, args); - } - - function wrap(wrapper) { - var __method = this; - return function() { - var a = update([__method.bind(this)], arguments); - return wrapper.apply(this, a); - } - } - - function methodize() { - if (this._methodized) return this._methodized; - var __method = this; - return this._methodized = function() { - var a = update([this], arguments); - return __method.apply(null, a); - }; - } - - return { - argumentNames: argumentNames, - bind: bind, - bindAsEventListener: bindAsEventListener, - curry: curry, - delay: delay, - defer: defer, - wrap: wrap, - methodize: methodize - } -})()); - - -Date.prototype.toJSON = function() { - return '"' + this.getUTCFullYear() + '-' + - (this.getUTCMonth() + 1).toPaddedString(2) + '-' + - this.getUTCDate().toPaddedString(2) + 'T' + - this.getUTCHours().toPaddedString(2) + ':' + - this.getUTCMinutes().toPaddedString(2) + ':' + - this.getUTCSeconds().toPaddedString(2) + 'Z"'; -}; - - -RegExp.prototype.match = RegExp.prototype.test; - -RegExp.escape = function(str) { - return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); -}; -var PeriodicalExecuter = Class.create({ - initialize: function(callback, frequency) { - this.callback = callback; - this.frequency = frequency; - this.currentlyExecuting = false; - - this.registerCallback(); - }, - - registerCallback: function() { - this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); - }, - - execute: function() { - this.callback(this); - }, - - stop: function() { - if (!this.timer) return; - clearInterval(this.timer); - this.timer = null; - }, - - onTimerEvent: function() { - if (!this.currentlyExecuting) { - try { - this.currentlyExecuting = true; - this.execute(); - this.currentlyExecuting = false; - } catch(e) { - this.currentlyExecuting = false; - throw e; - } - } - } -}); -Object.extend(String, { - interpret: function(value) { - return value == null ? '' : String(value); - }, - specialChar: { - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '\\': '\\\\' - } -}); - -Object.extend(String.prototype, (function() { - - function prepareReplacement(replacement) { - if (Object.isFunction(replacement)) return replacement; - var template = new Template(replacement); - return function(match) { return template.evaluate(match) }; - } - - function gsub(pattern, replacement) { - var result = '', source = this, match; - replacement = prepareReplacement(replacement); - - if (Object.isString(pattern)) - pattern = RegExp.escape(pattern); - - if (!(pattern.length || pattern.source)) { - replacement = replacement(''); - return replacement + source.split('').join(replacement) + replacement; - } - - while (source.length > 0) { - if (match = source.match(pattern)) { - result += source.slice(0, match.index); - result += String.interpret(replacement(match)); - source = source.slice(match.index + match[0].length); - } else { - result += source, source = ''; - } - } - return result; - } - - function sub(pattern, replacement, count) { - replacement = prepareReplacement(replacement); - count = Object.isUndefined(count) ? 1 : count; - - return this.gsub(pattern, function(match) { - if (--count < 0) return match[0]; - return replacement(match); - }); - } - - function scan(pattern, iterator) { - this.gsub(pattern, iterator); - return String(this); - } - - function truncate(length, truncation) { - length = length || 30; - truncation = Object.isUndefined(truncation) ? '...' : truncation; - return this.length > length ? - this.slice(0, length - truncation.length) + truncation : String(this); - } - - function strip() { - return this.replace(/^\s+/, '').replace(/\s+$/, ''); - } - - function stripTags() { - return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); - } - - function stripScripts() { - return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); - } - - function extractScripts() { - var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); - var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); - return (this.match(matchAll) || []).map(function(scriptTag) { - return (scriptTag.match(matchOne) || ['', ''])[1]; - }); - } - - function evalScripts() { - return this.extractScripts().map(function(script) { return eval(script) }); - } - - function escapeHTML() { - return this.replace(/&/g,'&').replace(//g,'>'); - } - - function unescapeHTML() { - return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); - } - - - function toQueryParams(separator) { - var match = this.strip().match(/([^?#]*)(#.*)?$/); - if (!match) return { }; - - return match[1].split(separator || '&').inject({ }, function(hash, pair) { - if ((pair = pair.split('='))[0]) { - var key = decodeURIComponent(pair.shift()); - var value = pair.length > 1 ? pair.join('=') : pair[0]; - if (value != undefined) value = decodeURIComponent(value); - - if (key in hash) { - if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; - hash[key].push(value); - } - else hash[key] = value; - } - return hash; - }); - } - - function toArray() { - return this.split(''); - } - - function succ() { - return this.slice(0, this.length - 1) + - String.fromCharCode(this.charCodeAt(this.length - 1) + 1); - } - - function times(count) { - return count < 1 ? '' : new Array(count + 1).join(this); - } - - function camelize() { - var parts = this.split('-'), len = parts.length; - if (len == 1) return parts[0]; - - var camelized = this.charAt(0) == '-' - ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) - : parts[0]; - - for (var i = 1; i < len; i++) - camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); - - return camelized; - } - - function capitalize() { - return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); - } - - function underscore() { - return this.replace(/::/g, '/') - .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') - .replace(/([a-z\d])([A-Z])/g, '$1_$2') - .replace(/-/g, '_') - .toLowerCase(); - } - - function dasherize() { - return this.replace(/_/g, '-'); - } - - function inspect(useDoubleQuotes) { - var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { - if (character in String.specialChar) { - return String.specialChar[character]; - } - return '\\u00' + character.charCodeAt().toPaddedString(2, 16); - }); - if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; - return "'" + escapedString.replace(/'/g, '\\\'') + "'"; - } - - function toJSON() { - return this.inspect(true); - } - - function unfilterJSON(filter) { - return this.replace(filter || Prototype.JSONFilter, '$1'); - } - - function isJSON() { - var str = this; - if (str.blank()) return false; - str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); - return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); - } - - function evalJSON(sanitize) { - var json = this.unfilterJSON(); - try { - if (!sanitize || json.isJSON()) return eval('(' + json + ')'); - } catch (e) { } - throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); - } - - function include(pattern) { - return this.indexOf(pattern) > -1; - } - - function startsWith(pattern) { - return this.indexOf(pattern) === 0; - } - - function endsWith(pattern) { - var d = this.length - pattern.length; - return d >= 0 && this.lastIndexOf(pattern) === d; - } - - function empty() { - return this == ''; - } - - function blank() { - return /^\s*$/.test(this); - } - - function interpolate(object, pattern) { - return new Template(this, pattern).evaluate(object); - } - - return { - gsub: gsub, - sub: sub, - scan: scan, - truncate: truncate, - strip: String.prototype.trim ? String.prototype.trim : strip, - stripTags: stripTags, - stripScripts: stripScripts, - extractScripts: extractScripts, - evalScripts: evalScripts, - escapeHTML: escapeHTML, - unescapeHTML: unescapeHTML, - toQueryParams: toQueryParams, - parseQuery: toQueryParams, - toArray: toArray, - succ: succ, - times: times, - camelize: camelize, - capitalize: capitalize, - underscore: underscore, - dasherize: dasherize, - inspect: inspect, - toJSON: toJSON, - unfilterJSON: unfilterJSON, - isJSON: isJSON, - evalJSON: evalJSON, - include: include, - startsWith: startsWith, - endsWith: endsWith, - empty: empty, - blank: blank, - interpolate: interpolate - }; -})()); - -var Template = Class.create({ - initialize: function(template, pattern) { - this.template = template.toString(); - this.pattern = pattern || Template.Pattern; - }, - - evaluate: function(object) { - if (object && Object.isFunction(object.toTemplateReplacements)) - object = object.toTemplateReplacements(); - - return this.template.gsub(this.pattern, function(match) { - if (object == null) return (match[1] + ''); - - var before = match[1] || ''; - if (before == '\\') return match[2]; - - var ctx = object, expr = match[3]; - var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; - match = pattern.exec(expr); - if (match == null) return before; - - while (match != null) { - var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; - ctx = ctx[comp]; - if (null == ctx || '' == match[3]) break; - expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); - match = pattern.exec(expr); - } - - return before + String.interpret(ctx); - }); - } -}); -Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; - -var $break = { }; - -var Enumerable = (function() { - function each(iterator, context) { - var index = 0; - try { - this._each(function(value) { - iterator.call(context, value, index++); - }); - } catch (e) { - if (e != $break) throw e; - } - return this; - } - - function eachSlice(number, iterator, context) { - var index = -number, slices = [], array = this.toArray(); - if (number < 1) return array; - while ((index += number) < array.length) - slices.push(array.slice(index, index+number)); - return slices.collect(iterator, context); - } - - function all(iterator, context) { - iterator = iterator || Prototype.K; - var result = true; - this.each(function(value, index) { - result = result && !!iterator.call(context, value, index); - if (!result) throw $break; - }); - return result; - } - - function any(iterator, context) { - iterator = iterator || Prototype.K; - var result = false; - this.each(function(value, index) { - if (result = !!iterator.call(context, value, index)) - throw $break; - }); - return result; - } - - function collect(iterator, context) { - iterator = iterator || Prototype.K; - var results = []; - this.each(function(value, index) { - results.push(iterator.call(context, value, index)); - }); - return results; - } - - function detect(iterator, context) { - var result; - this.each(function(value, index) { - if (iterator.call(context, value, index)) { - result = value; - throw $break; - } - }); - return result; - } - - function findAll(iterator, context) { - var results = []; - this.each(function(value, index) { - if (iterator.call(context, value, index)) - results.push(value); - }); - return results; - } - - function grep(filter, iterator, context) { - iterator = iterator || Prototype.K; - var results = []; - - if (Object.isString(filter)) - filter = new RegExp(RegExp.escape(filter)); - - this.each(function(value, index) { - if (filter.match(value)) - results.push(iterator.call(context, value, index)); - }); - return results; - } - - function include(object) { - if (Object.isFunction(this.indexOf)) - if (this.indexOf(object) != -1) return true; - - var found = false; - this.each(function(value) { - if (value == object) { - found = true; - throw $break; - } - }); - return found; - } - - function inGroupsOf(number, fillWith) { - fillWith = Object.isUndefined(fillWith) ? null : fillWith; - return this.eachSlice(number, function(slice) { - while(slice.length < number) slice.push(fillWith); - return slice; - }); - } - - function inject(memo, iterator, context) { - this.each(function(value, index) { - memo = iterator.call(context, memo, value, index); - }); - return memo; - } - - function invoke(method) { - var args = $A(arguments).slice(1); - return this.map(function(value) { - return value[method].apply(value, args); - }); - } - - function max(iterator, context) { - iterator = iterator || Prototype.K; - var result; - this.each(function(value, index) { - value = iterator.call(context, value, index); - if (result == null || value >= result) - result = value; - }); - return result; - } - - function min(iterator, context) { - iterator = iterator || Prototype.K; - var result; - this.each(function(value, index) { - value = iterator.call(context, value, index); - if (result == null || value < result) - result = value; - }); - return result; - } - - function partition(iterator, context) { - iterator = iterator || Prototype.K; - var trues = [], falses = []; - this.each(function(value, index) { - (iterator.call(context, value, index) ? - trues : falses).push(value); - }); - return [trues, falses]; - } - - function pluck(property) { - var results = []; - this.each(function(value) { - results.push(value[property]); - }); - return results; - } - - function reject(iterator, context) { - var results = []; - this.each(function(value, index) { - if (!iterator.call(context, value, index)) - results.push(value); - }); - return results; - } - - function sortBy(iterator, context) { - return this.map(function(value, index) { - return { - value: value, - criteria: iterator.call(context, value, index) - }; - }).sort(function(left, right) { - var a = left.criteria, b = right.criteria; - return a < b ? -1 : a > b ? 1 : 0; - }).pluck('value'); - } - - function toArray() { - return this.map(); - } - - function zip() { - var iterator = Prototype.K, args = $A(arguments); - if (Object.isFunction(args.last())) - iterator = args.pop(); - - var collections = [this].concat(args).map($A); - return this.map(function(value, index) { - return iterator(collections.pluck(index)); - }); - } - - function size() { - return this.toArray().length; - } - - function inspect() { - return '#'; - } - - - - - - - - - - return { - each: each, - eachSlice: eachSlice, - all: all, - every: all, - any: any, - some: any, - collect: collect, - map: collect, - detect: detect, - findAll: findAll, - select: findAll, - filter: findAll, - grep: grep, - include: include, - member: include, - inGroupsOf: inGroupsOf, - inject: inject, - invoke: invoke, - max: max, - min: min, - partition: partition, - pluck: pluck, - reject: reject, - sortBy: sortBy, - toArray: toArray, - entries: toArray, - zip: zip, - size: size, - inspect: inspect, - find: detect - }; -})(); -function $A(iterable) { - if (!iterable) return []; - if ('toArray' in Object(iterable)) return iterable.toArray(); - var length = iterable.length || 0, results = new Array(length); - while (length--) results[length] = iterable[length]; - return results; -} - -function $w(string) { - if (!Object.isString(string)) return []; - string = string.strip(); - return string ? string.split(/\s+/) : []; -} - -Array.from = $A; - - -(function() { - var arrayProto = Array.prototype, - slice = arrayProto.slice, - _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available - - function each(iterator) { - for (var i = 0, length = this.length; i < length; i++) - iterator(this[i]); - } - if (!_each) _each = each; - - function clear() { - this.length = 0; - return this; - } - - function first() { - return this[0]; - } - - function last() { - return this[this.length - 1]; - } - - function compact() { - return this.select(function(value) { - return value != null; - }); - } - - function flatten() { - return this.inject([], function(array, value) { - if (Object.isArray(value)) - return array.concat(value.flatten()); - array.push(value); - return array; - }); - } - - function without() { - var values = slice.call(arguments, 0); - return this.select(function(value) { - return !values.include(value); - }); - } - - function reverse(inline) { - return (inline !== false ? this : this.toArray())._reverse(); - } - - function uniq(sorted) { - return this.inject([], function(array, value, index) { - if (0 == index || (sorted ? array.last() != value : !array.include(value))) - array.push(value); - return array; - }); - } - - function intersect(array) { - return this.uniq().findAll(function(item) { - return array.detect(function(value) { return item === value }); - }); - } - - - function clone() { - return slice.call(this, 0); - } - - function size() { - return this.length; - } - - function inspect() { - return '[' + this.map(Object.inspect).join(', ') + ']'; - } - - function toJSON() { - var results = []; - this.each(function(object) { - var value = Object.toJSON(object); - if (!Object.isUndefined(value)) results.push(value); - }); - return '[' + results.join(', ') + ']'; - } - - function indexOf(item, i) { - i || (i = 0); - var length = this.length; - if (i < 0) i = length + i; - for (; i < length; i++) - if (this[i] === item) return i; - return -1; - } - - function lastIndexOf(item, i) { - i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; - var n = this.slice(0, i).reverse().indexOf(item); - return (n < 0) ? n : i - n - 1; - } - - function concat() { - var array = slice.call(this, 0), item; - for (var i = 0, length = arguments.length; i < length; i++) { - item = arguments[i]; - if (Object.isArray(item) && !('callee' in item)) { - for (var j = 0, arrayLength = item.length; j < arrayLength; j++) - array.push(item[j]); - } else { - array.push(item); - } - } - return array; - } - - Object.extend(arrayProto, Enumerable); - - if (!arrayProto._reverse) - arrayProto._reverse = arrayProto.reverse; - - Object.extend(arrayProto, { - _each: _each, - clear: clear, - first: first, - last: last, - compact: compact, - flatten: flatten, - without: without, - reverse: reverse, - uniq: uniq, - intersect: intersect, - clone: clone, - toArray: clone, - size: size, - inspect: inspect, - toJSON: toJSON - }); - - var CONCAT_ARGUMENTS_BUGGY = (function() { - return [].concat(arguments)[0][0] !== 1; - })(1,2) - - if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; - - if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; - if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; -})(); -function $H(object) { - return new Hash(object); -}; - -var Hash = Class.create(Enumerable, (function() { - function initialize(object) { - this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); - } - - function _each(iterator) { - for (var key in this._object) { - var value = this._object[key], pair = [key, value]; - pair.key = key; - pair.value = value; - iterator(pair); - } - } - - function set(key, value) { - return this._object[key] = value; - } - - function get(key) { - if (this._object[key] !== Object.prototype[key]) - return this._object[key]; - } - - function unset(key) { - var value = this._object[key]; - delete this._object[key]; - return value; - } - - function toObject() { - return Object.clone(this._object); - } - - function keys() { - return this.pluck('key'); - } - - function values() { - return this.pluck('value'); - } - - function index(value) { - var match = this.detect(function(pair) { - return pair.value === value; - }); - return match && match.key; - } - - function merge(object) { - return this.clone().update(object); - } - - function update(object) { - return new Hash(object).inject(this, function(result, pair) { - result.set(pair.key, pair.value); - return result; - }); - } - - function toQueryPair(key, value) { - if (Object.isUndefined(value)) return key; - return key + '=' + encodeURIComponent(String.interpret(value)); - } - - function toQueryString() { - return this.inject([], function(results, pair) { - var key = encodeURIComponent(pair.key), values = pair.value; - - if (values && typeof values == 'object') { - if (Object.isArray(values)) - return results.concat(values.map(toQueryPair.curry(key))); - } else results.push(toQueryPair(key, values)); - return results; - }).join('&'); - } - - function inspect() { - return '#'; - } - - function toJSON() { - return Object.toJSON(this.toObject()); - } - - function clone() { - return new Hash(this); - } - - return { - initialize: initialize, - _each: _each, - set: set, - get: get, - unset: unset, - toObject: toObject, - toTemplateReplacements: toObject, - keys: keys, - values: values, - index: index, - merge: merge, - update: update, - toQueryString: toQueryString, - inspect: inspect, - toJSON: toJSON, - clone: clone - }; -})()); - -Hash.from = $H; -Object.extend(Number.prototype, (function() { - function toColorPart() { - return this.toPaddedString(2, 16); - } - - function succ() { - return this + 1; - } - - function times(iterator, context) { - $R(0, this, true).each(iterator, context); - return this; - } - - function toPaddedString(length, radix) { - var string = this.toString(radix || 10); - return '0'.times(length - string.length) + string; - } - - function toJSON() { - return isFinite(this) ? this.toString() : 'null'; - } - - function abs() { - return Math.abs(this); - } - - function round() { - return Math.round(this); - } - - function ceil() { - return Math.ceil(this); - } - - function floor() { - return Math.floor(this); - } - - return { - toColorPart: toColorPart, - succ: succ, - times: times, - toPaddedString: toPaddedString, - toJSON: toJSON, - abs: abs, - round: round, - ceil: ceil, - floor: floor - }; -})()); - -function $R(start, end, exclusive) { - return new ObjectRange(start, end, exclusive); -} - -var ObjectRange = Class.create(Enumerable, (function() { - function initialize(start, end, exclusive) { - this.start = start; - this.end = end; - this.exclusive = exclusive; - } - - function _each(iterator) { - var value = this.start; - while (this.include(value)) { - iterator(value); - value = value.succ(); - } - } - - function include(value) { - if (value < this.start) - return false; - if (this.exclusive) - return value < this.end; - return value <= this.end; - } - - return { - initialize: initialize, - _each: _each, - include: include - }; -})()); - - - -var Ajax = { - getTransport: function() { - return Try.these( - function() {return new XMLHttpRequest()}, - function() {return new ActiveXObject('Msxml2.XMLHTTP')}, - function() {return new ActiveXObject('Microsoft.XMLHTTP')} - ) || false; - }, - - activeRequestCount: 0 -}; - -Ajax.Responders = { - responders: [], - - _each: function(iterator) { - this.responders._each(iterator); - }, - - register: function(responder) { - if (!this.include(responder)) - this.responders.push(responder); - }, - - unregister: function(responder) { - this.responders = this.responders.without(responder); - }, - - dispatch: function(callback, request, transport, json) { - this.each(function(responder) { - if (Object.isFunction(responder[callback])) { - try { - responder[callback].apply(responder, [request, transport, json]); - } catch (e) { } - } - }); - } -}; - -Object.extend(Ajax.Responders, Enumerable); - -Ajax.Responders.register({ - onCreate: function() { Ajax.activeRequestCount++ }, - onComplete: function() { Ajax.activeRequestCount-- } -}); -Ajax.Base = Class.create({ - initialize: function(options) { - this.options = { - method: 'post', - asynchronous: true, - contentType: 'application/x-www-form-urlencoded', - encoding: 'UTF-8', - parameters: '', - evalJSON: true, - evalJS: true - }; - Object.extend(this.options, options || { }); - - this.options.method = this.options.method.toLowerCase(); - - if (Object.isString(this.options.parameters)) - this.options.parameters = this.options.parameters.toQueryParams(); - else if (Object.isHash(this.options.parameters)) - this.options.parameters = this.options.parameters.toObject(); - } -}); -Ajax.Request = Class.create(Ajax.Base, { - _complete: false, - - initialize: function($super, url, options) { - $super(options); - this.transport = Ajax.getTransport(); - this.request(url); - }, - - request: function(url) { - this.url = url; - this.method = this.options.method; - var params = Object.clone(this.options.parameters); - - if (!['get', 'post'].include(this.method)) { - params['_method'] = this.method; - this.method = 'post'; - } - - this.parameters = params; - - if (params = Object.toQueryString(params)) { - if (this.method == 'get') - this.url += (this.url.include('?') ? '&' : '?') + params; - else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) - params += '&_='; - } - - try { - var response = new Ajax.Response(this); - if (this.options.onCreate) this.options.onCreate(response); - Ajax.Responders.dispatch('onCreate', this, response); - - this.transport.open(this.method.toUpperCase(), this.url, - this.options.asynchronous); - - if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); - - this.transport.onreadystatechange = this.onStateChange.bind(this); - this.setRequestHeaders(); - - this.body = this.method == 'post' ? (this.options.postBody || params) : null; - this.transport.send(this.body); - - /* Force Firefox to handle ready state 4 for synchronous requests */ - if (!this.options.asynchronous && this.transport.overrideMimeType) - this.onStateChange(); - - } - catch (e) { - this.dispatchException(e); - } - }, - - onStateChange: function() { - var readyState = this.transport.readyState; - if (readyState > 1 && !((readyState == 4) && this._complete)) - this.respondToReadyState(this.transport.readyState); - }, - - setRequestHeaders: function() { - var headers = { - 'X-Requested-With': 'XMLHttpRequest', - 'X-Prototype-Version': Prototype.Version, - 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' - }; - - if (this.method == 'post') { - headers['Content-type'] = this.options.contentType + - (this.options.encoding ? '; charset=' + this.options.encoding : ''); - - /* Force "Connection: close" for older Mozilla browsers to work - * around a bug where XMLHttpRequest sends an incorrect - * Content-length header. See Mozilla Bugzilla #246651. - */ - if (this.transport.overrideMimeType && - (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) - headers['Connection'] = 'close'; - } - - if (typeof this.options.requestHeaders == 'object') { - var extras = this.options.requestHeaders; - - if (Object.isFunction(extras.push)) - for (var i = 0, length = extras.length; i < length; i += 2) - headers[extras[i]] = extras[i+1]; - else - $H(extras).each(function(pair) { headers[pair.key] = pair.value }); - } - - for (var name in headers) - this.transport.setRequestHeader(name, headers[name]); - }, - - success: function() { - var status = this.getStatus(); - return !status || (status >= 200 && status < 300); - }, - - getStatus: function() { - try { - return this.transport.status || 0; - } catch (e) { return 0 } - }, - - respondToReadyState: function(readyState) { - var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); - - if (state == 'Complete') { - try { - this._complete = true; - (this.options['on' + response.status] - || this.options['on' + (this.success() ? 'Success' : 'Failure')] - || Prototype.emptyFunction)(response, response.headerJSON); - } catch (e) { - this.dispatchException(e); - } - - var contentType = response.getHeader('Content-type'); - if (this.options.evalJS == 'force' - || (this.options.evalJS && this.isSameOrigin() && contentType - && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) - this.evalResponse(); - } - - try { - (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); - Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); - } catch (e) { - this.dispatchException(e); - } - - if (state == 'Complete') { - this.transport.onreadystatechange = Prototype.emptyFunction; - } - }, - - isSameOrigin: function() { - var m = this.url.match(/^\s*https?:\/\/[^\/]*/); - return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ - protocol: location.protocol, - domain: document.domain, - port: location.port ? ':' + location.port : '' - })); - }, - - getHeader: function(name) { - try { - return this.transport.getResponseHeader(name) || null; - } catch (e) { return null; } - }, - - evalResponse: function() { - try { - return eval((this.transport.responseText || '').unfilterJSON()); - } catch (e) { - this.dispatchException(e); - } - }, - - dispatchException: function(exception) { - (this.options.onException || Prototype.emptyFunction)(this, exception); - Ajax.Responders.dispatch('onException', this, exception); - } -}); - -Ajax.Request.Events = - ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; - - - - - - - - -Ajax.Response = Class.create({ - initialize: function(request){ - this.request = request; - var transport = this.transport = request.transport, - readyState = this.readyState = transport.readyState; - - if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { - this.status = this.getStatus(); - this.statusText = this.getStatusText(); - this.responseText = String.interpret(transport.responseText); - this.headerJSON = this._getHeaderJSON(); - } - - if(readyState == 4) { - var xml = transport.responseXML; - this.responseXML = Object.isUndefined(xml) ? null : xml; - this.responseJSON = this._getResponseJSON(); - } - }, - - status: 0, - - statusText: '', - - getStatus: Ajax.Request.prototype.getStatus, - - getStatusText: function() { - try { - return this.transport.statusText || ''; - } catch (e) { return '' } - }, - - getHeader: Ajax.Request.prototype.getHeader, - - getAllHeaders: function() { - try { - return this.getAllResponseHeaders(); - } catch (e) { return null } - }, - - getResponseHeader: function(name) { - return this.transport.getResponseHeader(name); - }, - - getAllResponseHeaders: function() { - return this.transport.getAllResponseHeaders(); - }, - - _getHeaderJSON: function() { - var json = this.getHeader('X-JSON'); - if (!json) return null; - json = decodeURIComponent(escape(json)); - try { - return json.evalJSON(this.request.options.sanitizeJSON || - !this.request.isSameOrigin()); - } catch (e) { - this.request.dispatchException(e); - } - }, - - _getResponseJSON: function() { - var options = this.request.options; - if (!options.evalJSON || (options.evalJSON != 'force' && - !(this.getHeader('Content-type') || '').include('application/json')) || - this.responseText.blank()) - return null; - try { - return this.responseText.evalJSON(options.sanitizeJSON || - !this.request.isSameOrigin()); - } catch (e) { - this.request.dispatchException(e); - } - } -}); - -Ajax.Updater = Class.create(Ajax.Request, { - initialize: function($super, container, url, options) { - this.container = { - success: (container.success || container), - failure: (container.failure || (container.success ? null : container)) - }; - - options = Object.clone(options); - var onComplete = options.onComplete; - options.onComplete = (function(response, json) { - this.updateContent(response.responseText); - if (Object.isFunction(onComplete)) onComplete(response, json); - }).bind(this); - - $super(url, options); - }, - - updateContent: function(responseText) { - var receiver = this.container[this.success() ? 'success' : 'failure'], - options = this.options; - - if (!options.evalScripts) responseText = responseText.stripScripts(); - - if (receiver = $(receiver)) { - if (options.insertion) { - if (Object.isString(options.insertion)) { - var insertion = { }; insertion[options.insertion] = responseText; - receiver.insert(insertion); - } - else options.insertion(receiver, responseText); - } - else receiver.update(responseText); - } - } -}); - -Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { - initialize: function($super, container, url, options) { - $super(options); - this.onComplete = this.options.onComplete; - - this.frequency = (this.options.frequency || 2); - this.decay = (this.options.decay || 1); - - this.updater = { }; - this.container = container; - this.url = url; - - this.start(); - }, - - start: function() { - this.options.onComplete = this.updateComplete.bind(this); - this.onTimerEvent(); - }, - - stop: function() { - this.updater.options.onComplete = undefined; - clearTimeout(this.timer); - (this.onComplete || Prototype.emptyFunction).apply(this, arguments); - }, - - updateComplete: function(response) { - if (this.options.decay) { - this.decay = (response.responseText == this.lastText ? - this.decay * this.options.decay : 1); - - this.lastText = response.responseText; - } - this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); - }, - - onTimerEvent: function() { - this.updater = new Ajax.Updater(this.container, this.url, this.options); - } -}); - - - -function $(element) { - if (arguments.length > 1) { - for (var i = 0, elements = [], length = arguments.length; i < length; i++) - elements.push($(arguments[i])); - return elements; - } - if (Object.isString(element)) - element = document.getElementById(element); - return Element.extend(element); -} - -if (Prototype.BrowserFeatures.XPath) { - document._getElementsByXPath = function(expression, parentElement) { - var results = []; - var query = document.evaluate(expression, $(parentElement) || document, - null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); - for (var i = 0, length = query.snapshotLength; i < length; i++) - results.push(Element.extend(query.snapshotItem(i))); - return results; - }; -} - -/*--------------------------------------------------------------------------*/ - -if (!window.Node) var Node = { }; - -if (!Node.ELEMENT_NODE) { - Object.extend(Node, { - ELEMENT_NODE: 1, - ATTRIBUTE_NODE: 2, - TEXT_NODE: 3, - CDATA_SECTION_NODE: 4, - ENTITY_REFERENCE_NODE: 5, - ENTITY_NODE: 6, - PROCESSING_INSTRUCTION_NODE: 7, - COMMENT_NODE: 8, - DOCUMENT_NODE: 9, - DOCUMENT_TYPE_NODE: 10, - DOCUMENT_FRAGMENT_NODE: 11, - NOTATION_NODE: 12 - }); -} - - -(function(global) { - - var SETATTRIBUTE_IGNORES_NAME = (function(){ - var elForm = document.createElement("form"); - var elInput = document.createElement("input"); - var root = document.documentElement; - elInput.setAttribute("name", "test"); - elForm.appendChild(elInput); - root.appendChild(elForm); - var isBuggy = elForm.elements - ? (typeof elForm.elements.test == "undefined") - : null; - root.removeChild(elForm); - elForm = elInput = null; - return isBuggy; - })(); - - var element = global.Element; - global.Element = function(tagName, attributes) { - attributes = attributes || { }; - tagName = tagName.toLowerCase(); - var cache = Element.cache; - if (SETATTRIBUTE_IGNORES_NAME && attributes.name) { - tagName = '<' + tagName + ' name="' + attributes.name + '">'; - delete attributes.name; - return Element.writeAttribute(document.createElement(tagName), attributes); - } - if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); - return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); - }; - Object.extend(global.Element, element || { }); - if (element) global.Element.prototype = element.prototype; -})(this); - -Element.cache = { }; -Element.idCounter = 1; - -Element.Methods = { - visible: function(element) { - return $(element).style.display != 'none'; - }, - - toggle: function(element) { - element = $(element); - Element[Element.visible(element) ? 'hide' : 'show'](element); - return element; - }, - - - hide: function(element) { - element = $(element); - element.style.display = 'none'; - return element; - }, - - show: function(element) { - element = $(element); - element.style.display = ''; - return element; - }, - - remove: function(element) { - element = $(element); - element.parentNode.removeChild(element); - return element; - }, - - update: (function(){ - - var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ - var el = document.createElement("select"), - isBuggy = true; - el.innerHTML = ""; - if (el.options && el.options[0]) { - isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; - } - el = null; - return isBuggy; - })(); - - var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ - try { - var el = document.createElement("table"); - if (el && el.tBodies) { - el.innerHTML = "test"; - var isBuggy = typeof el.tBodies[0] == "undefined"; - el = null; - return isBuggy; - } - } catch (e) { - return true; - } - })(); - - var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { - var s = document.createElement("script"), - isBuggy = false; - try { - s.appendChild(document.createTextNode("")); - isBuggy = !s.firstChild || - s.firstChild && s.firstChild.nodeType !== 3; - } catch (e) { - isBuggy = true; - } - s = null; - return isBuggy; - })(); - - function update(element, content) { - element = $(element); - - if (content && content.toElement) - content = content.toElement(); - - if (Object.isElement(content)) - return element.update().insert(content); - - content = Object.toHTML(content); - - var tagName = element.tagName.toUpperCase(); - - if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { - element.text = content; - return element; - } - - if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) { - if (tagName in Element._insertionTranslations.tags) { - while (element.firstChild) { - element.removeChild(element.firstChild); - } - Element._getContentFromAnonymousElement(tagName, content.stripScripts()) - .each(function(node) { - element.appendChild(node) - }); - } - else { - element.innerHTML = content.stripScripts(); - } - } - else { - element.innerHTML = content.stripScripts(); - } - - content.evalScripts.bind(content).defer(); - return element; - } - - return update; - })(), - - replace: function(element, content) { - element = $(element); - if (content && content.toElement) content = content.toElement(); - else if (!Object.isElement(content)) { - content = Object.toHTML(content); - var range = element.ownerDocument.createRange(); - range.selectNode(element); - content.evalScripts.bind(content).defer(); - content = range.createContextualFragment(content.stripScripts()); - } - element.parentNode.replaceChild(content, element); - return element; - }, - - insert: function(element, insertions) { - element = $(element); - - if (Object.isString(insertions) || Object.isNumber(insertions) || - Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) - insertions = {bottom:insertions}; - - var content, insert, tagName, childNodes; - - for (var position in insertions) { - content = insertions[position]; - position = position.toLowerCase(); - insert = Element._insertionTranslations[position]; - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) { - insert(element, content); - continue; - } - - content = Object.toHTML(content); - - tagName = ((position == 'before' || position == 'after') - ? element.parentNode : element).tagName.toUpperCase(); - - childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); - - if (position == 'top' || position == 'after') childNodes.reverse(); - childNodes.each(insert.curry(element)); - - content.evalScripts.bind(content).defer(); - } - - return element; - }, - - wrap: function(element, wrapper, attributes) { - element = $(element); - if (Object.isElement(wrapper)) - $(wrapper).writeAttribute(attributes || { }); - else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); - else wrapper = new Element('div', wrapper); - if (element.parentNode) - element.parentNode.replaceChild(wrapper, element); - wrapper.appendChild(element); - return wrapper; - }, - - inspect: function(element) { - element = $(element); - var result = '<' + element.tagName.toLowerCase(); - $H({'id': 'id', 'className': 'class'}).each(function(pair) { - var property = pair.first(), attribute = pair.last(); - var value = (element[property] || '').toString(); - if (value) result += ' ' + attribute + '=' + value.inspect(true); - }); - return result + '>'; - }, - - recursivelyCollect: function(element, property) { - element = $(element); - var elements = []; - while (element = element[property]) - if (element.nodeType == 1) - elements.push(Element.extend(element)); - return elements; - }, - - ancestors: function(element) { - return Element.recursivelyCollect(element, 'parentNode'); - }, - - descendants: function(element) { - return Element.select(element, "*"); - }, - - firstDescendant: function(element) { - element = $(element).firstChild; - while (element && element.nodeType != 1) element = element.nextSibling; - return $(element); - }, - - immediateDescendants: function(element) { - if (!(element = $(element).firstChild)) return []; - while (element && element.nodeType != 1) element = element.nextSibling; - if (element) return [element].concat($(element).nextSiblings()); - return []; - }, - - previousSiblings: function(element) { - return Element.recursivelyCollect(element, 'previousSibling'); - }, - - nextSiblings: function(element) { - return Element.recursivelyCollect(element, 'nextSibling'); - }, - - siblings: function(element) { - element = $(element); - return Element.previousSiblings(element).reverse() - .concat(Element.nextSiblings(element)); - }, - - match: function(element, selector) { - if (Object.isString(selector)) - selector = new Selector(selector); - return selector.match($(element)); - }, - - up: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return $(element.parentNode); - var ancestors = Element.ancestors(element); - return Object.isNumber(expression) ? ancestors[expression] : - Selector.findElement(ancestors, expression, index); - }, - - down: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return Element.firstDescendant(element); - return Object.isNumber(expression) ? Element.descendants(element)[expression] : - Element.select(element, expression)[index || 0]; - }, - - previous: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); - var previousSiblings = Element.previousSiblings(element); - return Object.isNumber(expression) ? previousSiblings[expression] : - Selector.findElement(previousSiblings, expression, index); - }, - - next: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); - var nextSiblings = Element.nextSiblings(element); - return Object.isNumber(expression) ? nextSiblings[expression] : - Selector.findElement(nextSiblings, expression, index); - }, - - - select: function(element) { - var args = Array.prototype.slice.call(arguments, 1); - return Selector.findChildElements(element, args); - }, - - adjacent: function(element) { - var args = Array.prototype.slice.call(arguments, 1); - return Selector.findChildElements(element.parentNode, args).without(element); - }, - - identify: function(element) { - element = $(element); - var id = Element.readAttribute(element, 'id'); - if (id) return id; - do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); - Element.writeAttribute(element, 'id', id); - return id; - }, - - readAttribute: function(element, name) { - element = $(element); - if (Prototype.Browser.IE) { - var t = Element._attributeTranslations.read; - if (t.values[name]) return t.values[name](element, name); - if (t.names[name]) name = t.names[name]; - if (name.include(':')) { - return (!element.attributes || !element.attributes[name]) ? null : - element.attributes[name].value; - } - } - return element.getAttribute(name); - }, - - writeAttribute: function(element, name, value) { - element = $(element); - var attributes = { }, t = Element._attributeTranslations.write; - - if (typeof name == 'object') attributes = name; - else attributes[name] = Object.isUndefined(value) ? true : value; - - for (var attr in attributes) { - name = t.names[attr] || attr; - value = attributes[attr]; - if (t.values[attr]) name = t.values[attr](element, value); - if (value === false || value === null) - element.removeAttribute(name); - else if (value === true) - element.setAttribute(name, name); - else element.setAttribute(name, value); - } - return element; - }, - - getHeight: function(element) { - return Element.getDimensions(element).height; - }, - - getWidth: function(element) { - return Element.getDimensions(element).width; - }, - - classNames: function(element) { - return new Element.ClassNames(element); - }, - - hasClassName: function(element, className) { - if (!(element = $(element))) return; - var elementClassName = element.className; - return (elementClassName.length > 0 && (elementClassName == className || - new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); - }, - - addClassName: function(element, className) { - if (!(element = $(element))) return; - if (!Element.hasClassName(element, className)) - element.className += (element.className ? ' ' : '') + className; - return element; - }, - - removeClassName: function(element, className) { - if (!(element = $(element))) return; - element.className = element.className.replace( - new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); - return element; - }, - - toggleClassName: function(element, className) { - if (!(element = $(element))) return; - return Element[Element.hasClassName(element, className) ? - 'removeClassName' : 'addClassName'](element, className); - }, - - cleanWhitespace: function(element) { - element = $(element); - var node = element.firstChild; - while (node) { - var nextNode = node.nextSibling; - if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) - element.removeChild(node); - node = nextNode; - } - return element; - }, - - empty: function(element) { - return $(element).innerHTML.blank(); - }, - - descendantOf: function(element, ancestor) { - element = $(element), ancestor = $(ancestor); - - if (element.compareDocumentPosition) - return (element.compareDocumentPosition(ancestor) & 8) === 8; - - if (ancestor.contains) - return ancestor.contains(element) && ancestor !== element; - - while (element = element.parentNode) - if (element == ancestor) return true; - - return false; - }, - - scrollTo: function(element) { - element = $(element); - var pos = Element.cumulativeOffset(element); - window.scrollTo(pos[0], pos[1]); - return element; - }, - - getStyle: function(element, style) { - element = $(element); - style = style == 'float' ? 'cssFloat' : style.camelize(); - var value = element.style[style]; - if (!value || value == 'auto') { - var css = document.defaultView.getComputedStyle(element, null); - value = css ? css[style] : null; - } - if (style == 'opacity') return value ? parseFloat(value) : 1.0; - return value == 'auto' ? null : value; - }, - - getOpacity: function(element) { - return $(element).getStyle('opacity'); - }, - - setStyle: function(element, styles) { - element = $(element); - var elementStyle = element.style, match; - if (Object.isString(styles)) { - element.style.cssText += ';' + styles; - return styles.include('opacity') ? - element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; - } - for (var property in styles) - if (property == 'opacity') element.setOpacity(styles[property]); - else - elementStyle[(property == 'float' || property == 'cssFloat') ? - (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : - property] = styles[property]; - - return element; - }, - - setOpacity: function(element, value) { - element = $(element); - element.style.opacity = (value == 1 || value === '') ? '' : - (value < 0.00001) ? 0 : value; - return element; - }, - - getDimensions: function(element) { - element = $(element); - var display = Element.getStyle(element, 'display'); - if (display != 'none' && display != null) // Safari bug - return {width: element.offsetWidth, height: element.offsetHeight}; - - var els = element.style; - var originalVisibility = els.visibility; - var originalPosition = els.position; - var originalDisplay = els.display; - els.visibility = 'hidden'; - if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari - els.position = 'absolute'; - els.display = 'block'; - var originalWidth = element.clientWidth; - var originalHeight = element.clientHeight; - els.display = originalDisplay; - els.position = originalPosition; - els.visibility = originalVisibility; - return {width: originalWidth, height: originalHeight}; - }, - - makePositioned: function(element) { - element = $(element); - var pos = Element.getStyle(element, 'position'); - if (pos == 'static' || !pos) { - element._madePositioned = true; - element.style.position = 'relative'; - if (Prototype.Browser.Opera) { - element.style.top = 0; - element.style.left = 0; - } - } - return element; - }, - - undoPositioned: function(element) { - element = $(element); - if (element._madePositioned) { - element._madePositioned = undefined; - element.style.position = - element.style.top = - element.style.left = - element.style.bottom = - element.style.right = ''; - } - return element; - }, - - makeClipping: function(element) { - element = $(element); - if (element._overflow) return element; - element._overflow = Element.getStyle(element, 'overflow') || 'auto'; - if (element._overflow !== 'hidden') - element.style.overflow = 'hidden'; - return element; - }, - - undoClipping: function(element) { - element = $(element); - if (!element._overflow) return element; - element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; - element._overflow = null; - return element; - }, - - cumulativeOffset: function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - } while (element); - return Element._returnOffset(valueL, valueT); - }, - - positionedOffset: function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - if (element) { - if (element.tagName.toUpperCase() == 'BODY') break; - var p = Element.getStyle(element, 'position'); - if (p !== 'static') break; - } - } while (element); - return Element._returnOffset(valueL, valueT); - }, - - absolutize: function(element) { - element = $(element); - if (Element.getStyle(element, 'position') == 'absolute') return element; - - var offsets = Element.positionedOffset(element); - var top = offsets[1]; - var left = offsets[0]; - var width = element.clientWidth; - var height = element.clientHeight; - - element._originalLeft = left - parseFloat(element.style.left || 0); - element._originalTop = top - parseFloat(element.style.top || 0); - element._originalWidth = element.style.width; - element._originalHeight = element.style.height; - - element.style.position = 'absolute'; - element.style.top = top + 'px'; - element.style.left = left + 'px'; - element.style.width = width + 'px'; - element.style.height = height + 'px'; - return element; - }, - - relativize: function(element) { - element = $(element); - if (Element.getStyle(element, 'position') == 'relative') return element; - - element.style.position = 'relative'; - var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); - var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); - - element.style.top = top + 'px'; - element.style.left = left + 'px'; - element.style.height = element._originalHeight; - element.style.width = element._originalWidth; - return element; - }, - - cumulativeScrollOffset: function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.scrollTop || 0; - valueL += element.scrollLeft || 0; - element = element.parentNode; - } while (element); - return Element._returnOffset(valueL, valueT); - }, - - getOffsetParent: function(element) { - if (element.offsetParent) return $(element.offsetParent); - if (element == document.body) return $(element); - - while ((element = element.parentNode) && element != document.body) - if (Element.getStyle(element, 'position') != 'static') - return $(element); - - return $(document.body); - }, - - viewportOffset: function(forElement) { - var valueT = 0, valueL = 0; - - var element = forElement; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - - if (element.offsetParent == document.body && - Element.getStyle(element, 'position') == 'absolute') break; - - } while (element = element.offsetParent); - - element = forElement; - do { - if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { - valueT -= element.scrollTop || 0; - valueL -= element.scrollLeft || 0; - } - } while (element = element.parentNode); - - return Element._returnOffset(valueL, valueT); - }, - - clonePosition: function(element, source) { - var options = Object.extend({ - setLeft: true, - setTop: true, - setWidth: true, - setHeight: true, - offsetTop: 0, - offsetLeft: 0 - }, arguments[2] || { }); - - source = $(source); - var p = Element.viewportOffset(source); - - element = $(element); - var delta = [0, 0]; - var parent = null; - if (Element.getStyle(element, 'position') == 'absolute') { - parent = Element.getOffsetParent(element); - delta = Element.viewportOffset(parent); - } - - if (parent == document.body) { - delta[0] -= document.body.offsetLeft; - delta[1] -= document.body.offsetTop; - } - - if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; - if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; - if (options.setWidth) element.style.width = source.offsetWidth + 'px'; - if (options.setHeight) element.style.height = source.offsetHeight + 'px'; - return element; - } -}; - -Object.extend(Element.Methods, { - getElementsBySelector: Element.Methods.select, - - childElements: Element.Methods.immediateDescendants -}); - -Element._attributeTranslations = { - write: { - names: { - className: 'class', - htmlFor: 'for' - }, - values: { } - } -}; - -if (Prototype.Browser.Opera) { - Element.Methods.getStyle = Element.Methods.getStyle.wrap( - function(proceed, element, style) { - switch (style) { - case 'left': case 'top': case 'right': case 'bottom': - if (proceed(element, 'position') === 'static') return null; - case 'height': case 'width': - if (!Element.visible(element)) return null; - - var dim = parseInt(proceed(element, style), 10); - - if (dim !== element['offset' + style.capitalize()]) - return dim + 'px'; - - var properties; - if (style === 'height') { - properties = ['border-top-width', 'padding-top', - 'padding-bottom', 'border-bottom-width']; - } - else { - properties = ['border-left-width', 'padding-left', - 'padding-right', 'border-right-width']; - } - return properties.inject(dim, function(memo, property) { - var val = proceed(element, property); - return val === null ? memo : memo - parseInt(val, 10); - }) + 'px'; - default: return proceed(element, style); - } - } - ); - - Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( - function(proceed, element, attribute) { - if (attribute === 'title') return element.title; - return proceed(element, attribute); - } - ); -} - -else if (Prototype.Browser.IE) { - Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( - function(proceed, element) { - element = $(element); - try { element.offsetParent } - catch(e) { return $(document.body) } - var position = element.getStyle('position'); - if (position !== 'static') return proceed(element); - element.setStyle({ position: 'relative' }); - var value = proceed(element); - element.setStyle({ position: position }); - return value; - } - ); - - $w('positionedOffset viewportOffset').each(function(method) { - Element.Methods[method] = Element.Methods[method].wrap( - function(proceed, element) { - element = $(element); - try { element.offsetParent } - catch(e) { return Element._returnOffset(0,0) } - var position = element.getStyle('position'); - if (position !== 'static') return proceed(element); - var offsetParent = element.getOffsetParent(); - if (offsetParent && offsetParent.getStyle('position') === 'fixed') - offsetParent.setStyle({ zoom: 1 }); - element.setStyle({ position: 'relative' }); - var value = proceed(element); - element.setStyle({ position: position }); - return value; - } - ); - }); - - Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( - function(proceed, element) { - try { element.offsetParent } - catch(e) { return Element._returnOffset(0,0) } - return proceed(element); - } - ); - - Element.Methods.getStyle = function(element, style) { - element = $(element); - style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); - var value = element.style[style]; - if (!value && element.currentStyle) value = element.currentStyle[style]; - - if (style == 'opacity') { - if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) - if (value[1]) return parseFloat(value[1]) / 100; - return 1.0; - } - - if (value == 'auto') { - if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) - return element['offset' + style.capitalize()] + 'px'; - return null; - } - return value; - }; - - Element.Methods.setOpacity = function(element, value) { - function stripAlpha(filter){ - return filter.replace(/alpha\([^\)]*\)/gi,''); - } - element = $(element); - var currentStyle = element.currentStyle; - if ((currentStyle && !currentStyle.hasLayout) || - (!currentStyle && element.style.zoom == 'normal')) - element.style.zoom = 1; - - var filter = element.getStyle('filter'), style = element.style; - if (value == 1 || value === '') { - (filter = stripAlpha(filter)) ? - style.filter = filter : style.removeAttribute('filter'); - return element; - } else if (value < 0.00001) value = 0; - style.filter = stripAlpha(filter) + - 'alpha(opacity=' + (value * 100) + ')'; - return element; - }; - - Element._attributeTranslations = (function(){ - - var classProp = 'className'; - var forProp = 'for'; - - var el = document.createElement('div'); - - el.setAttribute(classProp, 'x'); - - if (el.className !== 'x') { - el.setAttribute('class', 'x'); - if (el.className === 'x') { - classProp = 'class'; - } - } - el = null; - - el = document.createElement('label'); - el.setAttribute(forProp, 'x'); - if (el.htmlFor !== 'x') { - el.setAttribute('htmlFor', 'x'); - if (el.htmlFor === 'x') { - forProp = 'htmlFor'; - } - } - el = null; - - return { - read: { - names: { - 'class': classProp, - 'className': classProp, - 'for': forProp, - 'htmlFor': forProp - }, - values: { - _getAttr: function(element, attribute) { - return element.getAttribute(attribute); - }, - _getAttr2: function(element, attribute) { - return element.getAttribute(attribute, 2); - }, - _getAttrNode: function(element, attribute) { - var node = element.getAttributeNode(attribute); - return node ? node.value : ""; - }, - _getEv: (function(){ - - var el = document.createElement('div'); - el.onclick = Prototype.emptyFunction; - var value = el.getAttribute('onclick'); - var f; - - if (String(value).indexOf('{') > -1) { - f = function(element, attribute) { - attribute = element.getAttribute(attribute); - if (!attribute) return null; - attribute = attribute.toString(); - attribute = attribute.split('{')[1]; - attribute = attribute.split('}')[0]; - return attribute.strip(); - }; - } - else if (value === '') { - f = function(element, attribute) { - attribute = element.getAttribute(attribute); - if (!attribute) return null; - return attribute.strip(); - }; - } - el = null; - return f; - })(), - _flag: function(element, attribute) { - return $(element).hasAttribute(attribute) ? attribute : null; - }, - style: function(element) { - return element.style.cssText.toLowerCase(); - }, - title: function(element) { - return element.title; - } - } - } - } - })(); - - Element._attributeTranslations.write = { - names: Object.extend({ - cellpadding: 'cellPadding', - cellspacing: 'cellSpacing' - }, Element._attributeTranslations.read.names), - values: { - checked: function(element, value) { - element.checked = !!value; - }, - - style: function(element, value) { - element.style.cssText = value ? value : ''; - } - } - }; - - Element._attributeTranslations.has = {}; - - $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + - 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { - Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; - Element._attributeTranslations.has[attr.toLowerCase()] = attr; - }); - - (function(v) { - Object.extend(v, { - href: v._getAttr2, - src: v._getAttr2, - type: v._getAttr, - action: v._getAttrNode, - disabled: v._flag, - checked: v._flag, - readonly: v._flag, - multiple: v._flag, - onload: v._getEv, - onunload: v._getEv, - onclick: v._getEv, - ondblclick: v._getEv, - onmousedown: v._getEv, - onmouseup: v._getEv, - onmouseover: v._getEv, - onmousemove: v._getEv, - onmouseout: v._getEv, - onfocus: v._getEv, - onblur: v._getEv, - onkeypress: v._getEv, - onkeydown: v._getEv, - onkeyup: v._getEv, - onsubmit: v._getEv, - onreset: v._getEv, - onselect: v._getEv, - onchange: v._getEv - }); - })(Element._attributeTranslations.read.values); - - if (Prototype.BrowserFeatures.ElementExtensions) { - (function() { - function _descendants(element) { - var nodes = element.getElementsByTagName('*'), results = []; - for (var i = 0, node; node = nodes[i]; i++) - if (node.tagName !== "!") // Filter out comment nodes. - results.push(node); - return results; - } - - Element.Methods.down = function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return element.firstDescendant(); - return Object.isNumber(expression) ? _descendants(element)[expression] : - Element.select(element, expression)[index || 0]; - } - })(); - } - -} - -else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { - Element.Methods.setOpacity = function(element, value) { - element = $(element); - element.style.opacity = (value == 1) ? 0.999999 : - (value === '') ? '' : (value < 0.00001) ? 0 : value; - return element; - }; -} - -else if (Prototype.Browser.WebKit) { - Element.Methods.setOpacity = function(element, value) { - element = $(element); - element.style.opacity = (value == 1 || value === '') ? '' : - (value < 0.00001) ? 0 : value; - - if (value == 1) - if(element.tagName.toUpperCase() == 'IMG' && element.width) { - element.width++; element.width--; - } else try { - var n = document.createTextNode(' '); - element.appendChild(n); - element.removeChild(n); - } catch (e) { } - - return element; - }; - - Element.Methods.cumulativeOffset = function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - if (element.offsetParent == document.body) - if (Element.getStyle(element, 'position') == 'absolute') break; - - element = element.offsetParent; - } while (element); - - return Element._returnOffset(valueL, valueT); - }; -} - -if ('outerHTML' in document.documentElement) { - Element.Methods.replace = function(element, content) { - element = $(element); - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) { - element.parentNode.replaceChild(content, element); - return element; - } - - content = Object.toHTML(content); - var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); - - if (Element._insertionTranslations.tags[tagName]) { - var nextSibling = element.next(); - var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); - parent.removeChild(element); - if (nextSibling) - fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); - else - fragments.each(function(node) { parent.appendChild(node) }); - } - else element.outerHTML = content.stripScripts(); - - content.evalScripts.bind(content).defer(); - return element; - }; -} - -Element._returnOffset = function(l, t) { - var result = [l, t]; - result.left = l; - result.top = t; - return result; -}; - -Element._getContentFromAnonymousElement = function(tagName, html) { - var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; - if (t) { - div.innerHTML = t[0] + html + t[1]; - t[2].times(function() { div = div.firstChild }); - } else div.innerHTML = html; - return $A(div.childNodes); -}; - -Element._insertionTranslations = { - before: function(element, node) { - element.parentNode.insertBefore(node, element); - }, - top: function(element, node) { - element.insertBefore(node, element.firstChild); - }, - bottom: function(element, node) { - element.appendChild(node); - }, - after: function(element, node) { - element.parentNode.insertBefore(node, element.nextSibling); - }, - tags: { - TABLE: ['', '
', 1], - TBODY: ['', '
', 2], - TR: ['', '
', 3], - TD: ['
', '
', 4], - SELECT: ['', 1] - } -}; - -(function() { - var tags = Element._insertionTranslations.tags; - Object.extend(tags, { - THEAD: tags.TBODY, - TFOOT: tags.TBODY, - TH: tags.TD - }); -})(); - -Element.Methods.Simulated = { - hasAttribute: function(element, attribute) { - attribute = Element._attributeTranslations.has[attribute] || attribute; - var node = $(element).getAttributeNode(attribute); - return !!(node && node.specified); - } -}; - -Element.Methods.ByTag = { }; - -Object.extend(Element, Element.Methods); - -(function(div) { - - if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) { - window.HTMLElement = { }; - window.HTMLElement.prototype = div['__proto__']; - Prototype.BrowserFeatures.ElementExtensions = true; - } - - div = null; - -})(document.createElement('div')) - -Element.extend = (function() { - - function checkDeficiency(tagName) { - if (typeof window.Element != 'undefined') { - var proto = window.Element.prototype; - if (proto) { - var id = '_' + (Math.random()+'').slice(2); - var el = document.createElement(tagName); - proto[id] = 'x'; - var isBuggy = (el[id] !== 'x'); - delete proto[id]; - el = null; - return isBuggy; - } - } - return false; - } - - function extendElementWith(element, methods) { - for (var property in methods) { - var value = methods[property]; - if (Object.isFunction(value) && !(property in element)) - element[property] = value.methodize(); - } - } - - var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object'); - - if (Prototype.BrowserFeatures.SpecificElementExtensions) { - if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) { - return function(element) { - if (element && typeof element._extendedByPrototype == 'undefined') { - var t = element.tagName; - if (t && (/^(?:object|applet|embed)$/i.test(t))) { - extendElementWith(element, Element.Methods); - extendElementWith(element, Element.Methods.Simulated); - extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); - } - } - return element; - } - } - return Prototype.K; - } - - var Methods = { }, ByTag = Element.Methods.ByTag; - - var extend = Object.extend(function(element) { - if (!element || typeof element._extendedByPrototype != 'undefined' || - element.nodeType != 1 || element == window) return element; - - var methods = Object.clone(Methods), - tagName = element.tagName.toUpperCase(); - - if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); - - extendElementWith(element, methods); - - element._extendedByPrototype = Prototype.emptyFunction; - return element; - - }, { - refresh: function() { - if (!Prototype.BrowserFeatures.ElementExtensions) { - Object.extend(Methods, Element.Methods); - Object.extend(Methods, Element.Methods.Simulated); - } - } - }); - - extend.refresh(); - return extend; -})(); - -Element.hasAttribute = function(element, attribute) { - if (element.hasAttribute) return element.hasAttribute(attribute); - return Element.Methods.Simulated.hasAttribute(element, attribute); -}; - -Element.addMethods = function(methods) { - var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; - - if (!methods) { - Object.extend(Form, Form.Methods); - Object.extend(Form.Element, Form.Element.Methods); - Object.extend(Element.Methods.ByTag, { - "FORM": Object.clone(Form.Methods), - "INPUT": Object.clone(Form.Element.Methods), - "SELECT": Object.clone(Form.Element.Methods), - "TEXTAREA": Object.clone(Form.Element.Methods) - }); - } - - if (arguments.length == 2) { - var tagName = methods; - methods = arguments[1]; - } - - if (!tagName) Object.extend(Element.Methods, methods || { }); - else { - if (Object.isArray(tagName)) tagName.each(extend); - else extend(tagName); - } - - function extend(tagName) { - tagName = tagName.toUpperCase(); - if (!Element.Methods.ByTag[tagName]) - Element.Methods.ByTag[tagName] = { }; - Object.extend(Element.Methods.ByTag[tagName], methods); - } - - function copy(methods, destination, onlyIfAbsent) { - onlyIfAbsent = onlyIfAbsent || false; - for (var property in methods) { - var value = methods[property]; - if (!Object.isFunction(value)) continue; - if (!onlyIfAbsent || !(property in destination)) - destination[property] = value.methodize(); - } - } - - function findDOMClass(tagName) { - var klass; - var trans = { - "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", - "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", - "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", - "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", - "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": - "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": - "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": - "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": - "FrameSet", "IFRAME": "IFrame" - }; - if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; - if (window[klass]) return window[klass]; - klass = 'HTML' + tagName + 'Element'; - if (window[klass]) return window[klass]; - klass = 'HTML' + tagName.capitalize() + 'Element'; - if (window[klass]) return window[klass]; - - var element = document.createElement(tagName); - var proto = element['__proto__'] || element.constructor.prototype; - element = null; - return proto; - } - - var elementPrototype = window.HTMLElement ? HTMLElement.prototype : - Element.prototype; - - if (F.ElementExtensions) { - copy(Element.Methods, elementPrototype); - copy(Element.Methods.Simulated, elementPrototype, true); - } - - if (F.SpecificElementExtensions) { - for (var tag in Element.Methods.ByTag) { - var klass = findDOMClass(tag); - if (Object.isUndefined(klass)) continue; - copy(T[tag], klass.prototype); - } - } - - Object.extend(Element, Element.Methods); - delete Element.ByTag; - - if (Element.extend.refresh) Element.extend.refresh(); - Element.cache = { }; -}; - - -document.viewport = { - - getDimensions: function() { - return { width: this.getWidth(), height: this.getHeight() }; - }, - - getScrollOffsets: function() { - return Element._returnOffset( - window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, - window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); - } -}; - -(function(viewport) { - var B = Prototype.Browser, doc = document, element, property = {}; - - function getRootElement() { - if (B.WebKit && !doc.evaluate) - return document; - - if (B.Opera && window.parseFloat(window.opera.version()) < 9.5) - return document.body; - - return document.documentElement; - } - - function define(D) { - if (!element) element = getRootElement(); - - property[D] = 'client' + D; - - viewport['get' + D] = function() { return element[property[D]] }; - return viewport['get' + D](); - } - - viewport.getWidth = define.curry('Width'); - - viewport.getHeight = define.curry('Height'); -})(document.viewport); - - -Element.Storage = { - UID: 1 -}; - -Element.addMethods({ - getStorage: function(element) { - if (!(element = $(element))) return; - - var uid; - if (element === window) { - uid = 0; - } else { - if (typeof element._prototypeUID === "undefined") - element._prototypeUID = [Element.Storage.UID++]; - uid = element._prototypeUID[0]; - } - - if (!Element.Storage[uid]) - Element.Storage[uid] = $H(); - - return Element.Storage[uid]; - }, - - store: function(element, key, value) { - if (!(element = $(element))) return; - - if (arguments.length === 2) { - Element.getStorage(element).update(key); - } else { - Element.getStorage(element).set(key, value); - } - - return element; - }, - - retrieve: function(element, key, defaultValue) { - if (!(element = $(element))) return; - var hash = Element.getStorage(element), value = hash.get(key); - - if (Object.isUndefined(value)) { - hash.set(key, defaultValue); - value = defaultValue; - } - - return value; - }, - - clone: function(element, deep) { - if (!(element = $(element))) return; - var clone = element.cloneNode(deep); - clone._prototypeUID = void 0; - if (deep) { - var descendants = Element.select(clone, '*'), - i = descendants.length; - while (i--) { - descendants[i]._prototypeUID = void 0; - } - } - return Element.extend(clone); - } -}); -/* Portions of the Selector class are derived from Jack Slocum's DomQuery, - * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style - * license. Please see http://www.yui-ext.com/ for more information. */ - -var Selector = Class.create({ - initialize: function(expression) { - this.expression = expression.strip(); - - if (this.shouldUseSelectorsAPI()) { - this.mode = 'selectorsAPI'; - } else if (this.shouldUseXPath()) { - this.mode = 'xpath'; - this.compileXPathMatcher(); - } else { - this.mode = "normal"; - this.compileMatcher(); - } - - }, - - shouldUseXPath: (function() { - - var IS_DESCENDANT_SELECTOR_BUGGY = (function(){ - var isBuggy = false; - if (document.evaluate && window.XPathResult) { - var el = document.createElement('div'); - el.innerHTML = '
'; - - var xpath = ".//*[local-name()='ul' or local-name()='UL']" + - "//*[local-name()='li' or local-name()='LI']"; - - var result = document.evaluate(xpath, el, null, - XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); - - isBuggy = (result.snapshotLength !== 2); - el = null; - } - return isBuggy; - })(); - - return function() { - if (!Prototype.BrowserFeatures.XPath) return false; - - var e = this.expression; - - if (Prototype.Browser.WebKit && - (e.include("-of-type") || e.include(":empty"))) - return false; - - if ((/(\[[\w-]*?:|:checked)/).test(e)) - return false; - - if (IS_DESCENDANT_SELECTOR_BUGGY) return false; - - return true; - } - - })(), - - shouldUseSelectorsAPI: function() { - if (!Prototype.BrowserFeatures.SelectorsAPI) return false; - - if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false; - - if (!Selector._div) Selector._div = new Element('div'); - - try { - Selector._div.querySelector(this.expression); - } catch(e) { - return false; - } - - return true; - }, - - compileMatcher: function() { - var e = this.expression, ps = Selector.patterns, h = Selector.handlers, - c = Selector.criteria, le, p, m, len = ps.length, name; - - if (Selector._cache[e]) { - this.matcher = Selector._cache[e]; - return; - } - - this.matcher = ["this.matcher = function(root) {", - "var r = root, h = Selector.handlers, c = false, n;"]; - - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i = 0; i"; - } -}); - -if (Prototype.BrowserFeatures.SelectorsAPI && - document.compatMode === 'BackCompat') { - Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){ - var div = document.createElement('div'), - span = document.createElement('span'); - - div.id = "prototype_test_id"; - span.className = 'Test'; - div.appendChild(span); - var isIgnored = (div.querySelector('#prototype_test_id .test') !== null); - div = span = null; - return isIgnored; - })(); -} - -Object.extend(Selector, { - _cache: { }, - - xpath: { - descendant: "//*", - child: "/*", - adjacent: "/following-sibling::*[1]", - laterSibling: '/following-sibling::*', - tagName: function(m) { - if (m[1] == '*') return ''; - return "[local-name()='" + m[1].toLowerCase() + - "' or local-name()='" + m[1].toUpperCase() + "']"; - }, - className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", - id: "[@id='#{1}']", - attrPresence: function(m) { - m[1] = m[1].toLowerCase(); - return new Template("[@#{1}]").evaluate(m); - }, - attr: function(m) { - m[1] = m[1].toLowerCase(); - m[3] = m[5] || m[6]; - return new Template(Selector.xpath.operators[m[2]]).evaluate(m); - }, - pseudo: function(m) { - var h = Selector.xpath.pseudos[m[1]]; - if (!h) return ''; - if (Object.isFunction(h)) return h(m); - return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); - }, - operators: { - '=': "[@#{1}='#{3}']", - '!=': "[@#{1}!='#{3}']", - '^=': "[starts-with(@#{1}, '#{3}')]", - '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", - '*=': "[contains(@#{1}, '#{3}')]", - '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", - '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" - }, - pseudos: { - 'first-child': '[not(preceding-sibling::*)]', - 'last-child': '[not(following-sibling::*)]', - 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', - 'empty': "[count(*) = 0 and (count(text()) = 0)]", - 'checked': "[@checked]", - 'disabled': "[(@disabled) and (@type!='hidden')]", - 'enabled': "[not(@disabled) and (@type!='hidden')]", - 'not': function(m) { - var e = m[6], p = Selector.patterns, - x = Selector.xpath, le, v, len = p.length, name; - - var exclusion = []; - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i = 0; i= 0)]"; - return new Template(predicate).evaluate({ - fragment: fragment, a: a, b: b }); - } - } - } - }, - - criteria: { - tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', - className: 'n = h.className(n, r, "#{1}", c); c = false;', - id: 'n = h.id(n, r, "#{1}", c); c = false;', - attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', - attr: function(m) { - m[3] = (m[5] || m[6]); - return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); - }, - pseudo: function(m) { - if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); - return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); - }, - descendant: 'c = "descendant";', - child: 'c = "child";', - adjacent: 'c = "adjacent";', - laterSibling: 'c = "laterSibling";' - }, - - patterns: [ - { name: 'laterSibling', re: /^\s*~\s*/ }, - { name: 'child', re: /^\s*>\s*/ }, - { name: 'adjacent', re: /^\s*\+\s*/ }, - { name: 'descendant', re: /^\s/ }, - - { name: 'tagName', re: /^\s*(\*|[\w\-]+)(\b|$)?/ }, - { name: 'id', re: /^#([\w\-\*]+)(\b|$)/ }, - { name: 'className', re: /^\.([\w\-\*]+)(\b|$)/ }, - { name: 'pseudo', re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ }, - { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ }, - { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ } - ], - - assertions: { - tagName: function(element, matches) { - return matches[1].toUpperCase() == element.tagName.toUpperCase(); - }, - - className: function(element, matches) { - return Element.hasClassName(element, matches[1]); - }, - - id: function(element, matches) { - return element.id === matches[1]; - }, - - attrPresence: function(element, matches) { - return Element.hasAttribute(element, matches[1]); - }, - - attr: function(element, matches) { - var nodeValue = Element.readAttribute(element, matches[1]); - return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); - } - }, - - handlers: { - concat: function(a, b) { - for (var i = 0, node; node = b[i]; i++) - a.push(node); - return a; - }, - - mark: function(nodes) { - var _true = Prototype.emptyFunction; - for (var i = 0, node; node = nodes[i]; i++) - node._countedByPrototype = _true; - return nodes; - }, - - unmark: (function(){ - - var PROPERTIES_ATTRIBUTES_MAP = (function(){ - var el = document.createElement('div'), - isBuggy = false, - propName = '_countedByPrototype', - value = 'x' - el[propName] = value; - isBuggy = (el.getAttribute(propName) === value); - el = null; - return isBuggy; - })(); - - return PROPERTIES_ATTRIBUTES_MAP ? - function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node.removeAttribute('_countedByPrototype'); - return nodes; - } : - function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node._countedByPrototype = void 0; - return nodes; - } - })(), - - index: function(parentNode, reverse, ofType) { - parentNode._countedByPrototype = Prototype.emptyFunction; - if (reverse) { - for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { - var node = nodes[i]; - if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; - } - } else { - for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) - if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; - } - }, - - unique: function(nodes) { - if (nodes.length == 0) return nodes; - var results = [], n; - for (var i = 0, l = nodes.length; i < l; i++) - if (typeof (n = nodes[i])._countedByPrototype == 'undefined') { - n._countedByPrototype = Prototype.emptyFunction; - results.push(Element.extend(n)); - } - return Selector.handlers.unmark(results); - }, - - descendant: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - h.concat(results, node.getElementsByTagName('*')); - return results; - }, - - child: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) { - for (var j = 0, child; child = node.childNodes[j]; j++) - if (child.nodeType == 1 && child.tagName != '!') results.push(child); - } - return results; - }, - - adjacent: function(nodes) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - var next = this.nextElementSibling(node); - if (next) results.push(next); - } - return results; - }, - - laterSibling: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - h.concat(results, Element.nextSiblings(node)); - return results; - }, - - nextElementSibling: function(node) { - while (node = node.nextSibling) - if (node.nodeType == 1) return node; - return null; - }, - - previousElementSibling: function(node) { - while (node = node.previousSibling) - if (node.nodeType == 1) return node; - return null; - }, - - tagName: function(nodes, root, tagName, combinator) { - var uTagName = tagName.toUpperCase(); - var results = [], h = Selector.handlers; - if (nodes) { - if (combinator) { - if (combinator == "descendant") { - for (var i = 0, node; node = nodes[i]; i++) - h.concat(results, node.getElementsByTagName(tagName)); - return results; - } else nodes = this[combinator](nodes); - if (tagName == "*") return nodes; - } - for (var i = 0, node; node = nodes[i]; i++) - if (node.tagName.toUpperCase() === uTagName) results.push(node); - return results; - } else return root.getElementsByTagName(tagName); - }, - - id: function(nodes, root, id, combinator) { - var targetNode = $(id), h = Selector.handlers; - - if (root == document) { - if (!targetNode) return []; - if (!nodes) return [targetNode]; - } else { - if (!root.sourceIndex || root.sourceIndex < 1) { - var nodes = root.getElementsByTagName('*'); - for (var j = 0, node; node = nodes[j]; j++) { - if (node.id === id) return [node]; - } - } - } - - if (nodes) { - if (combinator) { - if (combinator == 'child') { - for (var i = 0, node; node = nodes[i]; i++) - if (targetNode.parentNode == node) return [targetNode]; - } else if (combinator == 'descendant') { - for (var i = 0, node; node = nodes[i]; i++) - if (Element.descendantOf(targetNode, node)) return [targetNode]; - } else if (combinator == 'adjacent') { - for (var i = 0, node; node = nodes[i]; i++) - if (Selector.handlers.previousElementSibling(targetNode) == node) - return [targetNode]; - } else nodes = h[combinator](nodes); - } - for (var i = 0, node; node = nodes[i]; i++) - if (node == targetNode) return [targetNode]; - return []; - } - return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; - }, - - className: function(nodes, root, className, combinator) { - if (nodes && combinator) nodes = this[combinator](nodes); - return Selector.handlers.byClassName(nodes, root, className); - }, - - byClassName: function(nodes, root, className) { - if (!nodes) nodes = Selector.handlers.descendant([root]); - var needle = ' ' + className + ' '; - for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { - nodeClassName = node.className; - if (nodeClassName.length == 0) continue; - if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) - results.push(node); - } - return results; - }, - - attrPresence: function(nodes, root, attr, combinator) { - if (!nodes) nodes = root.getElementsByTagName("*"); - if (nodes && combinator) nodes = this[combinator](nodes); - var results = []; - for (var i = 0, node; node = nodes[i]; i++) - if (Element.hasAttribute(node, attr)) results.push(node); - return results; - }, - - attr: function(nodes, root, attr, value, operator, combinator) { - if (!nodes) nodes = root.getElementsByTagName("*"); - if (nodes && combinator) nodes = this[combinator](nodes); - var handler = Selector.operators[operator], results = []; - for (var i = 0, node; node = nodes[i]; i++) { - var nodeValue = Element.readAttribute(node, attr); - if (nodeValue === null) continue; - if (handler(nodeValue, value)) results.push(node); - } - return results; - }, - - pseudo: function(nodes, name, value, root, combinator) { - if (nodes && combinator) nodes = this[combinator](nodes); - if (!nodes) nodes = root.getElementsByTagName("*"); - return Selector.pseudos[name](nodes, value, root); - } - }, - - pseudos: { - 'first-child': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Selector.handlers.previousElementSibling(node)) continue; - results.push(node); - } - return results; - }, - 'last-child': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Selector.handlers.nextElementSibling(node)) continue; - results.push(node); - } - return results; - }, - 'only-child': function(nodes, value, root) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) - results.push(node); - return results; - }, - 'nth-child': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root); - }, - 'nth-last-child': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, true); - }, - 'nth-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, false, true); - }, - 'nth-last-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, true, true); - }, - 'first-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, "1", root, false, true); - }, - 'last-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, "1", root, true, true); - }, - 'only-of-type': function(nodes, formula, root) { - var p = Selector.pseudos; - return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); - }, - - getIndices: function(a, b, total) { - if (a == 0) return b > 0 ? [b] : []; - return $R(1, total).inject([], function(memo, i) { - if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); - return memo; - }); - }, - - nth: function(nodes, formula, root, reverse, ofType) { - if (nodes.length == 0) return []; - if (formula == 'even') formula = '2n+0'; - if (formula == 'odd') formula = '2n+1'; - var h = Selector.handlers, results = [], indexed = [], m; - h.mark(nodes); - for (var i = 0, node; node = nodes[i]; i++) { - if (!node.parentNode._countedByPrototype) { - h.index(node.parentNode, reverse, ofType); - indexed.push(node.parentNode); - } - } - if (formula.match(/^\d+$/)) { // just a number - formula = Number(formula); - for (var i = 0, node; node = nodes[i]; i++) - if (node.nodeIndex == formula) results.push(node); - } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b - if (m[1] == "-") m[1] = -1; - var a = m[1] ? Number(m[1]) : 1; - var b = m[2] ? Number(m[2]) : 0; - var indices = Selector.pseudos.getIndices(a, b, nodes.length); - for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { - for (var j = 0; j < l; j++) - if (node.nodeIndex == indices[j]) results.push(node); - } - } - h.unmark(nodes); - h.unmark(indexed); - return results; - }, - - 'empty': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (node.tagName == '!' || node.firstChild) continue; - results.push(node); - } - return results; - }, - - 'not': function(nodes, selector, root) { - var h = Selector.handlers, selectorType, m; - var exclusions = new Selector(selector).findElements(root); - h.mark(exclusions); - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node._countedByPrototype) results.push(node); - h.unmark(exclusions); - return results; - }, - - 'enabled': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node.disabled && (!node.type || node.type !== 'hidden')) - results.push(node); - return results; - }, - - 'disabled': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (node.disabled) results.push(node); - return results; - }, - - 'checked': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (node.checked) results.push(node); - return results; - } - }, - - operators: { - '=': function(nv, v) { return nv == v; }, - '!=': function(nv, v) { return nv != v; }, - '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, - '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, - '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, - '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, - '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + - '-').include('-' + (v || "").toUpperCase() + '-'); } - }, - - split: function(expression) { - var expressions = []; - expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { - expressions.push(m[1].strip()); - }); - return expressions; - }, - - matchElements: function(elements, expression) { - var matches = $$(expression), h = Selector.handlers; - h.mark(matches); - for (var i = 0, results = [], element; element = elements[i]; i++) - if (element._countedByPrototype) results.push(element); - h.unmark(matches); - return results; - }, - - findElement: function(elements, expression, index) { - if (Object.isNumber(expression)) { - index = expression; expression = false; - } - return Selector.matchElements(elements, expression || '*')[index || 0]; - }, - - findChildElements: function(element, expressions) { - expressions = Selector.split(expressions.join(',')); - var results = [], h = Selector.handlers; - for (var i = 0, l = expressions.length, selector; i < l; i++) { - selector = new Selector(expressions[i].strip()); - h.concat(results, selector.findElements(element)); - } - return (l > 1) ? h.unique(results) : results; - } -}); - -if (Prototype.Browser.IE) { - Object.extend(Selector.handlers, { - concat: function(a, b) { - for (var i = 0, node; node = b[i]; i++) - if (node.tagName !== "!") a.push(node); - return a; - } - }); -} - -function $$() { - return Selector.findChildElements(document, $A(arguments)); -} - -var Form = { - reset: function(form) { - form = $(form); - form.reset(); - return form; - }, - - serializeElements: function(elements, options) { - if (typeof options != 'object') options = { hash: !!options }; - else if (Object.isUndefined(options.hash)) options.hash = true; - var key, value, submitted = false, submit = options.submit; - - var data = elements.inject({ }, function(result, element) { - if (!element.disabled && element.name) { - key = element.name; value = $(element).getValue(); - if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && - submit !== false && (!submit || key == submit) && (submitted = true)))) { - if (key in result) { - if (!Object.isArray(result[key])) result[key] = [result[key]]; - result[key].push(value); - } - else result[key] = value; - } - } - return result; - }); - - return options.hash ? data : Object.toQueryString(data); - } -}; - -Form.Methods = { - serialize: function(form, options) { - return Form.serializeElements(Form.getElements(form), options); - }, - - getElements: function(form) { - var elements = $(form).getElementsByTagName('*'), - element, - arr = [ ], - serializers = Form.Element.Serializers; - for (var i = 0; element = elements[i]; i++) { - arr.push(element); - } - return arr.inject([], function(elements, child) { - if (serializers[child.tagName.toLowerCase()]) - elements.push(Element.extend(child)); - return elements; - }) - }, - - getInputs: function(form, typeName, name) { - form = $(form); - var inputs = form.getElementsByTagName('input'); - - if (!typeName && !name) return $A(inputs).map(Element.extend); - - for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { - var input = inputs[i]; - if ((typeName && input.type != typeName) || (name && input.name != name)) - continue; - matchingInputs.push(Element.extend(input)); - } - - return matchingInputs; - }, - - disable: function(form) { - form = $(form); - Form.getElements(form).invoke('disable'); - return form; - }, - - enable: function(form) { - form = $(form); - Form.getElements(form).invoke('enable'); - return form; - }, - - findFirstElement: function(form) { - var elements = $(form).getElements().findAll(function(element) { - return 'hidden' != element.type && !element.disabled; - }); - var firstByIndex = elements.findAll(function(element) { - return element.hasAttribute('tabIndex') && element.tabIndex >= 0; - }).sortBy(function(element) { return element.tabIndex }).first(); - - return firstByIndex ? firstByIndex : elements.find(function(element) { - return /^(?:input|select|textarea)$/i.test(element.tagName); - }); - }, - - focusFirstElement: function(form) { - form = $(form); - form.findFirstElement().activate(); - return form; - }, - - request: function(form, options) { - form = $(form), options = Object.clone(options || { }); - - var params = options.parameters, action = form.readAttribute('action') || ''; - if (action.blank()) action = window.location.href; - options.parameters = form.serialize(true); - - if (params) { - if (Object.isString(params)) params = params.toQueryParams(); - Object.extend(options.parameters, params); - } - - if (form.hasAttribute('method') && !options.method) - options.method = form.method; - - return new Ajax.Request(action, options); - } -}; - -/*--------------------------------------------------------------------------*/ - - -Form.Element = { - focus: function(element) { - $(element).focus(); - return element; - }, - - select: function(element) { - $(element).select(); - return element; - } -}; - -Form.Element.Methods = { - - serialize: function(element) { - element = $(element); - if (!element.disabled && element.name) { - var value = element.getValue(); - if (value != undefined) { - var pair = { }; - pair[element.name] = value; - return Object.toQueryString(pair); - } - } - return ''; - }, - - getValue: function(element) { - element = $(element); - var method = element.tagName.toLowerCase(); - return Form.Element.Serializers[method](element); - }, - - setValue: function(element, value) { - element = $(element); - var method = element.tagName.toLowerCase(); - Form.Element.Serializers[method](element, value); - return element; - }, - - clear: function(element) { - $(element).value = ''; - return element; - }, - - present: function(element) { - return $(element).value != ''; - }, - - activate: function(element) { - element = $(element); - try { - element.focus(); - if (element.select && (element.tagName.toLowerCase() != 'input' || - !(/^(?:button|reset|submit)$/i.test(element.type)))) - element.select(); - } catch (e) { } - return element; - }, - - disable: function(element) { - element = $(element); - element.disabled = true; - return element; - }, - - enable: function(element) { - element = $(element); - element.disabled = false; - return element; - } -}; - -/*--------------------------------------------------------------------------*/ - -var Field = Form.Element; - -var $F = Form.Element.Methods.getValue; - -/*--------------------------------------------------------------------------*/ - -Form.Element.Serializers = { - input: function(element, value) { - switch (element.type.toLowerCase()) { - case 'checkbox': - case 'radio': - return Form.Element.Serializers.inputSelector(element, value); - default: - return Form.Element.Serializers.textarea(element, value); - } - }, - - inputSelector: function(element, value) { - if (Object.isUndefined(value)) return element.checked ? element.value : null; - else element.checked = !!value; - }, - - textarea: function(element, value) { - if (Object.isUndefined(value)) return element.value; - else element.value = value; - }, - - select: function(element, value) { - if (Object.isUndefined(value)) - return this[element.type == 'select-one' ? - 'selectOne' : 'selectMany'](element); - else { - var opt, currentValue, single = !Object.isArray(value); - for (var i = 0, length = element.length; i < length; i++) { - opt = element.options[i]; - currentValue = this.optionValue(opt); - if (single) { - if (currentValue == value) { - opt.selected = true; - return; - } - } - else opt.selected = value.include(currentValue); - } - } - }, - - selectOne: function(element) { - var index = element.selectedIndex; - return index >= 0 ? this.optionValue(element.options[index]) : null; - }, - - selectMany: function(element) { - var values, length = element.length; - if (!length) return null; - - for (var i = 0, values = []; i < length; i++) { - var opt = element.options[i]; - if (opt.selected) values.push(this.optionValue(opt)); - } - return values; - }, - - optionValue: function(opt) { - return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; - } -}; - -/*--------------------------------------------------------------------------*/ - - -Abstract.TimedObserver = Class.create(PeriodicalExecuter, { - initialize: function($super, element, frequency, callback) { - $super(callback, frequency); - this.element = $(element); - this.lastValue = this.getValue(); - }, - - execute: function() { - var value = this.getValue(); - if (Object.isString(this.lastValue) && Object.isString(value) ? - this.lastValue != value : String(this.lastValue) != String(value)) { - this.callback(this.element, value); - this.lastValue = value; - } - } -}); - -Form.Element.Observer = Class.create(Abstract.TimedObserver, { - getValue: function() { - return Form.Element.getValue(this.element); - } -}); - -Form.Observer = Class.create(Abstract.TimedObserver, { - getValue: function() { - return Form.serialize(this.element); - } -}); - -/*--------------------------------------------------------------------------*/ - -Abstract.EventObserver = Class.create({ - initialize: function(element, callback) { - this.element = $(element); - this.callback = callback; - - this.lastValue = this.getValue(); - if (this.element.tagName.toLowerCase() == 'form') - this.registerFormCallbacks(); - else - this.registerCallback(this.element); - }, - - onElementEvent: function() { - var value = this.getValue(); - if (this.lastValue != value) { - this.callback(this.element, value); - this.lastValue = value; - } - }, - - registerFormCallbacks: function() { - Form.getElements(this.element).each(this.registerCallback, this); - }, - - registerCallback: function(element) { - if (element.type) { - switch (element.type.toLowerCase()) { - case 'checkbox': - case 'radio': - Event.observe(element, 'click', this.onElementEvent.bind(this)); - break; - default: - Event.observe(element, 'change', this.onElementEvent.bind(this)); - break; - } - } - } -}); - -Form.Element.EventObserver = Class.create(Abstract.EventObserver, { - getValue: function() { - return Form.Element.getValue(this.element); - } -}); - -Form.EventObserver = Class.create(Abstract.EventObserver, { - getValue: function() { - return Form.serialize(this.element); - } -}); -(function() { - - var Event = { - KEY_BACKSPACE: 8, - KEY_TAB: 9, - KEY_RETURN: 13, - KEY_ESC: 27, - KEY_LEFT: 37, - KEY_UP: 38, - KEY_RIGHT: 39, - KEY_DOWN: 40, - KEY_DELETE: 46, - KEY_HOME: 36, - KEY_END: 35, - KEY_PAGEUP: 33, - KEY_PAGEDOWN: 34, - KEY_INSERT: 45, - - cache: {} - }; - - var docEl = document.documentElement; - var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl - && 'onmouseleave' in docEl; - - var _isButton; - if (Prototype.Browser.IE) { - var buttonMap = { 0: 1, 1: 4, 2: 2 }; - _isButton = function(event, code) { - return event.button === buttonMap[code]; - }; - } else if (Prototype.Browser.WebKit) { - _isButton = function(event, code) { - switch (code) { - case 0: return event.which == 1 && !event.metaKey; - case 1: return event.which == 1 && event.metaKey; - default: return false; - } - }; - } else { - _isButton = function(event, code) { - return event.which ? (event.which === code + 1) : (event.button === code); - }; - } - - function isLeftClick(event) { return _isButton(event, 0) } - - function isMiddleClick(event) { return _isButton(event, 1) } - - function isRightClick(event) { return _isButton(event, 2) } - - function element(event) { - event = Event.extend(event); - - var node = event.target, type = event.type, - currentTarget = event.currentTarget; - - if (currentTarget && currentTarget.tagName) { - if (type === 'load' || type === 'error' || - (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' - && currentTarget.type === 'radio')) - node = currentTarget; - } - - if (node.nodeType == Node.TEXT_NODE) - node = node.parentNode; - - return Element.extend(node); - } - - function findElement(event, expression) { - var element = Event.element(event); - if (!expression) return element; - var elements = [element].concat(element.ancestors()); - return Selector.findElement(elements, expression, 0); - } - - function pointer(event) { - return { x: pointerX(event), y: pointerY(event) }; - } - - function pointerX(event) { - var docElement = document.documentElement, - body = document.body || { scrollLeft: 0 }; - - return event.pageX || (event.clientX + - (docElement.scrollLeft || body.scrollLeft) - - (docElement.clientLeft || 0)); - } - - function pointerY(event) { - var docElement = document.documentElement, - body = document.body || { scrollTop: 0 }; - - return event.pageY || (event.clientY + - (docElement.scrollTop || body.scrollTop) - - (docElement.clientTop || 0)); - } - - - function stop(event) { - Event.extend(event); - event.preventDefault(); - event.stopPropagation(); - - event.stopped = true; - } - - Event.Methods = { - isLeftClick: isLeftClick, - isMiddleClick: isMiddleClick, - isRightClick: isRightClick, - - element: element, - findElement: findElement, - - pointer: pointer, - pointerX: pointerX, - pointerY: pointerY, - - stop: stop - }; - - - var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { - m[name] = Event.Methods[name].methodize(); - return m; - }); - - if (Prototype.Browser.IE) { - function _relatedTarget(event) { - var element; - switch (event.type) { - case 'mouseover': element = event.fromElement; break; - case 'mouseout': element = event.toElement; break; - default: return null; - } - return Element.extend(element); - } - - Object.extend(methods, { - stopPropagation: function() { this.cancelBubble = true }, - preventDefault: function() { this.returnValue = false }, - inspect: function() { return '[object Event]' } - }); - - Event.extend = function(event, element) { - if (!event) return false; - if (event._extendedByPrototype) return event; - - event._extendedByPrototype = Prototype.emptyFunction; - var pointer = Event.pointer(event); - - Object.extend(event, { - target: event.srcElement || element, - relatedTarget: _relatedTarget(event), - pageX: pointer.x, - pageY: pointer.y - }); - - return Object.extend(event, methods); - }; - } else { - Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; - Object.extend(Event.prototype, methods); - Event.extend = Prototype.K; - } - - function _createResponder(element, eventName, handler) { - var registry = Element.retrieve(element, 'prototype_event_registry'); - - if (Object.isUndefined(registry)) { - CACHE.push(element); - registry = Element.retrieve(element, 'prototype_event_registry', $H()); - } - - var respondersForEvent = registry.get(eventName); - if (Object.isUndefined(respondersForEvent)) { - respondersForEvent = []; - registry.set(eventName, respondersForEvent); - } - - if (respondersForEvent.pluck('handler').include(handler)) return false; - - var responder; - if (eventName.include(":")) { - responder = function(event) { - if (Object.isUndefined(event.eventName)) - return false; - - if (event.eventName !== eventName) - return false; - - Event.extend(event, element); - handler.call(element, event); - }; - } else { - if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && - (eventName === "mouseenter" || eventName === "mouseleave")) { - if (eventName === "mouseenter" || eventName === "mouseleave") { - responder = function(event) { - Event.extend(event, element); - - var parent = event.relatedTarget; - while (parent && parent !== element) { - try { parent = parent.parentNode; } - catch(e) { parent = element; } - } - - if (parent === element) return; - - handler.call(element, event); - }; - } - } else { - responder = function(event) { - Event.extend(event, element); - handler.call(element, event); - }; - } - } - - responder.handler = handler; - respondersForEvent.push(responder); - return responder; - } - - function _destroyCache() { - for (var i = 0, length = CACHE.length; i < length; i++) { - Event.stopObserving(CACHE[i]); - CACHE[i] = null; - } - } - - var CACHE = []; - - if (Prototype.Browser.IE) - window.attachEvent('onunload', _destroyCache); - - if (Prototype.Browser.WebKit) - window.addEventListener('unload', Prototype.emptyFunction, false); - - - var _getDOMEventName = Prototype.K; - - if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { - _getDOMEventName = function(eventName) { - var translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; - return eventName in translations ? translations[eventName] : eventName; - }; - } - - function observe(element, eventName, handler) { - element = $(element); - - var responder = _createResponder(element, eventName, handler); - - if (!responder) return element; - - if (eventName.include(':')) { - if (element.addEventListener) - element.addEventListener("dataavailable", responder, false); - else { - element.attachEvent("ondataavailable", responder); - element.attachEvent("onfilterchange", responder); - } - } else { - var actualEventName = _getDOMEventName(eventName); - - if (element.addEventListener) - element.addEventListener(actualEventName, responder, false); - else - element.attachEvent("on" + actualEventName, responder); - } - - return element; - } - - function stopObserving(element, eventName, handler) { - element = $(element); - - var registry = Element.retrieve(element, 'prototype_event_registry'); - - if (Object.isUndefined(registry)) return element; - - if (eventName && !handler) { - var responders = registry.get(eventName); - - if (Object.isUndefined(responders)) return element; - - responders.each( function(r) { - Element.stopObserving(element, eventName, r.handler); - }); - return element; - } else if (!eventName) { - registry.each( function(pair) { - var eventName = pair.key, responders = pair.value; - - responders.each( function(r) { - Element.stopObserving(element, eventName, r.handler); - }); - }); - return element; - } - - var responders = registry.get(eventName); - - if (!responders) return; - - var responder = responders.find( function(r) { return r.handler === handler; }); - if (!responder) return element; - - var actualEventName = _getDOMEventName(eventName); - - if (eventName.include(':')) { - if (element.removeEventListener) - element.removeEventListener("dataavailable", responder, false); - else { - element.detachEvent("ondataavailable", responder); - element.detachEvent("onfilterchange", responder); - } - } else { - if (element.removeEventListener) - element.removeEventListener(actualEventName, responder, false); - else - element.detachEvent('on' + actualEventName, responder); - } - - registry.set(eventName, responders.without(responder)); - - return element; - } - - function fire(element, eventName, memo, bubble) { - element = $(element); - - if (Object.isUndefined(bubble)) - bubble = true; - - if (element == document && document.createEvent && !element.dispatchEvent) - element = document.documentElement; - - var event; - if (document.createEvent) { - event = document.createEvent('HTMLEvents'); - event.initEvent('dataavailable', true, true); - } else { - event = document.createEventObject(); - event.eventType = bubble ? 'ondataavailable' : 'onfilterchange'; - } - - event.eventName = eventName; - event.memo = memo || { }; - - if (document.createEvent) - element.dispatchEvent(event); - else - element.fireEvent(event.eventType, event); - - return Event.extend(event); - } - - - Object.extend(Event, Event.Methods); - - Object.extend(Event, { - fire: fire, - observe: observe, - stopObserving: stopObserving - }); - - Element.addMethods({ - fire: fire, - - observe: observe, - - stopObserving: stopObserving - }); - - Object.extend(document, { - fire: fire.methodize(), - - observe: observe.methodize(), - - stopObserving: stopObserving.methodize(), - - loaded: false - }); - - if (window.Event) Object.extend(window.Event, Event); - else window.Event = Event; -})(); - -(function() { - /* Support for the DOMContentLoaded event is based on work by Dan Webb, - Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ - - var timer; - - function fireContentLoadedEvent() { - if (document.loaded) return; - if (timer) window.clearTimeout(timer); - document.loaded = true; - document.fire('dom:loaded'); - } - - function checkReadyState() { - if (document.readyState === 'complete') { - document.stopObserving('readystatechange', checkReadyState); - fireContentLoadedEvent(); - } - } - - function pollDoScroll() { - try { document.documentElement.doScroll('left'); } - catch(e) { - timer = pollDoScroll.defer(); - return; - } - fireContentLoadedEvent(); - } - - if (document.addEventListener) { - document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); - } else { - document.observe('readystatechange', checkReadyState); - if (window == top) - timer = pollDoScroll.defer(); - } - - Event.observe(window, 'load', fireContentLoadedEvent); -})(); - -Element.addMethods(); - -/*------------------------------- DEPRECATED -------------------------------*/ - -Hash.toQueryString = Object.toQueryString; - -var Toggle = { display: Element.toggle }; - -Element.Methods.childOf = Element.Methods.descendantOf; - -var Insertion = { - Before: function(element, content) { - return Element.insert(element, {before:content}); - }, - - Top: function(element, content) { - return Element.insert(element, {top:content}); - }, - - Bottom: function(element, content) { - return Element.insert(element, {bottom:content}); - }, - - After: function(element, content) { - return Element.insert(element, {after:content}); - } -}; - -var $continue = new Error('"throw $continue" is deprecated, use "return" instead'); - -var Position = { - includeScrollOffsets: false, - - prepare: function() { - this.deltaX = window.pageXOffset - || document.documentElement.scrollLeft - || document.body.scrollLeft - || 0; - this.deltaY = window.pageYOffset - || document.documentElement.scrollTop - || document.body.scrollTop - || 0; - }, - - within: function(element, x, y) { - if (this.includeScrollOffsets) - return this.withinIncludingScrolloffsets(element, x, y); - this.xcomp = x; - this.ycomp = y; - this.offset = Element.cumulativeOffset(element); - - return (y >= this.offset[1] && - y < this.offset[1] + element.offsetHeight && - x >= this.offset[0] && - x < this.offset[0] + element.offsetWidth); - }, - - withinIncludingScrolloffsets: function(element, x, y) { - var offsetcache = Element.cumulativeScrollOffset(element); - - this.xcomp = x + offsetcache[0] - this.deltaX; - this.ycomp = y + offsetcache[1] - this.deltaY; - this.offset = Element.cumulativeOffset(element); - - return (this.ycomp >= this.offset[1] && - this.ycomp < this.offset[1] + element.offsetHeight && - this.xcomp >= this.offset[0] && - this.xcomp < this.offset[0] + element.offsetWidth); - }, - - overlap: function(mode, element) { - if (!mode) return 0; - if (mode == 'vertical') - return ((this.offset[1] + element.offsetHeight) - this.ycomp) / - element.offsetHeight; - if (mode == 'horizontal') - return ((this.offset[0] + element.offsetWidth) - this.xcomp) / - element.offsetWidth; - }, - - - cumulativeOffset: Element.Methods.cumulativeOffset, - - positionedOffset: Element.Methods.positionedOffset, - - absolutize: function(element) { - Position.prepare(); - return Element.absolutize(element); - }, - - relativize: function(element) { - Position.prepare(); - return Element.relativize(element); - }, - - realOffset: Element.Methods.cumulativeScrollOffset, - - offsetParent: Element.Methods.getOffsetParent, - - page: Element.Methods.viewportOffset, - - clone: function(source, target, options) { - options = options || { }; - return Element.clonePosition(target, source, options); - } -}; - -/*--------------------------------------------------------------------------*/ - -if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){ - function iter(name) { - return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]"; - } - - instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ? - function(element, className) { - className = className.toString().strip(); - var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className); - return cond ? document._getElementsByXPath('.//*' + cond, element) : []; - } : function(element, className) { - className = className.toString().strip(); - var elements = [], classNames = (/\s/.test(className) ? $w(className) : null); - if (!classNames && !className) return elements; - - var nodes = $(element).getElementsByTagName('*'); - className = ' ' + className + ' '; - - for (var i = 0, child, cn; child = nodes[i]; i++) { - if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) || - (classNames && classNames.all(function(name) { - return !name.toString().blank() && cn.include(' ' + name + ' '); - })))) - elements.push(Element.extend(child)); - } - return elements; - }; - - return function(className, parentElement) { - return $(parentElement || document.body).getElementsByClassName(className); - }; -}(Element.Methods); - -/*--------------------------------------------------------------------------*/ - -Element.ClassNames = Class.create(); -Element.ClassNames.prototype = { - initialize: function(element) { - this.element = $(element); - }, - - _each: function(iterator) { - this.element.className.split(/\s+/).select(function(name) { - return name.length > 0; - })._each(iterator); - }, - - set: function(className) { - this.element.className = className; - }, - - add: function(classNameToAdd) { - if (this.include(classNameToAdd)) return; - this.set($A(this).concat(classNameToAdd).join(' ')); - }, - - remove: function(classNameToRemove) { - if (!this.include(classNameToRemove)) return; - this.set($A(this).without(classNameToRemove).join(' ')); - }, - - toString: function() { - return $A(this).join(' '); - } -}; - -Object.extend(Element.ClassNames.prototype, Enumerable); - -/*--------------------------------------------------------------------------*/ diff --git a/src/main/example/simplelogger.properties b/src/main/example/simplelogger.properties new file mode 100644 index 000000000..794b8ad5a --- /dev/null +++ b/src/main/example/simplelogger.properties @@ -0,0 +1,7 @@ +org.slf4j.simpleLogger.logFile=System.out +org.slf4j.simpleLogger.defaultLogLevel=off +org.slf4j.simpleLogger.showDateTime=true +org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss.SSS +org.slf4j.simpleLogger.showThreadName=false +org.slf4j.simpleLogger.showLogName=false +org.slf4j.simpleLogger.levelInBrackets=true \ No newline at end of file diff --git a/src/main/example/web-socket-js/WebSocketMain.swf b/src/main/example/web-socket-js/WebSocketMain.swf deleted file mode 100644 index 817446691..000000000 Binary files a/src/main/example/web-socket-js/WebSocketMain.swf and /dev/null differ diff --git a/src/main/example/web-socket-js/index.html b/src/main/example/web-socket-js/index.html deleted file mode 100644 index ee8db6e68..000000000 --- a/src/main/example/web-socket-js/index.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - diff --git a/src/main/example/web-socket-js/swfobject.js b/src/main/example/web-socket-js/swfobject.js deleted file mode 100644 index 8eafe9dd8..000000000 --- a/src/main/example/web-socket-js/swfobject.js +++ /dev/null @@ -1,4 +0,0 @@ -/* SWFObject v2.2 - is released under the MIT License -*/ -var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab -// License: New BSD License -// Reference: http://dev.w3.org/html5/websockets/ -// Reference: http://tools.ietf.org/html/rfc6455 - -(function() { - - if (window.WEB_SOCKET_FORCE_FLASH) { - // Keeps going. - } else if (window.WebSocket) { - return; - } else if (window.MozWebSocket) { - // Firefox. - window.WebSocket = MozWebSocket; - return; - } - - var logger; - if (window.WEB_SOCKET_LOGGER) { - logger = WEB_SOCKET_LOGGER; - } else if (window.console && window.console.log && window.console.error) { - // In some environment, console is defined but console.log or console.error is missing. - logger = window.console; - } else { - logger = {log: function(){ }, error: function(){ }}; - } - - // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash. - if (swfobject.getFlashPlayerVersion().major < 10) { - logger.error("Flash Player >= 10.0.0 is required."); - return; - } - if (location.protocol == "file:") { - logger.error( - "WARNING: web-socket-js doesn't work in file:///... URL " + - "unless you set Flash Security Settings properly. " + - "Open the page via Web server i.e. http://..."); - } - - /** - * Our own implementation of WebSocket class using Flash. - * @param {string} url - * @param {array or string} protocols - * @param {string} proxyHost - * @param {int} proxyPort - * @param {string} headers - */ - window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) { - var self = this; - self.__id = WebSocket.__nextId++; - WebSocket.__instances[self.__id] = self; - self.readyState = WebSocket.CONNECTING; - self.bufferedAmount = 0; - self.__events = {}; - if (!protocols) { - protocols = []; - } else if (typeof protocols == "string") { - protocols = [protocols]; - } - // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc. - // Otherwise, when onopen fires immediately, onopen is called before it is set. - self.__createTask = setTimeout(function() { - WebSocket.__addTask(function() { - self.__createTask = null; - WebSocket.__flash.create( - self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null); - }); - }, 0); - }; - - /** - * Send data to the web socket. - * @param {string} data The data to send to the socket. - * @return {boolean} True for success, false for failure. - */ - WebSocket.prototype.send = function(data) { - if (this.readyState == WebSocket.CONNECTING) { - throw "INVALID_STATE_ERR: Web Socket connection has not been established"; - } - // We use encodeURIComponent() here, because FABridge doesn't work if - // the argument includes some characters. We don't use escape() here - // because of this: - // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions - // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't - // preserve all Unicode characters either e.g. "\uffff" in Firefox. - // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require - // additional testing. - var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data)); - if (result < 0) { // success - return true; - } else { - this.bufferedAmount += result; - return false; - } - }; - - /** - * Close this web socket gracefully. - */ - WebSocket.prototype.close = function() { - if (this.__createTask) { - clearTimeout(this.__createTask); - this.__createTask = null; - this.readyState = WebSocket.CLOSED; - return; - } - if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) { - return; - } - this.readyState = WebSocket.CLOSING; - WebSocket.__flash.close(this.__id); - }; - - /** - * Implementation of {@link DOM 2 EventTarget Interface} - * - * @param {string} type - * @param {function} listener - * @param {boolean} useCapture - * @return void - */ - WebSocket.prototype.addEventListener = function(type, listener, useCapture) { - if (!(type in this.__events)) { - this.__events[type] = []; - } - this.__events[type].push(listener); - }; - - /** - * Implementation of {@link DOM 2 EventTarget Interface} - * - * @param {string} type - * @param {function} listener - * @param {boolean} useCapture - * @return void - */ - WebSocket.prototype.removeEventListener = function(type, listener, useCapture) { - if (!(type in this.__events)) return; - var events = this.__events[type]; - for (var i = events.length - 1; i >= 0; --i) { - if (events[i] === listener) { - events.splice(i, 1); - break; - } - } - }; - - /** - * Implementation of {@link DOM 2 EventTarget Interface} - * - * @param {Event} event - * @return void - */ - WebSocket.prototype.dispatchEvent = function(event) { - var events = this.__events[event.type] || []; - for (var i = 0; i < events.length; ++i) { - events[i](event); - } - var handler = this["on" + event.type]; - if (handler) handler.apply(this, [event]); - }; - - /** - * Handles an event from Flash. - * @param {Object} flashEvent - */ - WebSocket.prototype.__handleEvent = function(flashEvent) { - - if ("readyState" in flashEvent) { - this.readyState = flashEvent.readyState; - } - if ("protocol" in flashEvent) { - this.protocol = flashEvent.protocol; - } - - var jsEvent; - if (flashEvent.type == "open" || flashEvent.type == "error") { - jsEvent = this.__createSimpleEvent(flashEvent.type); - } else if (flashEvent.type == "close") { - jsEvent = this.__createSimpleEvent("close"); - jsEvent.wasClean = flashEvent.wasClean ? true : false; - jsEvent.code = flashEvent.code; - jsEvent.reason = flashEvent.reason; - } else if (flashEvent.type == "message") { - var data = decodeURIComponent(flashEvent.message); - jsEvent = this.__createMessageEvent("message", data); - } else { - throw "unknown event type: " + flashEvent.type; - } - - this.dispatchEvent(jsEvent); - - }; - - WebSocket.prototype.__createSimpleEvent = function(type) { - if (document.createEvent && window.Event) { - var event = document.createEvent("Event"); - event.initEvent(type, false, false); - return event; - } else { - return {type: type, bubbles: false, cancelable: false}; - } - }; - - WebSocket.prototype.__createMessageEvent = function(type, data) { - if (document.createEvent && window.MessageEvent && !window.opera) { - var event = document.createEvent("MessageEvent"); - event.initMessageEvent("message", false, false, data, null, null, window, null); - return event; - } else { - // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes. - return {type: type, data: data, bubbles: false, cancelable: false}; - } - }; - - /** - * Define the WebSocket readyState enumeration. - */ - WebSocket.CONNECTING = 0; - WebSocket.OPEN = 1; - WebSocket.CLOSING = 2; - WebSocket.CLOSED = 3; - - WebSocket.__initialized = false; - WebSocket.__flash = null; - WebSocket.__instances = {}; - WebSocket.__tasks = []; - WebSocket.__nextId = 0; - - /** - * Load a new flash security policy file. - * @param {string} url - */ - WebSocket.loadFlashPolicyFile = function(url){ - WebSocket.__addTask(function() { - WebSocket.__flash.loadManualPolicyFile(url); - }); - }; - - /** - * Loads WebSocketMain.swf and creates WebSocketMain object in Flash. - */ - WebSocket.__initialize = function() { - - if (WebSocket.__initialized) return; - WebSocket.__initialized = true; - - if (WebSocket.__swfLocation) { - // For backword compatibility. - window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation; - } - if (!window.WEB_SOCKET_SWF_LOCATION) { - logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); - return; - } - if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR && - !WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) && - WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) { - var swfHost = RegExp.$1; - if (location.host != swfHost) { - logger.error( - "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " + - "('" + location.host + "' != '" + swfHost + "'). " + - "See also 'How to host HTML file and SWF file in different domains' section " + - "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " + - "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;"); - } - } - var container = document.createElement("div"); - container.id = "webSocketContainer"; - // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents - // Flash from loading at least in IE. So we move it out of the screen at (-100, -100). - // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash - // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is - // the best we can do as far as we know now. - container.style.position = "absolute"; - if (WebSocket.__isFlashLite()) { - container.style.left = "0px"; - container.style.top = "0px"; - } else { - container.style.left = "-100px"; - container.style.top = "-100px"; - } - var holder = document.createElement("div"); - holder.id = "webSocketFlash"; - container.appendChild(holder); - document.body.appendChild(container); - // See this article for hasPriority: - // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html - swfobject.embedSWF( - WEB_SOCKET_SWF_LOCATION, - "webSocketFlash", - "1" /* width */, - "1" /* height */, - "10.0.0" /* SWF version */, - null, - null, - {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"}, - null, - function(e) { - if (!e.success) { - logger.error("[WebSocket] swfobject.embedSWF failed"); - } - } - ); - - }; - - /** - * Called by Flash to notify JS that it's fully loaded and ready - * for communication. - */ - WebSocket.__onFlashInitialized = function() { - // We need to set a timeout here to avoid round-trip calls - // to flash during the initialization process. - setTimeout(function() { - WebSocket.__flash = document.getElementById("webSocketFlash"); - WebSocket.__flash.setCallerUrl(location.href); - WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG); - for (var i = 0; i < WebSocket.__tasks.length; ++i) { - WebSocket.__tasks[i](); - } - WebSocket.__tasks = []; - }, 0); - }; - - /** - * Called by Flash to notify WebSockets events are fired. - */ - WebSocket.__onFlashEvent = function() { - setTimeout(function() { - try { - // Gets events using receiveEvents() instead of getting it from event object - // of Flash event. This is to make sure to keep message order. - // It seems sometimes Flash events don't arrive in the same order as they are sent. - var events = WebSocket.__flash.receiveEvents(); - for (var i = 0; i < events.length; ++i) { - WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]); - } - } catch (e) { - logger.error(e); - } - }, 0); - return true; - }; - - // Called by Flash. - WebSocket.__log = function(message) { - logger.log(decodeURIComponent(message)); - }; - - // Called by Flash. - WebSocket.__error = function(message) { - logger.error(decodeURIComponent(message)); - }; - - WebSocket.__addTask = function(task) { - if (WebSocket.__flash) { - task(); - } else { - WebSocket.__tasks.push(task); - } - }; - - /** - * Test if the browser is running flash lite. - * @return {boolean} True if flash lite is running, false otherwise. - */ - WebSocket.__isFlashLite = function() { - if (!window.navigator || !window.navigator.mimeTypes) { - return false; - } - var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"]; - if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) { - return false; - } - return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false; - }; - - if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) { - // NOTE: - // This fires immediately if web_socket.js is dynamically loaded after - // the document is loaded. - swfobject.addDomLoadEvent(function() { - WebSocket.__initialize(); - }); - } - -})(); diff --git a/src/main/java/org/java_websocket/AbstractWebSocket.java b/src/main/java/org/java_websocket/AbstractWebSocket.java new file mode 100644 index 000000000..ae9ce1824 --- /dev/null +++ b/src/main/java/org/java_websocket/AbstractWebSocket.java @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.util.NamedThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Base class for additional implementations for the server as well as the client + */ +public abstract class AbstractWebSocket extends WebSocketAdapter { + + /** + * Logger instance + * + * @since 1.4.0 + */ + private final Logger log = LoggerFactory.getLogger(AbstractWebSocket.class); + + /** + * Attribute which allows you to deactivate the Nagle's algorithm + * + * @since 1.3.3 + */ + private boolean tcpNoDelay; + + /** + * Attribute which allows you to enable/disable the SO_REUSEADDR socket option. + * + * @since 1.3.5 + */ + private boolean reuseAddr; + + /** + * Attribute for a service that triggers lost connection checking + * + * @since 1.4.1 + */ + private ScheduledExecutorService connectionLostCheckerService; + /** + * Attribute for a task that checks for lost connections + * + * @since 1.4.1 + */ + private ScheduledFuture connectionLostCheckerFuture; + + /** + * Attribute for the lost connection check interval in nanoseconds + * + * @since 1.3.4 + */ + private long connectionLostTimeout = TimeUnit.SECONDS.toNanos(60); + + /** + * Attribute to keep track if the WebSocket Server/Client is running/connected + * + * @since 1.3.9 + */ + private boolean websocketRunning = false; + + /** + * Attribute to start internal threads as daemon + * + * @since 1.5.6 + */ + private boolean daemon = false; + + /** + * Attribute to sync on + */ + private final Object syncConnectionLost = new Object(); + + /** + * TCP receive buffer size that will be used for sockets (zero means use system default) + * + * @since 1.5.7 + */ + private int receiveBufferSize = 0; + + /** + * Used for internal buffer allocations when the socket buffer size is not specified. + */ + protected static int DEFAULT_READ_BUFFER_SIZE = 65536; + + /** + * Get the interval checking for lost connections Default is 60 seconds + * + * @return the interval in seconds + * @since 1.3.4 + */ + public int getConnectionLostTimeout() { + synchronized (syncConnectionLost) { + return (int) TimeUnit.NANOSECONDS.toSeconds(connectionLostTimeout); + } + } + + /** + * Setter for the interval checking for lost connections A value lower or equal 0 results in the + * check to be deactivated + * + * @param connectionLostTimeout the interval in seconds + * @since 1.3.4 + */ + public void setConnectionLostTimeout(int connectionLostTimeout) { + synchronized (syncConnectionLost) { + this.connectionLostTimeout = TimeUnit.SECONDS.toNanos(connectionLostTimeout); + if (this.connectionLostTimeout <= 0) { + log.trace("Connection lost timer stopped"); + cancelConnectionLostTimer(); + return; + } + if (this.websocketRunning) { + log.trace("Connection lost timer restarted"); + //Reset all the pings + try { + ArrayList connections = new ArrayList<>(getConnections()); + WebSocketImpl webSocketImpl; + for (WebSocket conn : connections) { + if (conn instanceof WebSocketImpl) { + webSocketImpl = (WebSocketImpl) conn; + webSocketImpl.updateLastPong(); + } + } + } catch (Exception e) { + log.error("Exception during connection lost restart", e); + } + restartConnectionLostTimer(); + } + } + } + + /** + * Stop the connection lost timer + * + * @since 1.3.4 + */ + protected void stopConnectionLostTimer() { + synchronized (syncConnectionLost) { + if (connectionLostCheckerService != null || connectionLostCheckerFuture != null) { + this.websocketRunning = false; + log.trace("Connection lost timer stopped"); + cancelConnectionLostTimer(); + } + } + } + + /** + * Start the connection lost timer + * + * @since 1.3.4 + */ + protected void startConnectionLostTimer() { + synchronized (syncConnectionLost) { + if (this.connectionLostTimeout <= 0) { + log.trace("Connection lost timer deactivated"); + return; + } + log.trace("Connection lost timer started"); + this.websocketRunning = true; + restartConnectionLostTimer(); + } + } + + /** + * This methods allows the reset of the connection lost timer in case of a changed parameter + * + * @since 1.3.4 + */ + private void restartConnectionLostTimer() { + cancelConnectionLostTimer(); + connectionLostCheckerService = Executors + .newSingleThreadScheduledExecutor(new NamedThreadFactory("WebSocketConnectionLostChecker", daemon)); + Runnable connectionLostChecker = new Runnable() { + + /** + * Keep the connections in a separate list to not cause deadlocks + */ + private ArrayList connections = new ArrayList<>(); + + @Override + public void run() { + connections.clear(); + try { + connections.addAll(getConnections()); + long minimumPongTime; + synchronized (syncConnectionLost) { + minimumPongTime = (long) (System.nanoTime() - (connectionLostTimeout * 1.5)); + } + for (WebSocket conn : connections) { + executeConnectionLostDetection(conn, minimumPongTime); + } + } catch (Exception e) { + //Ignore this exception + } + connections.clear(); + } + }; + + connectionLostCheckerFuture = connectionLostCheckerService + .scheduleAtFixedRate(connectionLostChecker, connectionLostTimeout, connectionLostTimeout, + TimeUnit.NANOSECONDS); + } + + /** + * Send a ping to the endpoint or close the connection since the other endpoint did not respond + * with a ping + * + * @param webSocket the websocket instance + * @param minimumPongTime the lowest/oldest allowable last pong time (in nanoTime) before we + * consider the connection to be lost + */ + private void executeConnectionLostDetection(WebSocket webSocket, long minimumPongTime) { + if (!(webSocket instanceof WebSocketImpl)) { + return; + } + WebSocketImpl webSocketImpl = (WebSocketImpl) webSocket; + if (webSocketImpl.getLastPong() < minimumPongTime) { + log.trace("Closing connection due to no pong received: {}", webSocketImpl); + webSocketImpl.closeConnection(CloseFrame.ABNORMAL_CLOSE, + "The connection was closed because the other endpoint did not respond with a pong in time. For more information check: https://github.com/TooTallNate/Java-WebSocket/wiki/Lost-connection-detection"); + } else { + if (webSocketImpl.isOpen()) { + webSocketImpl.sendPing(); + } else { + log.trace("Trying to ping a non open connection: {}", webSocketImpl); + } + } + } + + /** + * Getter to get all the currently available connections + * + * @return the currently available connections + * @since 1.3.4 + */ + protected abstract Collection getConnections(); + + /** + * Cancel any running timer for the connection lost detection + * + * @since 1.3.4 + */ + private void cancelConnectionLostTimer() { + if (connectionLostCheckerService != null) { + connectionLostCheckerService.shutdownNow(); + connectionLostCheckerService = null; + } + if (connectionLostCheckerFuture != null) { + connectionLostCheckerFuture.cancel(false); + connectionLostCheckerFuture = null; + } + } + + /** + * Tests if TCP_NODELAY is enabled. + * + * @return a boolean indicating whether or not TCP_NODELAY is enabled for new connections. + * @since 1.3.3 + */ + public boolean isTcpNoDelay() { + return tcpNoDelay; + } + + /** + * Setter for tcpNoDelay + *

+ * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm) for new connections + * + * @param tcpNoDelay true to enable TCP_NODELAY, false to disable. + * @since 1.3.3 + */ + public void setTcpNoDelay(boolean tcpNoDelay) { + this.tcpNoDelay = tcpNoDelay; + } + + /** + * Tests Tests if SO_REUSEADDR is enabled. + * + * @return a boolean indicating whether or not SO_REUSEADDR is enabled. + * @since 1.3.5 + */ + public boolean isReuseAddr() { + return reuseAddr; + } + + /** + * Setter for soReuseAddr + *

+ * Enable/disable SO_REUSEADDR for the socket + * + * @param reuseAddr whether to enable or disable SO_REUSEADDR + * @since 1.3.5 + */ + public void setReuseAddr(boolean reuseAddr) { + this.reuseAddr = reuseAddr; + } + + + /** + * Getter for daemon + * + * @return whether internal threads are spawned in daemon mode + * @since 1.5.6 + */ + public boolean isDaemon() { + return daemon; + } + + /** + * Setter for daemon + *

+ * Controls whether or not internal threads are spawned in daemon mode + * + * @since 1.5.6 + */ + public void setDaemon(boolean daemon) { + this.daemon = daemon; + } + + /** + * Returns the TCP receive buffer size that will be used for sockets (or zero, if not explicitly set). + * @see java.net.Socket#setReceiveBufferSize(int) + * + * @since 1.5.7 + */ + public int getReceiveBufferSize() { + return receiveBufferSize; + } + + /** + * Sets the TCP receive buffer size that will be used for sockets. + * If this is not explicitly set (or set to zero), the system default is used. + * @see java.net.Socket#setReceiveBufferSize(int) + * + * @since 1.5.7 + */ + public void setReceiveBufferSize(int receiveBufferSize) { + if (receiveBufferSize < 0) { + throw new IllegalArgumentException("buffer size < 0"); + } + this.receiveBufferSize = receiveBufferSize; + } + +} diff --git a/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java b/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java index 0481a6d4a..33e6ddcc9 100644 --- a/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java +++ b/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java @@ -1,3 +1,28 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket; import java.io.IOException; @@ -5,71 +30,82 @@ import java.nio.channels.ByteChannel; import java.nio.channels.SocketChannel; -import javax.net.ssl.SSLException; - - +/** + * @deprecated + */ +@Deprecated public class AbstractWrappedByteChannel implements WrappedByteChannel { - private final ByteChannel channel; - - public AbstractWrappedByteChannel( ByteChannel towrap ) { - this.channel = towrap; - } - - public AbstractWrappedByteChannel( WrappedByteChannel towrap ) { - this.channel = towrap; - } - - @Override - public int read( ByteBuffer dst ) throws IOException { - return channel.read( dst ); - } - - @Override - public boolean isOpen() { - return channel.isOpen(); - } - - @Override - public void close() throws IOException { - channel.close(); - } - - @Override - public int write( ByteBuffer src ) throws IOException { - return channel.write( src ); - } - - @Override - public boolean isNeedWrite() { - return channel instanceof WrappedByteChannel ? ( (WrappedByteChannel) channel ).isNeedWrite() : false; - } - - @Override - public void writeMore() throws IOException { - if( channel instanceof WrappedByteChannel ) - ( (WrappedByteChannel) channel ).writeMore(); - - } - - @Override - public boolean isNeedRead() { - return channel instanceof WrappedByteChannel ? ( (WrappedByteChannel) channel ).isNeedRead() : false; - - } - - @Override - public int readMore( ByteBuffer dst ) throws SSLException { - return channel instanceof WrappedByteChannel ? ( (WrappedByteChannel) channel ).readMore( dst ) : 0; - } - - @Override - public boolean isBlocking() { - if( channel instanceof SocketChannel ) - return ( (SocketChannel) channel ).isBlocking(); - else if( channel instanceof WrappedByteChannel ) - return ( (WrappedByteChannel) channel ).isBlocking(); - return false; - } + private final ByteChannel channel; + + /** + * @deprecated + */ + @Deprecated + public AbstractWrappedByteChannel(ByteChannel towrap) { + this.channel = towrap; + } + + /** + * @deprecated + */ + @Deprecated + public AbstractWrappedByteChannel(WrappedByteChannel towrap) { + this.channel = towrap; + } + + @Override + public int read(ByteBuffer dst) throws IOException { + return channel.read(dst); + } + + @Override + public boolean isOpen() { + return channel.isOpen(); + } + + @Override + public void close() throws IOException { + channel.close(); + } + + @Override + public int write(ByteBuffer src) throws IOException { + return channel.write(src); + } + + @Override + public boolean isNeedWrite() { + return channel instanceof WrappedByteChannel && ((WrappedByteChannel) channel).isNeedWrite(); + } + + @Override + public void writeMore() throws IOException { + if (channel instanceof WrappedByteChannel) { + ((WrappedByteChannel) channel).writeMore(); + } + + } + + @Override + public boolean isNeedRead() { + return channel instanceof WrappedByteChannel && ((WrappedByteChannel) channel).isNeedRead(); + + } + + @Override + public int readMore(ByteBuffer dst) throws IOException { + return channel instanceof WrappedByteChannel ? ((WrappedByteChannel) channel).readMore(dst) : 0; + } + + @Override + public boolean isBlocking() { + if (channel instanceof SocketChannel) { + return ((SocketChannel) channel).isBlocking(); + } else if (channel instanceof WrappedByteChannel) { + return ((WrappedByteChannel) channel).isBlocking(); + } + return false; + } } diff --git a/src/main/java/org/java_websocket/SSLSocketChannel.java b/src/main/java/org/java_websocket/SSLSocketChannel.java new file mode 100644 index 000000000..b4024505b --- /dev/null +++ b/src/main/java/org/java_websocket/SSLSocketChannel.java @@ -0,0 +1,539 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket; + +import java.io.IOException; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.concurrent.ExecutorService; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +import org.java_websocket.interfaces.ISSLChannel; +import org.java_websocket.util.ByteBufferUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * A class that represents an SSL/TLS peer, and can be extended to create a client or a server. + *

+ * It makes use of the JSSE framework, and specifically the {@link SSLEngine} logic, which is + * described by Oracle as "an advanced API, not appropriate for casual use", since it requires the + * user to implement much of the communication establishment procedure himself. More information + * about it can be found here: http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SSLEngine + *

+ * {@link SSLSocketChannel} implements the handshake protocol, required to establish a connection + * between two peers, which is common for both client and server and provides the abstract {@link + * SSLSocketChannel#read(ByteBuffer)} and {@link SSLSocketChannel#write(ByteBuffer)} (String)} + * methods, that need to be implemented by the specific SSL/TLS peer that is going to extend this + * class. + * + * @author Alex Karnezis + *

+ * Modified by marci4 to allow the usage as a ByteChannel + *

+ * Permission for usage received at May 25, 2017 by Alex Karnezis + */ +public class SSLSocketChannel implements WrappedByteChannel, ByteChannel, ISSLChannel { + + /** + * Logger instance + * + * @since 1.4.0 + */ + private final Logger log = LoggerFactory.getLogger(SSLSocketChannel.class); + + /** + * The underlying socket channel + */ + private final SocketChannel socketChannel; + + /** + * The engine which will be used for un-/wrapping of buffers + */ + private final SSLEngine engine; + + + /** + * Will contain this peer's application data in plaintext, that will be later encrypted using + * {@link SSLEngine#wrap(ByteBuffer, ByteBuffer)} and sent to the other peer. This buffer can + * typically be of any size, as long as it is large enough to contain this peer's outgoing + * messages. If this peer tries to send a message bigger than buffer's capacity a {@link + * BufferOverflowException} will be thrown. + */ + private ByteBuffer myAppData; + + /** + * Will contain this peer's encrypted data, that will be generated after {@link + * SSLEngine#wrap(ByteBuffer, ByteBuffer)} is applied on {@link SSLSocketChannel#myAppData}. It + * should be initialized using {@link SSLSession#getPacketBufferSize()}, which returns the size up + * to which, SSL/TLS packets will be generated from the engine under a session. All SSLEngine + * network buffers should be sized at least this large to avoid insufficient space problems when + * performing wrap and unwrap calls. + */ + private ByteBuffer myNetData; + + /** + * Will contain the other peer's (decrypted) application data. It must be large enough to hold the + * application data from any peer. Can be initialized with {@link SSLSession#getApplicationBufferSize()} + * for an estimation of the other peer's application data and should be enlarged if this size is + * not enough. + */ + private ByteBuffer peerAppData; + + /** + * Will contain the other peer's encrypted data. The SSL/TLS protocols specify that + * implementations should produce packets containing at most 16 KB of plaintext, so a buffer sized + * to this value should normally cause no capacity problems. However, some implementations violate + * the specification and generate large records up to 32 KB. If the {@link + * SSLEngine#unwrap(ByteBuffer, ByteBuffer)} detects large inbound packets, the buffer sizes + * returned by SSLSession will be updated dynamically, so the this peer should check for overflow + * conditions and enlarge the buffer using the session's (updated) buffer size. + */ + private ByteBuffer peerNetData; + + /** + * Will be used to execute tasks that may emerge during handshake in parallel with the server's + * main thread. + */ + private ExecutorService executor; + + + public SSLSocketChannel(SocketChannel inputSocketChannel, SSLEngine inputEngine, + ExecutorService inputExecutor, SelectionKey key) throws IOException { + if (inputSocketChannel == null || inputEngine == null || executor == inputExecutor) { + throw new IllegalArgumentException("parameter must not be null"); + } + + this.socketChannel = inputSocketChannel; + this.engine = inputEngine; + this.executor = inputExecutor; + myNetData = ByteBuffer.allocate(engine.getSession().getPacketBufferSize()); + peerNetData = ByteBuffer.allocate(engine.getSession().getPacketBufferSize()); + this.engine.beginHandshake(); + if (doHandshake()) { + if (key != null) { + key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); + } + } else { + try { + socketChannel.close(); + } catch (IOException e) { + log.error("Exception during the closing of the channel", e); + } + } + } + + @Override + public synchronized int read(ByteBuffer dst) throws IOException { + if (!dst.hasRemaining()) { + return 0; + } + if (peerAppData.hasRemaining()) { + peerAppData.flip(); + return ByteBufferUtils.transferByteBuffer(peerAppData, dst); + } + peerNetData.compact(); + + int bytesRead = socketChannel.read(peerNetData); + /* + * If bytesRead are 0 put we still have some data in peerNetData still to an unwrap (for testcase 1.1.6) + */ + if (bytesRead > 0 || peerNetData.hasRemaining()) { + peerNetData.flip(); + while (peerNetData.hasRemaining()) { + peerAppData.compact(); + SSLEngineResult result; + try { + result = engine.unwrap(peerNetData, peerAppData); + } catch (SSLException e) { + log.error("SSLException during unwrap", e); + throw e; + } + switch (result.getStatus()) { + case OK: + peerAppData.flip(); + return ByteBufferUtils.transferByteBuffer(peerAppData, dst); + case BUFFER_UNDERFLOW: + peerAppData.flip(); + return ByteBufferUtils.transferByteBuffer(peerAppData, dst); + case BUFFER_OVERFLOW: + peerAppData = enlargeApplicationBuffer(peerAppData); + return read(dst); + case CLOSED: + closeConnection(); + dst.clear(); + return -1; + default: + throw new IllegalStateException("Invalid SSL status: " + result.getStatus()); + } + } + } else if (bytesRead < 0) { + handleEndOfStream(); + } + ByteBufferUtils.transferByteBuffer(peerAppData, dst); + return bytesRead; + } + + @Override + public synchronized int write(ByteBuffer output) throws IOException { + int num = 0; + while (output.hasRemaining()) { + // The loop has a meaning for (outgoing) messages larger than 16KB. + // Every wrap call will remove 16KB from the original message and send it to the remote peer. + myNetData.clear(); + SSLEngineResult result = engine.wrap(output, myNetData); + switch (result.getStatus()) { + case OK: + myNetData.flip(); + while (myNetData.hasRemaining()) { + num += socketChannel.write(myNetData); + } + break; + case BUFFER_OVERFLOW: + myNetData = enlargePacketBuffer(myNetData); + break; + case BUFFER_UNDERFLOW: + throw new SSLException( + "Buffer underflow occurred after a wrap. I don't think we should ever get here."); + case CLOSED: + closeConnection(); + return 0; + default: + throw new IllegalStateException("Invalid SSL status: " + result.getStatus()); + } + } + return num; + } + + /** + * Implements the handshake protocol between two peers, required for the establishment of the + * SSL/TLS connection. During the handshake, encryption configuration information - such as the + * list of available cipher suites - will be exchanged and if the handshake is successful will + * lead to an established SSL/TLS session. + *

+ *

+ * A typical handshake will usually contain the following steps: + *

+ *

    + *
  • 1. wrap: ClientHello
  • + *
  • 2. unwrap: ServerHello/Cert/ServerHelloDone
  • + *
  • 3. wrap: ClientKeyExchange
  • + *
  • 4. wrap: ChangeCipherSpec
  • + *
  • 5. wrap: Finished
  • + *
  • 6. unwrap: ChangeCipherSpec
  • + *
  • 7. unwrap: Finished
  • + *
+ *

+ * Handshake is also used during the end of the session, in order to properly close the connection between the two peers. + * A proper connection close will typically include the one peer sending a CLOSE message to another, and then wait for + * the other's CLOSE message to close the transport link. The other peer from his perspective would read a CLOSE message + * from his peer and then enter the handshake procedure to send his own CLOSE message as well. + * + * @return True if the connection handshake was successful or false if an error occurred. + * @throws IOException - if an error occurs during read/write to the socket channel. + */ + private boolean doHandshake() throws IOException { + SSLEngineResult result; + HandshakeStatus handshakeStatus; + + // NioSslPeer's fields myAppData and peerAppData are supposed to be large enough to hold all message data the peer + // will send and expects to receive from the other peer respectively. Since the messages to be exchanged will usually be less + // than 16KB long the capacity of these fields should also be smaller. Here we initialize these two local buffers + // to be used for the handshake, while keeping client's buffers at the same size. + int appBufferSize = engine.getSession().getApplicationBufferSize(); + myAppData = ByteBuffer.allocate(appBufferSize); + peerAppData = ByteBuffer.allocate(appBufferSize); + myNetData.clear(); + peerNetData.clear(); + + handshakeStatus = engine.getHandshakeStatus(); + boolean handshakeComplete = false; + while (!handshakeComplete) { + switch (handshakeStatus) { + case FINISHED: + handshakeComplete = !this.peerNetData.hasRemaining(); + if (handshakeComplete) { + return true; + } + socketChannel.write(this.peerNetData); + break; + case NEED_UNWRAP: + if (socketChannel.read(peerNetData) < 0) { + if (engine.isInboundDone() && engine.isOutboundDone()) { + return false; + } + try { + engine.closeInbound(); + } catch (SSLException e) { + //Ignore, can't do anything against this exception + } + engine.closeOutbound(); + // After closeOutbound the engine will be set to WRAP state, in order to try to send a close message to the client. + handshakeStatus = engine.getHandshakeStatus(); + break; + } + peerNetData.flip(); + try { + result = engine.unwrap(peerNetData, peerAppData); + peerNetData.compact(); + handshakeStatus = result.getHandshakeStatus(); + } catch (SSLException sslException) { + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + break; + } + switch (result.getStatus()) { + case OK: + break; + case BUFFER_OVERFLOW: + // Will occur when peerAppData's capacity is smaller than the data derived from peerNetData's unwrap. + peerAppData = enlargeApplicationBuffer(peerAppData); + break; + case BUFFER_UNDERFLOW: + // Will occur either when no data was read from the peer or when the peerNetData buffer was too small to hold all peer's data. + peerNetData = handleBufferUnderflow(peerNetData); + break; + case CLOSED: + if (engine.isOutboundDone()) { + return false; + } else { + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + break; + } + default: + throw new IllegalStateException("Invalid SSL status: " + result.getStatus()); + } + break; + case NEED_WRAP: + myNetData.clear(); + try { + result = engine.wrap(myAppData, myNetData); + handshakeStatus = result.getHandshakeStatus(); + } catch (SSLException sslException) { + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + break; + } + switch (result.getStatus()) { + case OK: + myNetData.flip(); + while (myNetData.hasRemaining()) { + socketChannel.write(myNetData); + } + break; + case BUFFER_OVERFLOW: + // Will occur if there is not enough space in myNetData buffer to write all the data that would be generated by the method wrap. + // Since myNetData is set to session's packet size we should not get to this point because SSLEngine is supposed + // to produce messages smaller or equal to that, but a general handling would be the following: + myNetData = enlargePacketBuffer(myNetData); + break; + case BUFFER_UNDERFLOW: + throw new SSLException( + "Buffer underflow occurred after a wrap. I don't think we should ever get here."); + case CLOSED: + try { + myNetData.flip(); + while (myNetData.hasRemaining()) { + socketChannel.write(myNetData); + } + // At this point the handshake status will probably be NEED_UNWRAP so we make sure that peerNetData is clear to read. + peerNetData.clear(); + } catch (Exception e) { + handshakeStatus = engine.getHandshakeStatus(); + } + break; + default: + throw new IllegalStateException("Invalid SSL status: " + result.getStatus()); + } + break; + case NEED_TASK: + Runnable task; + while ((task = engine.getDelegatedTask()) != null) { + executor.execute(task); + } + handshakeStatus = engine.getHandshakeStatus(); + break; + + case NOT_HANDSHAKING: + break; + default: + throw new IllegalStateException("Invalid SSL status: " + handshakeStatus); + } + } + + return true; + + } + + /** + * Enlarging a packet buffer (peerNetData or myNetData) + * + * @param buffer the buffer to enlarge + * @return the enlarged buffer + */ + private ByteBuffer enlargePacketBuffer(ByteBuffer buffer) { + return enlargeBuffer(buffer, engine.getSession().getPacketBufferSize()); + } + + /** + * Enlarging a packet buffer (peerAppData or myAppData) + * + * @param buffer the buffer to enlarge + * @return the enlarged buffer + */ + private ByteBuffer enlargeApplicationBuffer(ByteBuffer buffer) { + return enlargeBuffer(buffer, engine.getSession().getApplicationBufferSize()); + } + + /** + * Compares sessionProposedCapacity with buffer's capacity. If buffer's capacity is + * smaller, returns a buffer with the proposed capacity. If it's equal or larger, returns a buffer + * with capacity twice the size of the initial one. + * + * @param buffer - the buffer to be enlarged. + * @param sessionProposedCapacity - the minimum size of the new buffer, proposed by {@link + * SSLSession}. + * @return A new buffer with a larger capacity. + */ + private ByteBuffer enlargeBuffer(ByteBuffer buffer, int sessionProposedCapacity) { + if (sessionProposedCapacity > buffer.capacity()) { + buffer = ByteBuffer.allocate(sessionProposedCapacity); + } else { + buffer = ByteBuffer.allocate(buffer.capacity() * 2); + } + return buffer; + } + + /** + * Handles {@link SSLEngineResult.Status#BUFFER_UNDERFLOW}. Will check if the buffer is already + * filled, and if there is no space problem will return the same buffer, so the client tries to + * read again. If the buffer is already filled will try to enlarge the buffer either to session's + * proposed size or to a larger capacity. A buffer underflow can happen only after an unwrap, so + * the buffer will always be a peerNetData buffer. + * + * @param buffer - will always be peerNetData buffer. + * @return The same buffer if there is no space problem or a new buffer with the same data but + * more space. + */ + private ByteBuffer handleBufferUnderflow(ByteBuffer buffer) { + if (engine.getSession().getPacketBufferSize() < buffer.limit()) { + return buffer; + } else { + ByteBuffer replaceBuffer = enlargePacketBuffer(buffer); + buffer.flip(); + replaceBuffer.put(buffer); + return replaceBuffer; + } + } + + /** + * This method should be called when this peer wants to explicitly close the connection or when a + * close message has arrived from the other peer, in order to provide an orderly shutdown. + *

+ * It first calls {@link SSLEngine#closeOutbound()} which prepares this peer to send its own close + * message and sets {@link SSLEngine} to the NEED_WRAP state. Then, it delegates the + * exchange of close messages to the handshake method and finally, it closes socket channel. + * + * @throws IOException if an I/O error occurs to the socket channel. + */ + private void closeConnection() throws IOException { + engine.closeOutbound(); + try { + doHandshake(); + } catch (IOException e) { + //Just ignore this exception since we are closing the connection already + } + socketChannel.close(); + } + + /** + * In addition to orderly shutdowns, an unorderly shutdown may occur, when the transport link + * (socket channel) is severed before close messages are exchanged. This may happen by getting an + * -1 or {@link IOException} when trying to read from the socket channel, or an {@link + * IOException} when trying to write to it. In both cases {@link SSLEngine#closeInbound()} should + * be called and then try to follow the standard procedure. + * + * @throws IOException if an I/O error occurs to the socket channel. + */ + private void handleEndOfStream() throws IOException { + try { + engine.closeInbound(); + } catch (Exception e) { + log.error( + "This engine was forced to close inbound, without having received the proper SSL/TLS close notification message from the peer, due to end of stream."); + } + closeConnection(); + } + + @Override + public boolean isNeedWrite() { + return false; + } + + @Override + public void writeMore() throws IOException { + //Nothing to do since we write out all the data in a while loop + } + + @Override + public boolean isNeedRead() { + return peerNetData.hasRemaining() || peerAppData.hasRemaining(); + } + + @Override + public int readMore(ByteBuffer dst) throws IOException { + return read(dst); + } + + @Override + public boolean isBlocking() { + return socketChannel.isBlocking(); + } + + + @Override + public boolean isOpen() { + return socketChannel.isOpen(); + } + + @Override + public void close() throws IOException { + closeConnection(); + } + + @Override + public SSLEngine getSSLEngine() { + return engine; + } +} \ No newline at end of file diff --git a/src/main/java/org/java_websocket/SSLSocketChannel2.java b/src/main/java/org/java_websocket/SSLSocketChannel2.java index e540612bb..23c4f8af1 100644 --- a/src/main/java/org/java_websocket/SSLSocketChannel2.java +++ b/src/main/java/org/java_websocket/SSLSocketChannel2.java @@ -1,16 +1,30 @@ -/** - * Copyright (C) 2003 Alexander Kout - * Originally from the jFxp project (http://jfxp.sourceforge.net/). - * Copied with permission June 11, 2012 by Femi Omojola (fomojola@ideasynthesis.com). +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. */ + package org.java_websocket; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLEngineResult; -import javax.net.ssl.SSLEngineResult.HandshakeStatus; -import javax.net.ssl.SSLEngineResult.Status; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSession; import java.io.EOFException; import java.io.IOException; import java.net.Socket; @@ -26,361 +40,462 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLEngineResult.Status; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +import org.java_websocket.interfaces.ISSLChannel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Implements the relevant portions of the SocketChannel interface with the SSLEngine wrapper. */ -public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel { - /** - * This object is used to feed the {@link SSLEngine}'s wrap and unwrap methods during the handshake phase. - **/ - protected static ByteBuffer emptybuffer = ByteBuffer.allocate( 0 ); - - protected ExecutorService exec; - - protected List> tasks; - - /** raw payload incomming */ - protected ByteBuffer inData; - /** encrypted data outgoing */ - protected ByteBuffer outCrypt; - /** encrypted data incoming */ - protected ByteBuffer inCrypt; - - /** the underlying channel */ - protected SocketChannel socketChannel; - /** used to set interestOP SelectionKey.OP_WRITE for the underlying channel */ - protected SelectionKey selectionKey; - - protected SSLEngine sslEngine; - protected SSLEngineResult readEngineResult; - protected SSLEngineResult writeEngineResult; - - /** - * Should be used to count the buffer allocations. - * But because of #190 where HandshakeStatus.FINISHED is not properly returned by nio wrap/unwrap this variable is used to check whether {@link #createBuffers(SSLSession)} needs to be called. - **/ - protected int bufferallocations = 0; - - public SSLSocketChannel2( SocketChannel channel , SSLEngine sslEngine , ExecutorService exec , SelectionKey key ) throws IOException { - if( channel == null || sslEngine == null || exec == null ) - throw new IllegalArgumentException( "parameter must not be null" ); - - this.socketChannel = channel; - this.sslEngine = sslEngine; - this.exec = exec; - - readEngineResult = writeEngineResult = new SSLEngineResult( Status.BUFFER_UNDERFLOW, sslEngine.getHandshakeStatus(), 0, 0 ); // init to prevent NPEs - - tasks = new ArrayList>( 3 ); - if( key != null ) { - key.interestOps( key.interestOps() | SelectionKey.OP_WRITE ); - this.selectionKey = key; - } - createBuffers( sslEngine.getSession() ); - // kick off handshake - socketChannel.write( wrap( emptybuffer ) );// initializes res - processHandshake(); - } - - private void consumeFutureUninterruptible( Future f ) { - try { - boolean interrupted = false; - while ( true ) { - try { - f.get(); - break; - } catch ( InterruptedException e ) { - interrupted = true; - } - } - if( interrupted ) - Thread.currentThread().interrupt(); - } catch ( ExecutionException e ) { - throw new RuntimeException( e ); - } - } - - /** - * This method will do whatever necessary to process the sslengine handshake. - * Thats why it's called both from the {@link #read(ByteBuffer)} and {@link #write(ByteBuffer)} - **/ - private synchronized void processHandshake() throws IOException { - if( sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING ) - return; // since this may be called either from a reading or a writing thread and because this method is synchronized it is necessary to double check if we are still handshaking. - if( !tasks.isEmpty() ) { - Iterator> it = tasks.iterator(); - while ( it.hasNext() ) { - Future f = it.next(); - if( f.isDone() ) { - it.remove(); - } else { - if( isBlocking() ) - consumeFutureUninterruptible( f ); - return; - } - } - } - - if( sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ) { - if( !isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW ) { - inCrypt.compact(); - int read = socketChannel.read( inCrypt ); - if( read == -1 ) { - throw new IOException( "connection closed unexpectedly by peer" ); - } - inCrypt.flip(); - } - inData.compact(); - unwrap(); - if( readEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) { - createBuffers( sslEngine.getSession() ); - return; - } - } - consumeDelegatedTasks(); - if( tasks.isEmpty() || sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP ) { - socketChannel.write( wrap( emptybuffer ) ); - if( writeEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) { - createBuffers( sslEngine.getSession() ); - return; - } - } - assert ( sslEngine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING );// this function could only leave NOT_HANDSHAKING after createBuffers was called unless #190 occurs which means that nio wrap/unwrap never return HandshakeStatus.FINISHED - - bufferallocations = 1; // look at variable declaration why this line exists and #190. Without this line buffers would not be be recreated when #190 AND a rehandshake occur. - } - private synchronized ByteBuffer wrap( ByteBuffer b ) throws SSLException { - outCrypt.compact(); - writeEngineResult = sslEngine.wrap( b, outCrypt ); - outCrypt.flip(); - return outCrypt; - } - - /** - * performs the unwrap operation by unwrapping from {@link #inCrypt} to {@link #inData} - **/ - private synchronized ByteBuffer unwrap() throws SSLException { - int rem; - do { - rem = inData.remaining(); - readEngineResult = sslEngine.unwrap( inCrypt, inData ); - } while ( readEngineResult.getStatus() == SSLEngineResult.Status.OK && ( rem != inData.remaining() || sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) ); - inData.flip(); - return inData; - } - - protected void consumeDelegatedTasks() { - Runnable task; - while ( ( task = sslEngine.getDelegatedTask() ) != null ) { - tasks.add( exec.submit( task ) ); - // task.run(); - } - } - - protected void createBuffers( SSLSession session ) { - int netBufferMax = session.getPacketBufferSize(); - int appBufferMax = Math.max(session.getApplicationBufferSize(), netBufferMax); - - if( inData == null ) { - inData = ByteBuffer.allocate( appBufferMax ); - outCrypt = ByteBuffer.allocate( netBufferMax ); - inCrypt = ByteBuffer.allocate( netBufferMax ); - } else { - if( inData.capacity() != appBufferMax ) - inData = ByteBuffer.allocate( appBufferMax ); - if( outCrypt.capacity() != netBufferMax ) - outCrypt = ByteBuffer.allocate( netBufferMax ); - if( inCrypt.capacity() != netBufferMax ) - inCrypt = ByteBuffer.allocate( netBufferMax ); - } - inData.rewind(); - inData.flip(); - inCrypt.rewind(); - inCrypt.flip(); - outCrypt.rewind(); - outCrypt.flip(); - bufferallocations++; - } - - public int write( ByteBuffer src ) throws IOException { - if( !isHandShakeComplete() ) { - processHandshake(); - return 0; - } - // assert ( bufferallocations > 1 ); //see #190 - //if( bufferallocations <= 1 ) { - // createBuffers( sslEngine.getSession() ); - //} - int num = socketChannel.write( wrap( src ) ); - if (writeEngineResult.getStatus() == SSLEngineResult.Status.CLOSED) { - throw new EOFException("Connection is closed"); +public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel, ISSLChannel { + + /** + * This object is used to feed the {@link SSLEngine}'s wrap and unwrap methods during the + * handshake phase. + **/ + protected static ByteBuffer emptybuffer = ByteBuffer.allocate(0); + + /** + * Logger instance + * + * @since 1.4.0 + */ + private final Logger log = LoggerFactory.getLogger(SSLSocketChannel2.class); + + protected ExecutorService exec; + + protected List> tasks; + + /** + * raw payload incoming + */ + protected ByteBuffer inData; + /** + * encrypted data outgoing + */ + protected ByteBuffer outCrypt; + /** + * encrypted data incoming + */ + protected ByteBuffer inCrypt; + + /** + * the underlying channel + */ + protected SocketChannel socketChannel; + /** + * used to set interestOP SelectionKey.OP_WRITE for the underlying channel + */ + protected SelectionKey selectionKey; + + protected SSLEngine sslEngine; + protected SSLEngineResult readEngineResult; + protected SSLEngineResult writeEngineResult; + + /** + * Should be used to count the buffer allocations. But because of #190 where + * HandshakeStatus.FINISHED is not properly returned by nio wrap/unwrap this variable is used to + * check whether {@link #createBuffers(SSLSession)} needs to be called. + **/ + protected int bufferallocations = 0; + + public SSLSocketChannel2(SocketChannel channel, SSLEngine sslEngine, ExecutorService exec, + SelectionKey key) throws IOException { + if (channel == null || sslEngine == null || exec == null) { + throw new IllegalArgumentException("parameter must not be null"); + } + + this.socketChannel = channel; + this.sslEngine = sslEngine; + this.exec = exec; + + readEngineResult = writeEngineResult = new SSLEngineResult(Status.BUFFER_UNDERFLOW, + sslEngine.getHandshakeStatus(), 0, 0); // init to prevent NPEs + + tasks = new ArrayList>(3); + if (key != null) { + key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); + this.selectionKey = key; + } + createBuffers(sslEngine.getSession()); + // kick off handshake + socketChannel.write(wrap(emptybuffer));// initializes res + processHandshake(false); + } + + private void consumeFutureUninterruptible(Future f) { + try { + while (true) { + try { + f.get(); + break; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } - return num; - - } - - /** - * Blocks when in blocking mode until at least one byte has been decoded.
- * When not in blocking mode 0 may be returned. - * - * @return the number of bytes read. - **/ - public int read( ByteBuffer dst ) throws IOException { - if( !dst.hasRemaining() ) - return 0; - if( !isHandShakeComplete() ) { - if( isBlocking() ) { - while ( !isHandShakeComplete() ) { - processHandshake(); - } - } else { - processHandshake(); - if( !isHandShakeComplete() ) { - return 0; - } - } - } - // assert ( bufferallocations > 1 ); //see #190 - //if( bufferallocations <= 1 ) { - // createBuffers( sslEngine.getSession() ); - //} - /* 1. When "dst" is smaller than "inData" readRemaining will fill "dst" with data decoded in a previous read call. - * 2. When "inCrypt" contains more data than "inData" has remaining space, unwrap has to be called on more time(readRemaining) - */ - int purged = readRemaining( dst ); - if( purged != 0 ) - return purged; - - /* We only continue when we really need more data from the network. - * Thats the case if inData is empty or inCrypt holds to less data than necessary for decryption - */ - assert ( inData.position() == 0 ); - inData.clear(); - - if( !inCrypt.hasRemaining() ) - inCrypt.clear(); - else - inCrypt.compact(); - - if( isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW ) - if( socketChannel.read( inCrypt ) == -1 ) { - return -1; - } - inCrypt.flip(); - unwrap(); - - int transfered = transfereTo( inData, dst ); - if( transfered == 0 && isBlocking() ) { - return read( dst ); // "transfered" may be 0 when not enough bytes were received or during rehandshaking - } - return transfered; - } - /** - * {@link #read(ByteBuffer)} may not be to leave all buffers(inData, inCrypt) - **/ - private int readRemaining( ByteBuffer dst ) throws SSLException { - if( inData.hasRemaining() ) { - return transfereTo( inData, dst ); - } - if( !inData.hasRemaining() ) - inData.clear(); - // test if some bytes left from last read (e.g. BUFFER_UNDERFLOW) - if( inCrypt.hasRemaining() ) { - unwrap(); - int amount = transfereTo( inData, dst ); - if (readEngineResult.getStatus() == SSLEngineResult.Status.CLOSED) { - return -1; - } - if( amount > 0 ) - return amount; - } - return 0; - } - - public boolean isConnected() { - return socketChannel.isConnected(); - } - - public void close() throws IOException { - sslEngine.closeOutbound(); - sslEngine.getSession().invalidate(); - if( socketChannel.isOpen() ) - socketChannel.write( wrap( emptybuffer ) );// FIXME what if not all bytes can be written - socketChannel.close(); - exec.shutdownNow(); - } - - private boolean isHandShakeComplete() { - HandshakeStatus status = sslEngine.getHandshakeStatus(); - return status == SSLEngineResult.HandshakeStatus.FINISHED || status == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; - } - - public SelectableChannel configureBlocking( boolean b ) throws IOException { - return socketChannel.configureBlocking( b ); - } - - public boolean connect( SocketAddress remote ) throws IOException { - return socketChannel.connect( remote ); - } - - public boolean finishConnect() throws IOException { - return socketChannel.finishConnect(); - } - - public Socket socket() { - return socketChannel.socket(); - } - - public boolean isInboundDone() { - return sslEngine.isInboundDone(); - } - - @Override - public boolean isOpen() { - return socketChannel.isOpen(); - } - - @Override - public boolean isNeedWrite() { - return outCrypt.hasRemaining() || !isHandShakeComplete(); // FIXME this condition can cause high cpu load during handshaking when network is slow - } - - @Override - public void writeMore() throws IOException { - write( outCrypt ); - } - - @Override - public boolean isNeedRead() { - return inData.hasRemaining() || ( inCrypt.hasRemaining() && readEngineResult.getStatus() != Status.BUFFER_UNDERFLOW && readEngineResult.getStatus() != Status.CLOSED ); - } - - @Override - public int readMore( ByteBuffer dst ) throws SSLException { - return readRemaining( dst ); - } - - private int transfereTo( ByteBuffer from, ByteBuffer to ) { - int fremain = from.remaining(); - int toremain = to.remaining(); - if( fremain > toremain ) { - // FIXME there should be a more efficient transfer method - int limit = Math.min( fremain, toremain ); - for( int i = 0 ; i < limit ; i++ ) { - to.put( from.get() ); - } - return limit; - } else { - to.put( from ); - return fremain; - } - - } - - @Override - public boolean isBlocking() { - return socketChannel.isBlocking(); - } - + } + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + /** + * This method will do whatever necessary to process the sslEngine handshake. Thats why it's + * called both from the {@link #read(ByteBuffer)} and {@link #write(ByteBuffer)} + **/ + private synchronized void processHandshake(boolean isReading) throws IOException { + if (sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) { + return; // since this may be called either from a reading or a writing thread and because this method is synchronized it is necessary to double check if we are still handshaking. + } + if (!tasks.isEmpty()) { + Iterator> it = tasks.iterator(); + while (it.hasNext()) { + Future f = it.next(); + if (f.isDone()) { + it.remove(); + } else { + if (isBlocking()) { + consumeFutureUninterruptible(f); + } + return; + } + } + } + + if (isReading && sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) { + if (!isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW) { + inCrypt.compact(); + int read = socketChannel.read(inCrypt); + if (read == -1) { + throw new IOException("connection closed unexpectedly by peer"); + } + inCrypt.flip(); + } + inData.compact(); + unwrap(); + if (readEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED) { + createBuffers(sslEngine.getSession()); + return; + } + } + consumeDelegatedTasks(); + if (tasks.isEmpty() + || sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP) { + socketChannel.write(wrap(emptybuffer)); + if (writeEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED) { + createBuffers(sslEngine.getSession()); + return; + } + } + assert (sslEngine.getHandshakeStatus() + != HandshakeStatus.NOT_HANDSHAKING);// this function could only leave NOT_HANDSHAKING after createBuffers was called unless #190 occurs which means that nio wrap/unwrap never return HandshakeStatus.FINISHED + + bufferallocations = 1; // look at variable declaration why this line exists and #190. Without this line buffers would not be be recreated when #190 AND a rehandshake occur. + } + + private synchronized ByteBuffer wrap(ByteBuffer b) throws SSLException { + outCrypt.compact(); + writeEngineResult = sslEngine.wrap(b, outCrypt); + outCrypt.flip(); + return outCrypt; + } + + /** + * performs the unwrap operation by unwrapping from {@link #inCrypt} to {@link #inData} + **/ + private synchronized ByteBuffer unwrap() throws SSLException { + int rem; + //There are some ssl test suites, which get around the selector.select() call, which cause an infinite unwrap and 100% cpu usage (see #459 and #458) + if (readEngineResult.getStatus() == SSLEngineResult.Status.CLOSED + && sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) { + try { + close(); + } catch (IOException e) { + //Not really interesting + } + } + do { + rem = inData.remaining(); + readEngineResult = sslEngine.unwrap(inCrypt, inData); + } while (readEngineResult.getStatus() == SSLEngineResult.Status.OK && (rem != inData.remaining() + || sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP)); + inData.flip(); + return inData; + } + + protected void consumeDelegatedTasks() { + Runnable task; + while ((task = sslEngine.getDelegatedTask()) != null) { + tasks.add(exec.submit(task)); + // task.run(); + } + } + + protected void createBuffers(SSLSession session) { + saveCryptedData(); // save any remaining data in inCrypt + int netBufferMax = session.getPacketBufferSize(); + int appBufferMax = Math.max(session.getApplicationBufferSize(), netBufferMax); + + if (inData == null) { + inData = ByteBuffer.allocate(appBufferMax); + outCrypt = ByteBuffer.allocate(netBufferMax); + inCrypt = ByteBuffer.allocate(netBufferMax); + } else { + if (inData.capacity() != appBufferMax) { + inData = ByteBuffer.allocate(appBufferMax); + } + if (outCrypt.capacity() != netBufferMax) { + outCrypt = ByteBuffer.allocate(netBufferMax); + } + if (inCrypt.capacity() != netBufferMax) { + inCrypt = ByteBuffer.allocate(netBufferMax); + } + } + if (inData.remaining() != 0 && log.isTraceEnabled()) { + log.trace(new String(inData.array(), inData.position(), inData.remaining())); + } + inData.rewind(); + inData.flip(); + if (inCrypt.remaining() != 0 && log.isTraceEnabled()) { + log.trace(new String(inCrypt.array(), inCrypt.position(), inCrypt.remaining())); + } + inCrypt.rewind(); + inCrypt.flip(); + outCrypt.rewind(); + outCrypt.flip(); + bufferallocations++; + } + + public int write(ByteBuffer src) throws IOException { + if (!isHandShakeComplete()) { + processHandshake(false); + return 0; + } + // assert(bufferallocations > 1); // see #190 + // if(bufferallocations <= 1) { + // createBuffers(sslEngine.getSession()); + // } + int num = socketChannel.write(wrap(src)); + if (writeEngineResult.getStatus() == SSLEngineResult.Status.CLOSED) { + throw new EOFException("Connection is closed"); + } + return num; + + } + + /** + * Blocks when in blocking mode until at least one byte has been decoded.
When not in blocking + * mode 0 may be returned. + * + * @return the number of bytes read. + **/ + public int read(ByteBuffer dst) throws IOException { + tryRestoreCryptedData(); + while (true) { + if (!dst.hasRemaining()) { + return 0; + } + if (!isHandShakeComplete()) { + if (isBlocking()) { + while (!isHandShakeComplete()) { + processHandshake(true); + } + } else { + processHandshake(true); + if (!isHandShakeComplete()) { + return 0; + } + } + } + // assert(bufferallocations > 1); // see #190 + // if (bufferallocations <= 1) { + // createBuffers(sslEngine.getSession()); + // } + + /* 1. When "dst" is smaller than "inData" readRemaining will fill "dst" with data decoded in a previous read call. + * 2. When "inCrypt" contains more data than "inData" has remaining space, unwrap has to be called on more time(readRemaining) + */ + int purged = readRemaining(dst); + if (purged != 0) { + return purged; + } + + /* We only continue when we really need more data from the network. + * Thats the case if inData is empty or inCrypt holds to less data than necessary for decryption + */ + assert (inData.position() == 0); + inData.clear(); + + if (!inCrypt.hasRemaining()) { + inCrypt.clear(); + } else { + inCrypt.compact(); + } + + if (isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW) { + if (socketChannel.read(inCrypt) == -1) { + return -1; + } + } + inCrypt.flip(); + unwrap(); + + int transferred = transfereTo(inData, dst); + if (transferred == 0 && isBlocking()) { + continue; + } + return transferred; + } + } + + /** + * {@link #read(ByteBuffer)} may not be to leave all buffers(inData, inCrypt) + **/ + private int readRemaining(ByteBuffer dst) throws SSLException { + if (inData.hasRemaining()) { + return transfereTo(inData, dst); + } + if (!inData.hasRemaining()) { + inData.clear(); + } + tryRestoreCryptedData(); + // test if some bytes left from last read (e.g. BUFFER_UNDERFLOW) + if (inCrypt.hasRemaining()) { + unwrap(); + int amount = transfereTo(inData, dst); + if (readEngineResult.getStatus() == SSLEngineResult.Status.CLOSED) { + return -1; + } + if (amount > 0) { + return amount; + } + } + return 0; + } + + public boolean isConnected() { + return socketChannel.isConnected(); + } + + public void close() throws IOException { + sslEngine.closeOutbound(); + sslEngine.getSession().invalidate(); + try { + if (socketChannel.isOpen()) { + socketChannel.write(wrap(emptybuffer)); + } + } finally { // in case socketChannel.write produce exception - channel will never close + socketChannel.close(); + } + } + + private boolean isHandShakeComplete() { + HandshakeStatus status = sslEngine.getHandshakeStatus(); + return status == SSLEngineResult.HandshakeStatus.FINISHED + || status == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + } + + public SelectableChannel configureBlocking(boolean b) throws IOException { + return socketChannel.configureBlocking(b); + } + + public boolean connect(SocketAddress remote) throws IOException { + return socketChannel.connect(remote); + } + + public boolean finishConnect() throws IOException { + return socketChannel.finishConnect(); + } + + public Socket socket() { + return socketChannel.socket(); + } + + public boolean isInboundDone() { + return sslEngine.isInboundDone(); + } + + @Override + public boolean isOpen() { + return socketChannel.isOpen(); + } + + @Override + public boolean isNeedWrite() { + return outCrypt.hasRemaining() + || !isHandShakeComplete(); // FIXME this condition can cause high cpu load during handshaking when network is slow + } + + @Override + public void writeMore() throws IOException { + write(outCrypt); + } + + @Override + public boolean isNeedRead() { + return saveCryptData != null || inData.hasRemaining() || (inCrypt.hasRemaining() + && readEngineResult.getStatus() != Status.BUFFER_UNDERFLOW + && readEngineResult.getStatus() != Status.CLOSED); + } + + @Override + public int readMore(ByteBuffer dst) throws SSLException { + return readRemaining(dst); + } + + private int transfereTo(ByteBuffer from, ByteBuffer to) { + int fremain = from.remaining(); + int toremain = to.remaining(); + if (fremain > toremain) { + // FIXME there should be a more efficient transfer method + int limit = Math.min(fremain, toremain); + for (int i = 0; i < limit; i++) { + to.put(from.get()); + } + return limit; + } else { + to.put(from); + return fremain; + } + + } + + @Override + public boolean isBlocking() { + return socketChannel.isBlocking(); + } + + @Override + public SSLEngine getSSLEngine() { + return sslEngine; + } + + + // to avoid complexities with inCrypt, extra unwrapped data after SSL handshake will be saved off in a byte array + // and the inserted back on first read + private byte[] saveCryptData = null; + + private void saveCryptedData() { + // did we find any extra data? + if (inCrypt != null && inCrypt.remaining() > 0) { + int saveCryptSize = inCrypt.remaining(); + saveCryptData = new byte[saveCryptSize]; + inCrypt.get(saveCryptData); + } + } + + private void tryRestoreCryptedData() { + // was there any extra data, then put into inCrypt and clean up + if (saveCryptData != null) { + inCrypt.clear(); + inCrypt.put(saveCryptData); + inCrypt.flip(); + saveCryptData = null; + } + } } \ No newline at end of file diff --git a/src/main/java/org/java_websocket/SocketChannelIOHelper.java b/src/main/java/org/java_websocket/SocketChannelIOHelper.java index 0bd235f79..9a6cfbc51 100644 --- a/src/main/java/org/java_websocket/SocketChannelIOHelper.java +++ b/src/main/java/org/java_websocket/SocketChannelIOHelper.java @@ -1,71 +1,116 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; -import java.nio.channels.spi.AbstractSelectableChannel; - -import org.java_websocket.WebSocket.Role; +import org.java_websocket.enums.Role; public class SocketChannelIOHelper { - public static boolean read( final ByteBuffer buf, WebSocketImpl ws, ByteChannel channel ) throws IOException { - buf.clear(); - int read = channel.read( buf ); - buf.flip(); + private SocketChannelIOHelper() { + throw new IllegalStateException("Utility class"); + } + + public static boolean read(final ByteBuffer buf, WebSocketImpl ws, ByteChannel channel) + throws IOException { + buf.clear(); + int read = channel.read(buf); + buf.flip(); - if( read == -1 ) { - ws.eot(); - return false; - } - return read != 0; - } + if (read == -1) { + ws.eot(); + return false; + } + return read != 0; + } - /** - * @see WrappedByteChannel#readMore(ByteBuffer) - * @return returns whether there is more data left which can be obtained via {@link #readMore(ByteBuffer, WebSocketImpl, WrappedByteChannel)} - **/ - public static boolean readMore( final ByteBuffer buf, WebSocketImpl ws, WrappedByteChannel channel ) throws IOException { - buf.clear(); - int read = channel.readMore( buf ); - buf.flip(); + /** + * @param buf The ByteBuffer to read from + * @param ws The WebSocketImpl associated with the channels + * @param channel The channel to read from + * @return returns Whether there is more data left which can be obtained via {@link + * WrappedByteChannel#readMore(ByteBuffer)} + * @throws IOException May be thrown by {@link WrappedByteChannel#readMore(ByteBuffer)}# + * @see WrappedByteChannel#readMore(ByteBuffer) + **/ + public static boolean readMore(final ByteBuffer buf, WebSocketImpl ws, WrappedByteChannel channel) + throws IOException { + buf.clear(); + int read = channel.readMore(buf); + buf.flip(); - if( read == -1 ) { - ws.eot(); - return false; - } - return channel.isNeedRead(); - } + if (read == -1) { + ws.eot(); + return false; + } + return channel.isNeedRead(); + } - /** Returns whether the whole outQueue has been flushed */ - public static boolean batch( WebSocketImpl ws, ByteChannel sockchannel ) throws IOException { - ByteBuffer buffer = ws.outQueue.peek(); - WrappedByteChannel c = null; + /** + * Returns whether the whole outQueue has been flushed + * + * @param ws The WebSocketImpl associated with the channels + * @param sockchannel The channel to write to + * @return returns Whether there is more data to write + * @throws IOException May be thrown by {@link WrappedByteChannel#writeMore()} + */ + public static boolean batch(WebSocketImpl ws, ByteChannel sockchannel) throws IOException { + if (ws == null) { + return false; + } + ByteBuffer buffer = ws.outQueue.peek(); + WrappedByteChannel c = null; - if( buffer == null ) { - if( sockchannel instanceof WrappedByteChannel ) { - c = (WrappedByteChannel) sockchannel; - if( c.isNeedWrite() ) { - c.writeMore(); - } - } - } else { - do {// FIXME writing as much as possible is unfair!! - /*int written = */sockchannel.write( buffer ); - if( buffer.remaining() > 0 ) { - return false; - } else { - ws.outQueue.poll(); // Buffer finished. Remove it. - buffer = ws.outQueue.peek(); - } - } while ( buffer != null ); - } + if (buffer == null) { + if (sockchannel instanceof WrappedByteChannel) { + c = (WrappedByteChannel) sockchannel; + if (c.isNeedWrite()) { + c.writeMore(); + } + } + } else { + do { + // FIXME writing as much as possible is unfair!! + /*int written = */ + sockchannel.write(buffer); + if (buffer.remaining() > 0) { + return false; + } else { + ws.outQueue.poll(); // Buffer finished. Remove it. + buffer = ws.outQueue.peek(); + } + } while (buffer != null); + } - if( ws != null && ws.outQueue.isEmpty() && ws.isFlushAndClose() && ws.getDraft() != null && ws.getDraft().getRole() != null && ws.getDraft().getRole() == Role.SERVER ) {// - synchronized ( ws ) { - ws.closeConnection(); - } - } - return c != null ? !( (WrappedByteChannel) sockchannel ).isNeedWrite() : true; - } + if (ws.outQueue.isEmpty() && ws.isFlushAndClose() && ws.getDraft() != null + && ws.getDraft().getRole() != null && ws.getDraft().getRole() == Role.SERVER) { + ws.closeConnection(); + } + return c == null || !((WrappedByteChannel) sockchannel).isNeedWrite(); + } } diff --git a/src/main/java/org/java_websocket/WebSocket.java b/src/main/java/org/java_websocket/WebSocket.java index d211d63a2..d515f631b 100644 --- a/src/main/java/org/java_websocket/WebSocket.java +++ b/src/main/java/org/java_websocket/WebSocket.java @@ -1,124 +1,251 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket; import java.net.InetSocketAddress; import java.nio.ByteBuffer; -import java.nio.channels.NotYetConnectedException; - +import java.util.Collection; +import javax.net.ssl.SSLSession; import org.java_websocket.drafts.Draft; +import org.java_websocket.enums.Opcode; +import org.java_websocket.enums.ReadyState; +import org.java_websocket.exceptions.WebsocketNotConnectedException; import org.java_websocket.framing.Framedata; -import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.protocols.IProtocol; public interface WebSocket { - public enum Role { - CLIENT, SERVER - } - - public enum READYSTATE { - NOT_YET_CONNECTED, CONNECTING, OPEN, CLOSING, CLOSED; - } - - /** - * The default port of WebSockets, as defined in the spec. If the nullary - * constructor is used, DEFAULT_PORT will be the port the WebSocketServer - * is binded to. Note that ports under 1024 usually require root permissions. - */ - public static final int DEFAULT_PORT = 80; - - public static final int DEFAULT_WSS_PORT = 443; - - /** - * sends the closing handshake. - * may be send in response to an other handshake. - */ - public void close( int code, String message ); - - public void close( int code ); - - /** Convenience function which behaves like close(CloseFrame.NORMAL) */ - public void close(); - - /** - * This will close the connection immediately without a proper close handshake. - * The code and the message therefore won't be transfered over the wire also they will be forwarded to onClose/onWebsocketClose. - **/ - public abstract void closeConnection( int code, String message ); - - /** - * Send Text data to the other end. - * - * @throws IllegalArgumentException - * @throws NotYetConnectedException - */ - public abstract void send( String text ) throws NotYetConnectedException; - - /** - * Send Binary data (plain bytes) to the other end. - * - * @throws IllegalArgumentException - * @throws NotYetConnectedException - */ - public abstract void send( ByteBuffer bytes ) throws IllegalArgumentException , NotYetConnectedException; - - public abstract void send( byte[] bytes ) throws IllegalArgumentException , NotYetConnectedException; - - public abstract void sendFrame( Framedata framedata ); - - /** - * Allows to send continuous/fragmented frames conveniently.
- * For more into on this frame type see http://tools.ietf.org/html/rfc6455#section-5.4
- * - * If the first frame you send is also the last then it is not a fragmented frame and will received via onMessage instead of onFragmented even though it was send by this method. - * - * @param op - * This is only important for the first frame in the sequence. Opcode.TEXT, Opcode.BINARY are allowed. - * @param buffer - * The buffer which contains the payload. It may have no bytes remaining. - * @param fin - * true means the current frame is the last in the sequence. - **/ - public abstract void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ); - - public abstract boolean hasBufferedData(); - - /** - * @return never returns null - */ - public abstract InetSocketAddress getRemoteSocketAddress(); - - /** - * @return never returns null - */ - public abstract InetSocketAddress getLocalSocketAddress(); - - public abstract boolean isConnecting(); - - public abstract boolean isOpen(); - - public abstract boolean isClosing(); - - /** - * Returns true when no further frames may be submitted
- * This happens before the socket connection is closed. - */ - public abstract boolean isFlushAndClose(); - - /** Returns whether the close handshake has been completed and the socket is closed. */ - public abstract boolean isClosed(); - - public abstract Draft getDraft(); - - /** - * Retrieve the WebSocket 'readyState'. - * This represents the state of the connection. - * It returns a numerical value, as per W3C WebSockets specs. - * - * @return Returns '0 = CONNECTING', '1 = OPEN', '2 = CLOSING' or '3 = CLOSED' - */ - public abstract READYSTATE getReadyState(); - - /** - * Returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2
- * If the opening handshake has not yet happened it will return null. - **/ - public abstract String getResourceDescriptor(); + + /** + * sends the closing handshake. may be send in response to an other handshake. + * + * @param code the closing code + * @param message the closing message + */ + void close(int code, String message); + + /** + * sends the closing handshake. may be send in response to an other handshake. + * + * @param code the closing code + */ + void close(int code); + + /** + * Convenience function which behaves like close(CloseFrame.NORMAL) + */ + void close(); + + /** + * This will close the connection immediately without a proper close handshake. The code and the + * message therefore won't be transferred over the wire also they will be forwarded to + * onClose/onWebsocketClose. + * + * @param code the closing code + * @param message the closing message + **/ + void closeConnection(int code, String message); + + /** + * Send Text data to the other end. + * + * @param text the text data to send + * @throws WebsocketNotConnectedException websocket is not yet connected + */ + void send(String text); + + /** + * Send Binary data (plain bytes) to the other end. + * + * @param bytes the binary data to send + * @throws IllegalArgumentException the data is null + * @throws WebsocketNotConnectedException websocket is not yet connected + */ + void send(ByteBuffer bytes); + + /** + * Send Binary data (plain bytes) to the other end. + * + * @param bytes the byte array to send + * @throws IllegalArgumentException the data is null + * @throws WebsocketNotConnectedException websocket is not yet connected + */ + void send(byte[] bytes); + + /** + * Send a frame to the other end + * + * @param framedata the frame to send to the other end + */ + void sendFrame(Framedata framedata); + + /** + * Send a collection of frames to the other end + * + * @param frames the frames to send to the other end + */ + void sendFrame(Collection frames); + + /** + * Send a ping to the other end + * + * @throws WebsocketNotConnectedException websocket is not yet connected + */ + void sendPing(); + + /** + * Allows to send continuous/fragmented frames conveniently.
For more into on this frame type + * see http://tools.ietf.org/html/rfc6455#section-5.4
+ *

+ * If the first frame you send is also the last then it is not a fragmented frame and will + * received via onMessage instead of onFragmented even though it was send by this method. + * + * @param op This is only important for the first frame in the sequence. Opcode.TEXT, + * Opcode.BINARY are allowed. + * @param buffer The buffer which contains the payload. It may have no bytes remaining. + * @param fin true means the current frame is the last in the sequence. + **/ + void sendFragmentedFrame(Opcode op, ByteBuffer buffer, boolean fin); + + /** + * Checks if the websocket has buffered data + * + * @return has the websocket buffered data + */ + boolean hasBufferedData(); + + /** + * Returns the address of the endpoint this socket is connected to, or {@code null} if it is + * unconnected. + * + * @return the remote socket address or null, if this socket is unconnected + */ + InetSocketAddress getRemoteSocketAddress(); + + /** + * Returns the address of the endpoint this socket is bound to, or {@code null} if it is not + * bound. + * + * @return the local socket address or null, if this socket is not bound + */ + InetSocketAddress getLocalSocketAddress(); + + /** + * Is the websocket in the state OPEN + * + * @return state equals ReadyState.OPEN + */ + boolean isOpen(); + + /** + * Is the websocket in the state CLOSING + * + * @return state equals ReadyState.CLOSING + */ + boolean isClosing(); + + /** + * Returns true when no further frames may be submitted
This happens before the socket + * connection is closed. + * + * @return true when no further frames may be submitted + */ + boolean isFlushAndClose(); + + /** + * Is the websocket in the state CLOSED + * + * @return state equals ReadyState.CLOSED + */ + boolean isClosed(); + + /** + * Getter for the draft + * + * @return the used draft + */ + Draft getDraft(); + + /** + * Retrieve the WebSocket 'ReadyState'. This represents the state of the connection. It returns a + * numerical value, as per W3C WebSockets specs. + * + * @return Returns '0 = CONNECTING', '1 = OPEN', '2 = CLOSING' or '3 = CLOSED' + */ + ReadyState getReadyState(); + + /** + * Returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2
+ * If the opening handshake has not yet happened it will return null. + * + * @return Returns the decoded path component of this URI. + **/ + String getResourceDescriptor(); + + /** + * Setter for an attachment on the socket connection. The attachment may be of any type. + * + * @param attachment The object to be attached to the user + * @param The type of the attachment + * @since 1.3.7 + **/ + void setAttachment(T attachment); + + /** + * Getter for the connection attachment. + * + * @param The type of the attachment + * @return Returns the user attachment + * @since 1.3.7 + **/ + T getAttachment(); + + /** + * Does this websocket use an encrypted (wss/ssl) or unencrypted (ws) connection + * + * @return true, if the websocket does use wss and therefore has a SSLSession + * @since 1.4.1 + */ + boolean hasSSLSupport(); + + /** + * Returns the ssl session of websocket, if ssl/wss is used for this instance. + * + * @return the ssl session of this websocket instance + * @throws IllegalArgumentException the underlying channel does not use ssl (use hasSSLSupport() + * to check) + * @since 1.4.1 + */ + SSLSession getSSLSession() throws IllegalArgumentException; + + /** + * Returns the used Sec-WebSocket-Protocol for this websocket connection + * + * @return the Sec-WebSocket-Protocol or null, if no draft available + * @throws IllegalArgumentException the underlying draft does not support a Sec-WebSocket-Protocol + * @since 1.5.2 + */ + IProtocol getProtocol(); } \ No newline at end of file diff --git a/src/main/java/org/java_websocket/WebSocketAdapter.java b/src/main/java/org/java_websocket/WebSocketAdapter.java index 3b16674e7..d06ca6f91 100644 --- a/src/main/java/org/java_websocket/WebSocketAdapter.java +++ b/src/main/java/org/java_websocket/WebSocketAdapter.java @@ -1,104 +1,113 @@ -package org.java_websocket; +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ -import java.net.InetSocketAddress; +package org.java_websocket; import org.java_websocket.drafts.Draft; import org.java_websocket.exceptions.InvalidDataException; -import org.java_websocket.exceptions.InvalidHandshakeException; import org.java_websocket.framing.Framedata; -import org.java_websocket.framing.Framedata.Opcode; -import org.java_websocket.framing.FramedataImpl1; +import org.java_websocket.framing.PingFrame; +import org.java_websocket.framing.PongFrame; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.handshake.HandshakeImpl1Server; import org.java_websocket.handshake.ServerHandshake; import org.java_websocket.handshake.ServerHandshakeBuilder; /** - * This class default implements all methods of the WebSocketListener that can be overridden optionally when advances functionalities is needed.
+ * This class default implements all methods of the WebSocketListener that can be overridden + * optionally when advances functionalities is needed.
**/ public abstract class WebSocketAdapter implements WebSocketListener { - /** - * This default implementation does not do anything. Go ahead and overwrite it. - * - * @see org.java_websocket.WebSocketListener#onWebsocketHandshakeReceivedAsServer(WebSocket, Draft, ClientHandshake) - */ - @Override - public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException { - return new HandshakeImpl1Server(); - } - - @Override - public void onWebsocketHandshakeReceivedAsClient( WebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException { - } + private PingFrame pingFrame; - /** - * This default implementation does not do anything which will cause the connections to always progress. - * - * @see org.java_websocket.WebSocketListener#onWebsocketHandshakeSentAsClient(WebSocket, ClientHandshake) - */ - @Override - public void onWebsocketHandshakeSentAsClient( WebSocket conn, ClientHandshake request ) throws InvalidDataException { - } + /** + * This default implementation does not do anything. Go ahead and overwrite it. + * + * @see org.java_websocket.WebSocketListener#onWebsocketHandshakeReceivedAsServer(WebSocket, + * Draft, ClientHandshake) + */ + @Override + public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer(WebSocket conn, Draft draft, + ClientHandshake request) throws InvalidDataException { + return new HandshakeImpl1Server(); + } - /** - * This default implementation does not do anything. Go ahead and overwrite it - * - * @see org.java_websocket.WebSocketListener#onWebsocketMessageFragment(WebSocket, Framedata) - */ - @Override - public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { - } + @Override + public void onWebsocketHandshakeReceivedAsClient(WebSocket conn, ClientHandshake request, + ServerHandshake response) throws InvalidDataException { + //To overwrite + } - /** - * This default implementation will send a pong in response to the received ping. - * The pong frame will have the same payload as the ping frame. - * - * @see org.java_websocket.WebSocketListener#onWebsocketPing(WebSocket, Framedata) - */ - @Override - public void onWebsocketPing( WebSocket conn, Framedata f ) { - FramedataImpl1 resp = new FramedataImpl1( f ); - resp.setOptcode( Opcode.PONG ); - conn.sendFrame( resp ); - } + /** + * This default implementation does not do anything which will cause the connections to always + * progress. + * + * @see org.java_websocket.WebSocketListener#onWebsocketHandshakeSentAsClient(WebSocket, + * ClientHandshake) + */ + @Override + public void onWebsocketHandshakeSentAsClient(WebSocket conn, ClientHandshake request) + throws InvalidDataException { + //To overwrite + } - /** - * This default implementation does not do anything. Go ahead and overwrite it. - * - * @see org.java_websocket.WebSocketListener#onWebsocketPong(WebSocket, Framedata) - */ - @Override - public void onWebsocketPong( WebSocket conn, Framedata f ) { - } + /** + * This default implementation will send a pong in response to the received ping. The pong frame + * will have the same payload as the ping frame. + * + * @see org.java_websocket.WebSocketListener#onWebsocketPing(WebSocket, Framedata) + */ + @Override + public void onWebsocketPing(WebSocket conn, Framedata f) { + conn.sendFrame(new PongFrame((PingFrame) f)); + } - /** - * Gets the XML string that should be returned if a client requests a Flash - * security policy. - * - * The default implementation allows access from all remote domains, but - * only on the port that this WebSocketServer is listening on. - * - * This is specifically implemented for gitime's WebSocket client for Flash: - * http://github.com/gimite/web-socket-js - * - * @return An XML String that comforts to Flash's security policy. You MUST - * not include the null char at the end, it is appended automatically. - * @throws InvalidDataException thrown when some data that is required to generate the flash-policy like the websocket local port could not be obtained e.g because the websocket is not connected. - */ - @Override - public String getFlashPolicy( WebSocket conn ) throws InvalidDataException { - InetSocketAddress adr = conn.getLocalSocketAddress(); - if(null == adr){ - throw new InvalidHandshakeException( "socket not bound" ); - } - - StringBuffer sb = new StringBuffer( 90 ); - sb.append( "\0" ); - - return sb.toString(); - } + /** + * This default implementation does not do anything. Go ahead and overwrite it. + * + * @see org.java_websocket.WebSocketListener#onWebsocketPong(WebSocket, Framedata) + */ + @Override + public void onWebsocketPong(WebSocket conn, Framedata f) { + //To overwrite + } + /** + * Default implementation for onPreparePing, returns a (cached) PingFrame that has no application + * data. + * + * @param conn The WebSocket connection from which the ping frame will be sent. + * @return PingFrame to be sent. + * @see org.java_websocket.WebSocketListener#onPreparePing(WebSocket) + */ + @Override + public PingFrame onPreparePing(WebSocket conn) { + if (pingFrame == null) { + pingFrame = new PingFrame(); + } + return pingFrame; + } } diff --git a/src/main/java/org/java_websocket/WebSocketFactory.java b/src/main/java/org/java_websocket/WebSocketFactory.java index 651e97430..eed6e5ad3 100644 --- a/src/main/java/org/java_websocket/WebSocketFactory.java +++ b/src/main/java/org/java_websocket/WebSocketFactory.java @@ -1,12 +1,51 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket; -import java.net.Socket; import java.util.List; - import org.java_websocket.drafts.Draft; public interface WebSocketFactory { - public WebSocket createWebSocket( WebSocketAdapter a, Draft d, Socket s ); - public WebSocket createWebSocket( WebSocketAdapter a, List drafts, Socket s ); + + /** + * Create a new Websocket with the provided listener, drafts and socket + * + * @param a The Listener for the WebsocketImpl + * @param d The draft which should be used + * @return A WebsocketImpl + */ + WebSocket createWebSocket(WebSocketAdapter a, Draft d); + + /** + * Create a new Websocket with the provided listener, drafts and socket + * + * @param a The Listener for the WebsocketImpl + * @param drafts The drafts which should be used + * @return A WebsocketImpl + */ + WebSocket createWebSocket(WebSocketAdapter a, List drafts); } diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index 949fb8c8f..3289aefcf 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -1,737 +1,915 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket; import java.io.IOException; import java.net.InetSocketAddress; -import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; -import java.nio.channels.NotYetConnectedException; import java.nio.channels.SelectionKey; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; - +import javax.net.ssl.SSLSession; import org.java_websocket.drafts.Draft; -import org.java_websocket.drafts.Draft.CloseHandshakeType; -import org.java_websocket.drafts.Draft.HandshakeState; -import org.java_websocket.drafts.Draft_10; -import org.java_websocket.drafts.Draft_17; -import org.java_websocket.drafts.Draft_75; -import org.java_websocket.drafts.Draft_76; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.enums.CloseHandshakeType; +import org.java_websocket.enums.HandshakeState; +import org.java_websocket.enums.Opcode; +import org.java_websocket.enums.ReadyState; +import org.java_websocket.enums.Role; import org.java_websocket.exceptions.IncompleteHandshakeException; import org.java_websocket.exceptions.InvalidDataException; import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.exceptions.LimitExceededException; import org.java_websocket.exceptions.WebsocketNotConnectedException; import org.java_websocket.framing.CloseFrame; -import org.java_websocket.framing.CloseFrameBuilder; import org.java_websocket.framing.Framedata; -import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.framing.PingFrame; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.handshake.ClientHandshakeBuilder; import org.java_websocket.handshake.Handshakedata; import org.java_websocket.handshake.ServerHandshake; import org.java_websocket.handshake.ServerHandshakeBuilder; +import org.java_websocket.interfaces.ISSLChannel; +import org.java_websocket.protocols.IProtocol; import org.java_websocket.server.WebSocketServer.WebSocketWorker; import org.java_websocket.util.Charsetfunctions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * Represents one end (client or server) of a single WebSocketImpl connection. - * Takes care of the "handshake" phase, then allows for easy sending of - * text frames, and receiving frames through an event-based model. - * + * Represents one end (client or server) of a single WebSocketImpl connection. Takes care of the + * "handshake" phase, then allows for easy sending of text frames, and receiving frames through an + * event-based model. */ public class WebSocketImpl implements WebSocket { - public static int RCVBUF = 16384; - - public static/*final*/boolean DEBUG = false; // must be final in the future in order to take advantage of VM optimization - - public static final List defaultdraftlist = new ArrayList( 4 ); - static { - defaultdraftlist.add( new Draft_17() ); - defaultdraftlist.add( new Draft_10() ); - defaultdraftlist.add( new Draft_76() ); - defaultdraftlist.add( new Draft_75() ); - } - - public SelectionKey key; - - /** the possibly wrapped channel object whose selection is controlled by {@link #key} */ - public ByteChannel channel; - /** - * Queue of buffers that need to be sent to the client. - */ - public final BlockingQueue outQueue; - /** - * Queue of buffers that need to be processed - */ - public final BlockingQueue inQueue; - - /** - * Helper variable meant to store the thread which ( exclusively ) triggers this objects decode method. - **/ - public volatile WebSocketWorker workerThread; // TODO reset worker? - - /** When true no further frames may be submitted to be sent */ - private volatile boolean flushandclosestate = false; - - private READYSTATE readystate = READYSTATE.NOT_YET_CONNECTED; - - /** - * The listener to notify of WebSocket events. - */ - private final WebSocketListener wsl; - - private List knownDrafts; - - private Draft draft = null; - - private Role role; - - private Opcode current_continuous_frame_opcode = null; - - /** the bytes of an incomplete received handshake */ - private ByteBuffer tmpHandshakeBytes = ByteBuffer.allocate( 0 ); - - /** stores the handshake sent by this websocket ( Role.CLIENT only ) */ - private ClientHandshake handshakerequest = null; - - private String closemessage = null; - private Integer closecode = null; - private Boolean closedremotely = null; - - private String resourceDescriptor = null; - - /** - * crates a websocket with server role - */ - public WebSocketImpl( WebSocketListener listener , List drafts ) { - this( listener, (Draft) null ); - this.role = Role.SERVER; - // draft.copyInstance will be called when the draft is first needed - if( drafts == null || drafts.isEmpty() ) { - knownDrafts = defaultdraftlist; - } else { - knownDrafts = drafts; - } - } - - /** - * crates a websocket with client role - * - * @param listener - * may be unbound - */ - public WebSocketImpl( WebSocketListener listener , Draft draft ) { - if( listener == null || ( draft == null && role == Role.SERVER ) )// socket can be null because we want do be able to create the object without already having a bound channel - throw new IllegalArgumentException( "parameters must not be null" ); - this.outQueue = new LinkedBlockingQueue(); - inQueue = new LinkedBlockingQueue(); - this.wsl = listener; - this.role = Role.CLIENT; - if( draft != null ) - this.draft = draft.copyInstance(); - } - - @Deprecated - public WebSocketImpl( WebSocketListener listener , Draft draft , Socket socket ) { - this( listener, draft ); - } - - @Deprecated - public WebSocketImpl( WebSocketListener listener , List drafts , Socket socket ) { - this( listener, drafts ); - } - - /** - * - */ - public void decode( ByteBuffer socketBuffer ) { - assert ( socketBuffer.hasRemaining() ); - - if( DEBUG ) - System.out.println( "process(" + socketBuffer.remaining() + "): {" + ( socketBuffer.remaining() > 1000 ? "too big to display" : new String( socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining() ) ) + "}" ); - - if( readystate != READYSTATE.NOT_YET_CONNECTED ) { - decodeFrames( socketBuffer );; - } else { - if( decodeHandshake( socketBuffer ) ) { - assert ( tmpHandshakeBytes.hasRemaining() != socketBuffer.hasRemaining() || !socketBuffer.hasRemaining() ); // the buffers will never have remaining bytes at the same time - - if( socketBuffer.hasRemaining() ) { - decodeFrames( socketBuffer ); - } else if( tmpHandshakeBytes.hasRemaining() ) { - decodeFrames( tmpHandshakeBytes ); - } - } - } - assert ( isClosing() || isFlushAndClose() || !socketBuffer.hasRemaining() ); - } - /** - * Returns whether the handshake phase has is completed. - * In case of a broken handshake this will be never the case. - **/ - private boolean decodeHandshake( ByteBuffer socketBufferNew ) { - ByteBuffer socketBuffer; - if( tmpHandshakeBytes.capacity() == 0 ) { - socketBuffer = socketBufferNew; - } else { - if( tmpHandshakeBytes.remaining() < socketBufferNew.remaining() ) { - ByteBuffer buf = ByteBuffer.allocate( tmpHandshakeBytes.capacity() + socketBufferNew.remaining() ); - tmpHandshakeBytes.flip(); - buf.put( tmpHandshakeBytes ); - tmpHandshakeBytes = buf; - } - - tmpHandshakeBytes.put( socketBufferNew ); - tmpHandshakeBytes.flip(); - socketBuffer = tmpHandshakeBytes; - } - socketBuffer.mark(); - try { - if( draft == null ) { - HandshakeState isflashedgecase = isFlashEdgeCase( socketBuffer ); - if( isflashedgecase == HandshakeState.MATCHED ) { - try { - write( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( wsl.getFlashPolicy( this ) ) ) ); - close( CloseFrame.FLASHPOLICY, "" ); - } catch ( InvalidDataException e ) { - close( CloseFrame.ABNORMAL_CLOSE, "remote peer closed connection before flashpolicy could be transmitted", true ); - } - return false; - } - } - HandshakeState handshakestate = null; - - try { - if( role == Role.SERVER ) { - if( draft == null ) { - for( Draft d : knownDrafts ) { - d = d.copyInstance(); - try { - d.setParseMode( role ); - socketBuffer.reset(); - Handshakedata tmphandshake = d.translateHandshake( socketBuffer ); - if( tmphandshake instanceof ClientHandshake == false ) { - flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false ); - return false; - } - ClientHandshake handshake = (ClientHandshake) tmphandshake; - handshakestate = d.acceptHandshakeAsServer( handshake ); - if( handshakestate == HandshakeState.MATCHED ) { - resourceDescriptor = handshake.getResourceDescriptor(); - ServerHandshakeBuilder response; - try { - response = wsl.onWebsocketHandshakeReceivedAsServer( this, d, handshake ); - } catch ( InvalidDataException e ) { - flushAndClose( e.getCloseCode(), e.getMessage(), false ); - return false; - } catch ( RuntimeException e ) { - wsl.onWebsocketError( this, e ); - flushAndClose( CloseFrame.NEVER_CONNECTED, e.getMessage(), false ); - return false; - } - write( d.createHandshake( d.postProcessHandshakeResponseAsServer( handshake, response ), role ) ); - draft = d; - open( handshake ); - return true; - } - } catch ( InvalidHandshakeException e ) { - // go on with an other draft - } - } - if( draft == null ) { - close( CloseFrame.PROTOCOL_ERROR, "no draft matches" ); - } - return false; - } else { - // special case for multiple step handshakes - Handshakedata tmphandshake = draft.translateHandshake( socketBuffer ); - if( tmphandshake instanceof ClientHandshake == false ) { - flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false ); - return false; - } - ClientHandshake handshake = (ClientHandshake) tmphandshake; - handshakestate = draft.acceptHandshakeAsServer( handshake ); - - if( handshakestate == HandshakeState.MATCHED ) { - open( handshake ); - return true; - } else { - close( CloseFrame.PROTOCOL_ERROR, "the handshake did finaly not match" ); - } - return false; - } - } else if( role == Role.CLIENT ) { - draft.setParseMode( role ); - Handshakedata tmphandshake = draft.translateHandshake( socketBuffer ); - if( tmphandshake instanceof ServerHandshake == false ) { - flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false ); - return false; - } - ServerHandshake handshake = (ServerHandshake) tmphandshake; - handshakestate = draft.acceptHandshakeAsClient( handshakerequest, handshake ); - if( handshakestate == HandshakeState.MATCHED ) { - try { - wsl.onWebsocketHandshakeReceivedAsClient( this, handshakerequest, handshake ); - } catch ( InvalidDataException e ) { - flushAndClose( e.getCloseCode(), e.getMessage(), false ); - return false; - } catch ( RuntimeException e ) { - wsl.onWebsocketError( this, e ); - flushAndClose( CloseFrame.NEVER_CONNECTED, e.getMessage(), false ); - return false; - } - open( handshake ); - return true; - } else { - close( CloseFrame.PROTOCOL_ERROR, "draft " + draft + " refuses handshake" ); - } - } - } catch ( InvalidHandshakeException e ) { - close( e ); - } - } catch ( IncompleteHandshakeException e ) { - if( tmpHandshakeBytes.capacity() == 0 ) { - socketBuffer.reset(); - int newsize = e.getPreferedSize(); - if( newsize == 0 ) { - newsize = socketBuffer.capacity() + 16; - } else { - assert ( e.getPreferedSize() >= socketBuffer.remaining() ); - } - tmpHandshakeBytes = ByteBuffer.allocate( newsize ); - - tmpHandshakeBytes.put( socketBufferNew ); - // tmpHandshakeBytes.flip(); - } else { - tmpHandshakeBytes.position( tmpHandshakeBytes.limit() ); - tmpHandshakeBytes.limit( tmpHandshakeBytes.capacity() ); - } - } - return false; - } - - private void decodeFrames( ByteBuffer socketBuffer ) { - - List frames; - try { - frames = draft.translateFrame( socketBuffer ); - for( Framedata f : frames ) { - if( DEBUG ) - System.out.println( "matched frame: " + f ); - Opcode curop = f.getOpcode(); - boolean fin = f.isFin(); - - if( curop == Opcode.CLOSING ) { - int code = CloseFrame.NOCODE; - String reason = ""; - if( f instanceof CloseFrame ) { - CloseFrame cf = (CloseFrame) f; - code = cf.getCloseCode(); - reason = cf.getMessage(); - } - if( readystate == READYSTATE.CLOSING ) { - // complete the close handshake by disconnecting - closeConnection( code, reason, true ); - } else { - // echo close handshake - if( draft.getCloseHandshakeType() == CloseHandshakeType.TWOWAY ) - close( code, reason, true ); - else - flushAndClose( code, reason, false ); - } - continue; - } else if( curop == Opcode.PING ) { - wsl.onWebsocketPing( this, f ); - continue; - } else if( curop == Opcode.PONG ) { - wsl.onWebsocketPong( this, f ); - continue; - } else if( !fin || curop == Opcode.CONTINUOUS ) { - if( curop != Opcode.CONTINUOUS ) { - if( current_continuous_frame_opcode != null ) - throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Previous continuous frame sequence not completed." ); - current_continuous_frame_opcode = curop; - } else if( fin ) { - if( current_continuous_frame_opcode == null ) - throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." ); - current_continuous_frame_opcode = null; - } else if( current_continuous_frame_opcode == null ) { - throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." ); - } - try { - wsl.onWebsocketMessageFragment( this, f ); - } catch ( RuntimeException e ) { - wsl.onWebsocketError( this, e ); - } - - } else if( current_continuous_frame_opcode != null ) { - throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence not completed." ); - } else if( curop == Opcode.TEXT ) { - try { - wsl.onWebsocketMessage( this, Charsetfunctions.stringUtf8( f.getPayloadData() ) ); - } catch ( RuntimeException e ) { - wsl.onWebsocketError( this, e ); - } - } else if( curop == Opcode.BINARY ) { - try { - wsl.onWebsocketMessage( this, f.getPayloadData() ); - } catch ( RuntimeException e ) { - wsl.onWebsocketError( this, e ); - } - } else { - throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "non control or continious frame expected" ); - } - } - } catch ( InvalidDataException e1 ) { - wsl.onWebsocketError( this, e1 ); - close( e1 ); - return; - } - } - - private void close( int code, String message, boolean remote ) { - if( readystate != READYSTATE.CLOSING && readystate != READYSTATE.CLOSED ) { - if( readystate == READYSTATE.OPEN ) { - if( code == CloseFrame.ABNORMAL_CLOSE ) { - assert ( remote == false ); - readystate = READYSTATE.CLOSING; - flushAndClose( code, message, false ); - return; - } - if( draft.getCloseHandshakeType() != CloseHandshakeType.NONE ) { - try { - if( !remote ) { - try { - wsl.onWebsocketCloseInitiated( this, code, message ); - } catch ( RuntimeException e ) { - wsl.onWebsocketError( this, e ); - } - } - sendFrame( new CloseFrameBuilder( code, message ) ); - } catch ( InvalidDataException e ) { - wsl.onWebsocketError( this, e ); - flushAndClose( CloseFrame.ABNORMAL_CLOSE, "generated frame is invalid", false ); - } - } - flushAndClose( code, message, remote ); - } else if( code == CloseFrame.FLASHPOLICY ) { - assert ( remote ); - flushAndClose( CloseFrame.FLASHPOLICY, message, true ); - } else { - flushAndClose( CloseFrame.NEVER_CONNECTED, message, false ); - } - if( code == CloseFrame.PROTOCOL_ERROR )// this endpoint found a PROTOCOL_ERROR - flushAndClose( code, message, remote ); - readystate = READYSTATE.CLOSING; - tmpHandshakeBytes = null; - return; - } - } - - @Override - public void close( int code, String message ) { - close( code, message, false ); - } - - /** - * - * @param remote - * Indicates who "generated" code.
- * true means that this endpoint received the code from the other endpoint.
- * false means this endpoint decided to send the given code,
- * remote may also be true if this endpoint started the closing handshake since the other endpoint may not simply echo the code but close the connection the same time this endpoint does do but with an other code.
- **/ - - protected synchronized void closeConnection( int code, String message, boolean remote ) { - if( readystate == READYSTATE.CLOSED ) { - return; - } - - if( key != null ) { - // key.attach( null ); //see issue #114 - key.cancel(); - } - if( channel != null ) { - try { - channel.close(); - } catch ( IOException e ) { - wsl.onWebsocketError( this, e ); - } - } - try { - this.wsl.onWebsocketClose( this, code, message, remote ); - } catch ( RuntimeException e ) { - wsl.onWebsocketError( this, e ); - } - if( draft != null ) - draft.reset(); - handshakerequest = null; - - readystate = READYSTATE.CLOSED; - this.outQueue.clear(); - } - - protected void closeConnection( int code, boolean remote ) { - closeConnection( code, "", remote ); - } - - public void closeConnection() { - if( closedremotely == null ) { - throw new IllegalStateException( "this method must be used in conjuction with flushAndClose" ); - } - closeConnection( closecode, closemessage, closedremotely ); - } - - public void closeConnection( int code, String message ) { - closeConnection( code, message, false ); - } - - protected synchronized void flushAndClose( int code, String message, boolean remote ) { - if( flushandclosestate ) { - return; - } - closecode = code; - closemessage = message; - closedremotely = remote; - - flushandclosestate = true; - - wsl.onWriteDemand( this ); // ensures that all outgoing frames are flushed before closing the connection - try { - wsl.onWebsocketClosing( this, code, message, remote ); - } catch ( RuntimeException e ) { - wsl.onWebsocketError( this, e ); - } - if( draft != null ) - draft.reset(); - handshakerequest = null; - } - - public void eot() { - if( getReadyState() == READYSTATE.NOT_YET_CONNECTED ) { - closeConnection( CloseFrame.NEVER_CONNECTED, true ); - } else if( flushandclosestate ) { - closeConnection( closecode, closemessage, closedremotely ); - } else if( draft.getCloseHandshakeType() == CloseHandshakeType.NONE ) { - closeConnection( CloseFrame.NORMAL, true ); - } else if( draft.getCloseHandshakeType() == CloseHandshakeType.ONEWAY ) { - if( role == Role.SERVER ) - closeConnection( CloseFrame.ABNORMAL_CLOSE, true ); - else - closeConnection( CloseFrame.NORMAL, true ); - } else { - closeConnection( CloseFrame.ABNORMAL_CLOSE, true ); - } - } - - @Override - public void close( int code ) { - close( code, "", false ); - } - - public void close( InvalidDataException e ) { - close( e.getCloseCode(), e.getMessage(), false ); - } - - /** - * Send Text data to the other end. - * - * @throws IllegalArgumentException - * @throws NotYetConnectedException - */ - @Override - public void send( String text ) throws WebsocketNotConnectedException { - if( text == null ) - throw new IllegalArgumentException( "Cannot send 'null' data to a WebSocketImpl." ); - send( draft.createFrames( text, role == Role.CLIENT ) ); - } - - /** - * Send Binary data (plain bytes) to the other end. - * - * @throws IllegalArgumentException - * @throws NotYetConnectedException - */ - @Override - public void send( ByteBuffer bytes ) throws IllegalArgumentException , WebsocketNotConnectedException { - if( bytes == null ) - throw new IllegalArgumentException( "Cannot send 'null' data to a WebSocketImpl." ); - send( draft.createFrames( bytes, role == Role.CLIENT ) ); - } - - @Override - public void send( byte[] bytes ) throws IllegalArgumentException , WebsocketNotConnectedException { - send( ByteBuffer.wrap( bytes ) ); - } - - private void send( Collection frames ) { - if( !isOpen() ) - throw new WebsocketNotConnectedException(); - for( Framedata f : frames ) { - sendFrame( f ); - } - } - - @Override - public void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ) { - send( draft.continuousFrame( op, buffer, fin ) ); - } - - @Override - public void sendFrame( Framedata framedata ) { - if( DEBUG ) - System.out.println( "send frame: " + framedata ); - write( draft.createBinaryFrame( framedata ) ); - } - - @Override - public boolean hasBufferedData() { - return !this.outQueue.isEmpty(); - } - - private HandshakeState isFlashEdgeCase( ByteBuffer request ) throws IncompleteHandshakeException { - request.mark(); - if( request.limit() > Draft.FLASH_POLICY_REQUEST.length ) { - return HandshakeState.NOT_MATCHED; - } else if( request.limit() < Draft.FLASH_POLICY_REQUEST.length ) { - throw new IncompleteHandshakeException( Draft.FLASH_POLICY_REQUEST.length ); - } else { - - for( int flash_policy_index = 0 ; request.hasRemaining() ; flash_policy_index++ ) { - if( Draft.FLASH_POLICY_REQUEST[ flash_policy_index ] != request.get() ) { - request.reset(); - return HandshakeState.NOT_MATCHED; - } - } - return HandshakeState.MATCHED; - } - } - - public void startHandshake( ClientHandshakeBuilder handshakedata ) throws InvalidHandshakeException { - assert ( readystate != READYSTATE.CONNECTING ) : "shall only be called once"; - - // Store the Handshake Request we are about to send - this.handshakerequest = draft.postProcessHandshakeRequestAsClient( handshakedata ); - - resourceDescriptor = handshakedata.getResourceDescriptor(); - assert( resourceDescriptor != null ); - - // Notify Listener - try { - wsl.onWebsocketHandshakeSentAsClient( this, this.handshakerequest ); - } catch ( InvalidDataException e ) { - // Stop if the client code throws an exception - throw new InvalidHandshakeException( "Handshake data rejected by client." ); - } catch ( RuntimeException e ) { - wsl.onWebsocketError( this, e ); - throw new InvalidHandshakeException( "rejected because of" + e ); - } - - // Send - write( draft.createHandshake( this.handshakerequest, role ) ); - } - - private void write( ByteBuffer buf ) { - if( DEBUG ) - System.out.println( "write(" + buf.remaining() + "): {" + ( buf.remaining() > 1000 ? "too big to display" : new String( buf.array() ) ) + "}" ); - - outQueue.add( buf ); - /*try { - outQueue.put( buf ); - } catch ( InterruptedException e ) { - write( buf ); - Thread.currentThread().interrupt(); // keep the interrupted status - e.printStackTrace(); - }*/ - wsl.onWriteDemand( this ); - } - - private void write( List bufs ) { - for( ByteBuffer b : bufs ) { - write( b ); - } - } - - private void open( Handshakedata d ) { - if( DEBUG ) - System.out.println( "open using draft: " + draft.getClass().getSimpleName() ); - readystate = READYSTATE.OPEN; - try { - wsl.onWebsocketOpen( this, d ); - } catch ( RuntimeException e ) { - wsl.onWebsocketError( this, e ); - } - } - - @Override - public boolean isConnecting() { - assert ( flushandclosestate ? readystate == READYSTATE.CONNECTING : true ); - return readystate == READYSTATE.CONNECTING; // ifflushandclosestate - } - - @Override - public boolean isOpen() { - assert ( readystate == READYSTATE.OPEN ? !flushandclosestate : true ); - return readystate == READYSTATE.OPEN; - } - - @Override - public boolean isClosing() { - return readystate == READYSTATE.CLOSING; - } - - @Override - public boolean isFlushAndClose() { - return flushandclosestate; - } - - @Override - public boolean isClosed() { - return readystate == READYSTATE.CLOSED; - } - - @Override - public READYSTATE getReadyState() { - return readystate; - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public String toString() { - return super.toString(); // its nice to be able to set breakpoints here - } - - @Override - public InetSocketAddress getRemoteSocketAddress() { - return wsl.getRemoteSocketAddress( this ); - } - - @Override - public InetSocketAddress getLocalSocketAddress() { - return wsl.getLocalSocketAddress( this ); - } - - @Override - public Draft getDraft() { - return draft; - } - - @Override - public void close() { - close( CloseFrame.NORMAL ); - } - - @Override - public String getResourceDescriptor() { - return resourceDescriptor; - } + /** + * The default port of WebSockets, as defined in the spec. If the nullary constructor is used, + * DEFAULT_PORT will be the port the WebSocketServer is binded to. Note that ports under 1024 + * usually require root permissions. + */ + public static final int DEFAULT_PORT = 80; + + /** + * The default wss port of WebSockets, as defined in the spec. If the nullary constructor is used, + * DEFAULT_WSS_PORT will be the port the WebSocketServer is binded to. Note that ports under 1024 + * usually require root permissions. + */ + public static final int DEFAULT_WSS_PORT = 443; + + /** + * Logger instance + * + * @since 1.4.0 + */ + private final Logger log = LoggerFactory.getLogger(WebSocketImpl.class); + + /** + * Queue of buffers that need to be sent to the client. + */ + public final BlockingQueue outQueue; + /** + * Queue of buffers that need to be processed + */ + public final BlockingQueue inQueue; + /** + * The listener to notify of WebSocket events. + */ + private final WebSocketListener wsl; + + private SelectionKey key; + + /** + * the possibly wrapped channel object whose selection is controlled by {@link #key} + */ + private ByteChannel channel; + /** + * Helper variable meant to store the thread which ( exclusively ) triggers this objects decode + * method. + **/ + private WebSocketWorker workerThread; + /** + * When true no further frames may be submitted to be sent + */ + private boolean flushandclosestate = false; + + /** + * The current state of the connection + */ + private volatile ReadyState readyState = ReadyState.NOT_YET_CONNECTED; + + /** + * A list of drafts available for this websocket + */ + private List knownDrafts; + + /** + * The draft which is used by this websocket + */ + private Draft draft = null; + + /** + * The role which this websocket takes in the connection + */ + private Role role; + + /** + * the bytes of an incomplete received handshake + */ + private ByteBuffer tmpHandshakeBytes = ByteBuffer.allocate(0); + + /** + * stores the handshake sent by this websocket ( Role.CLIENT only ) + */ + private ClientHandshake handshakerequest = null; + + private String closemessage = null; + private Integer closecode = null; + private Boolean closedremotely = null; + + private String resourceDescriptor = null; + + /** + * Attribute, when the last pong was received + */ + private long lastPong = System.nanoTime(); + + /** + * Attribut to synchronize the write + */ + private final Object synchronizeWriteObject = new Object(); + + /** + * Attribute to store connection attachment + * + * @since 1.3.7 + */ + private Object attachment; + + /** + * Creates a websocket with server role + * + * @param listener The listener for this instance + * @param drafts The drafts which should be used + */ + public WebSocketImpl(WebSocketListener listener, List drafts) { + this(listener, (Draft) null); + this.role = Role.SERVER; + // draft.copyInstance will be called when the draft is first needed + if (drafts == null || drafts.isEmpty()) { + knownDrafts = new ArrayList<>(); + knownDrafts.add(new Draft_6455()); + } else { + knownDrafts = drafts; + } + } + + /** + * creates a websocket with client role + * + * @param listener The listener for this instance + * @param draft The draft which should be used + */ + public WebSocketImpl(WebSocketListener listener, Draft draft) { + // socket can be null because we want do be able to create the object without already having a bound channel + if (listener == null || (draft == null && role == Role.SERVER)) { + throw new IllegalArgumentException("parameters must not be null"); + } + this.outQueue = new LinkedBlockingQueue<>(); + inQueue = new LinkedBlockingQueue<>(); + this.wsl = listener; + this.role = Role.CLIENT; + if (draft != null) { + this.draft = draft.copyInstance(); + } + } + + /** + * Method to decode the provided ByteBuffer + * + * @param socketBuffer the ByteBuffer to decode + */ + public void decode(ByteBuffer socketBuffer) { + assert (socketBuffer.hasRemaining()); + if (log.isTraceEnabled()) { + log.trace("process({}): ({})", socketBuffer.remaining(), + (socketBuffer.remaining() > 1000 ? "too big to display" + : new String(socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining()))); + } + if (readyState != ReadyState.NOT_YET_CONNECTED) { + if (readyState == ReadyState.OPEN) { + decodeFrames(socketBuffer); + } + } else { + if (decodeHandshake(socketBuffer) && (!isClosing() && !isClosed())) { + assert (tmpHandshakeBytes.hasRemaining() != socketBuffer.hasRemaining() || !socketBuffer + .hasRemaining()); // the buffers will never have remaining bytes at the same time + if (socketBuffer.hasRemaining()) { + decodeFrames(socketBuffer); + } else if (tmpHandshakeBytes.hasRemaining()) { + decodeFrames(tmpHandshakeBytes); + } + } + } + } + + /** + * Returns whether the handshake phase has is completed. In case of a broken handshake this will + * be never the case. + **/ + private boolean decodeHandshake(ByteBuffer socketBufferNew) { + ByteBuffer socketBuffer; + if (tmpHandshakeBytes.capacity() == 0) { + socketBuffer = socketBufferNew; + } else { + if (tmpHandshakeBytes.remaining() < socketBufferNew.remaining()) { + ByteBuffer buf = ByteBuffer + .allocate(tmpHandshakeBytes.capacity() + socketBufferNew.remaining()); + tmpHandshakeBytes.flip(); + buf.put(tmpHandshakeBytes); + tmpHandshakeBytes = buf; + } + + tmpHandshakeBytes.put(socketBufferNew); + tmpHandshakeBytes.flip(); + socketBuffer = tmpHandshakeBytes; + } + socketBuffer.mark(); + try { + HandshakeState handshakestate; + try { + if (role == Role.SERVER) { + if (draft == null) { + for (Draft d : knownDrafts) { + d = d.copyInstance(); + try { + d.setParseMode(role); + socketBuffer.reset(); + Handshakedata tmphandshake = d.translateHandshake(socketBuffer); + if (!(tmphandshake instanceof ClientHandshake)) { + log.trace("Closing due to wrong handshake"); + closeConnectionDueToWrongHandshake( + new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "wrong http function")); + return false; + } + ClientHandshake handshake = (ClientHandshake) tmphandshake; + handshakestate = d.acceptHandshakeAsServer(handshake); + if (handshakestate == HandshakeState.MATCHED) { + resourceDescriptor = handshake.getResourceDescriptor(); + ServerHandshakeBuilder response; + try { + response = wsl.onWebsocketHandshakeReceivedAsServer(this, d, handshake); + } catch (InvalidDataException e) { + log.trace("Closing due to wrong handshake. Possible handshake rejection", e); + closeConnectionDueToWrongHandshake(e); + return false; + } catch (RuntimeException e) { + log.error("Closing due to internal server error", e); + wsl.onWebsocketError(this, e); + closeConnectionDueToInternalServerError(e); + return false; + } + write(d.createHandshake( + d.postProcessHandshakeResponseAsServer(handshake, response))); + draft = d; + open(handshake); + return true; + } + } catch (InvalidHandshakeException e) { + // go on with an other draft + } + } + if (draft == null) { + log.trace("Closing due to protocol error: no draft matches"); + closeConnectionDueToWrongHandshake( + new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "no draft matches")); + } + return false; + } else { + // special case for multiple step handshakes + Handshakedata tmphandshake = draft.translateHandshake(socketBuffer); + if (!(tmphandshake instanceof ClientHandshake)) { + log.trace("Closing due to protocol error: wrong http function"); + flushAndClose(CloseFrame.PROTOCOL_ERROR, "wrong http function", false); + return false; + } + ClientHandshake handshake = (ClientHandshake) tmphandshake; + handshakestate = draft.acceptHandshakeAsServer(handshake); + + if (handshakestate == HandshakeState.MATCHED) { + open(handshake); + return true; + } else { + log.trace("Closing due to protocol error: the handshake did finally not match"); + close(CloseFrame.PROTOCOL_ERROR, "the handshake did finally not match"); + } + return false; + } + } else if (role == Role.CLIENT) { + draft.setParseMode(role); + Handshakedata tmphandshake = draft.translateHandshake(socketBuffer); + if (!(tmphandshake instanceof ServerHandshake)) { + log.trace("Closing due to protocol error: wrong http function"); + flushAndClose(CloseFrame.PROTOCOL_ERROR, "wrong http function", false); + return false; + } + ServerHandshake handshake = (ServerHandshake) tmphandshake; + handshakestate = draft.acceptHandshakeAsClient(handshakerequest, handshake); + if (handshakestate == HandshakeState.MATCHED) { + try { + wsl.onWebsocketHandshakeReceivedAsClient(this, handshakerequest, handshake); + } catch (InvalidDataException e) { + log.trace("Closing due to invalid data exception. Possible handshake rejection", e); + flushAndClose(e.getCloseCode(), e.getMessage(), false); + return false; + } catch (RuntimeException e) { + log.error("Closing since client was never connected", e); + wsl.onWebsocketError(this, e); + flushAndClose(CloseFrame.NEVER_CONNECTED, e.getMessage(), false); + return false; + } + open(handshake); + return true; + } else { + log.trace("Closing due to protocol error: draft {} refuses handshake", draft); + close(CloseFrame.PROTOCOL_ERROR, "draft " + draft + " refuses handshake"); + } + } + } catch (InvalidHandshakeException e) { + log.trace("Closing due to invalid handshake", e); + close(e); + } + } catch (IncompleteHandshakeException e) { + if (tmpHandshakeBytes.capacity() == 0) { + socketBuffer.reset(); + int newsize = e.getPreferredSize(); + if (newsize == 0) { + newsize = socketBuffer.capacity() + 16; + } else { + assert (e.getPreferredSize() >= socketBuffer.remaining()); + } + tmpHandshakeBytes = ByteBuffer.allocate(newsize); + + tmpHandshakeBytes.put(socketBufferNew); + // tmpHandshakeBytes.flip(); + } else { + tmpHandshakeBytes.position(tmpHandshakeBytes.limit()); + tmpHandshakeBytes.limit(tmpHandshakeBytes.capacity()); + } + } + return false; + } + + private void decodeFrames(ByteBuffer socketBuffer) { + List frames; + try { + frames = draft.translateFrame(socketBuffer); + for (Framedata f : frames) { + log.trace("matched frame: {}", f); + draft.processFrame(this, f); + } + } catch (LimitExceededException e) { + if (e.getLimit() == Integer.MAX_VALUE) { + log.error("Closing due to invalid size of frame", e); + wsl.onWebsocketError(this, e); + } + close(e); + } catch (InvalidDataException e) { + log.error("Closing due to invalid data in frame", e); + wsl.onWebsocketError(this, e); + close(e); + } catch (VirtualMachineError | ThreadDeath | LinkageError e) { + log.error("Got fatal error during frame processing"); + throw e; + } catch (Error e) { + log.error("Closing web socket due to an error during frame processing"); + Exception exception = new Exception(e); + wsl.onWebsocketError(this, exception); + String errorMessage = "Got error " + e.getClass().getName(); + close(CloseFrame.UNEXPECTED_CONDITION, errorMessage); + } + } + + /** + * Close the connection if the received handshake was not correct + * + * @param exception the InvalidDataException causing this problem + */ + private void closeConnectionDueToWrongHandshake(InvalidDataException exception) { + write(generateHttpResponseDueToError(404)); + flushAndClose(exception.getCloseCode(), exception.getMessage(), false); + } + + /** + * Close the connection if there was a server error by a RuntimeException + * + * @param exception the RuntimeException causing this problem + */ + private void closeConnectionDueToInternalServerError(RuntimeException exception) { + write(generateHttpResponseDueToError(500)); + flushAndClose(CloseFrame.NEVER_CONNECTED, exception.getMessage(), false); + } + + /** + * Generate a simple response for the corresponding endpoint to indicate some error + * + * @param errorCode the http error code + * @return the complete response as ByteBuffer + */ + private ByteBuffer generateHttpResponseDueToError(int errorCode) { + String errorCodeDescription; + switch (errorCode) { + case 404: + errorCodeDescription = "404 WebSocket Upgrade Failure"; + break; + case 500: + default: + errorCodeDescription = "500 Internal Server Error"; + } + return ByteBuffer.wrap(Charsetfunctions.asciiBytes("HTTP/1.1 " + errorCodeDescription + + "\r\nContent-Type: text/html\r\nServer: TooTallNate Java-WebSocket\r\nContent-Length: " + + (48 + errorCodeDescription.length()) + "\r\n\r\n

" + + errorCodeDescription + "

")); + } + + public synchronized void close(int code, String message, boolean remote) { + if (readyState != ReadyState.CLOSING && readyState != ReadyState.CLOSED) { + if (readyState == ReadyState.OPEN) { + if (code == CloseFrame.ABNORMAL_CLOSE) { + assert (!remote); + readyState = ReadyState.CLOSING; + flushAndClose(code, message, false); + return; + } + if (draft.getCloseHandshakeType() != CloseHandshakeType.NONE) { + try { + if (!remote) { + try { + wsl.onWebsocketCloseInitiated(this, code, message); + } catch (RuntimeException e) { + wsl.onWebsocketError(this, e); + } + } + if (isOpen()) { + CloseFrame closeFrame = new CloseFrame(); + closeFrame.setReason(message); + closeFrame.setCode(code); + closeFrame.isValid(); + sendFrame(closeFrame); + } + } catch (InvalidDataException e) { + log.error("generated frame is invalid", e); + wsl.onWebsocketError(this, e); + flushAndClose(CloseFrame.ABNORMAL_CLOSE, "generated frame is invalid", false); + } + } + flushAndClose(code, message, remote); + } else if (code == CloseFrame.FLASHPOLICY) { + assert (remote); + flushAndClose(CloseFrame.FLASHPOLICY, message, true); + } else if (code == CloseFrame.PROTOCOL_ERROR) { // this endpoint found a PROTOCOL_ERROR + flushAndClose(code, message, remote); + } else { + flushAndClose(CloseFrame.NEVER_CONNECTED, message, false); + } + readyState = ReadyState.CLOSING; + tmpHandshakeBytes = null; + return; + } + } + + @Override + public void close(int code, String message) { + close(code, message, false); + } + + /** + * This will close the connection immediately without a proper close handshake. The code and the + * message therefore won't be transferred over the wire also they will be forwarded to + * onClose/onWebsocketClose. + * + * @param code the closing code + * @param message the closing message + * @param remote Indicates who "generated" code.
+ * true means that this endpoint received the code from + * the other endpoint.
false means this endpoint decided to send the given + * code,
+ * remote may also be true if this endpoint started the closing + * handshake since the other endpoint may not simply echo the code but + * close the connection the same time this endpoint does do but with an other + * code.
+ **/ + public synchronized void closeConnection(int code, String message, boolean remote) { + if (readyState == ReadyState.CLOSED) { + return; + } + //Methods like eot() call this method without calling onClose(). Due to that reason we have to adjust the ReadyState manually + if (readyState == ReadyState.OPEN) { + if (code == CloseFrame.ABNORMAL_CLOSE) { + readyState = ReadyState.CLOSING; + } + } + if (key != null) { + // key.attach( null ); //see issue #114 + key.cancel(); + } + if (channel != null) { + try { + channel.close(); + } catch (IOException e) { + if (e.getMessage() != null && e.getMessage().equals("Broken pipe")) { + log.trace("Caught IOException: Broken pipe during closeConnection()", e); + } else { + log.error("Exception during channel.close()", e); + wsl.onWebsocketError(this, e); + } + } + } + try { + this.wsl.onWebsocketClose(this, code, message, remote); + } catch (RuntimeException e) { + + wsl.onWebsocketError(this, e); + } + if (draft != null) { + draft.reset(); + } + handshakerequest = null; + readyState = ReadyState.CLOSED; + } + + protected void closeConnection(int code, boolean remote) { + closeConnection(code, "", remote); + } + + public void closeConnection() { + if (closedremotely == null) { + throw new IllegalStateException("this method must be used in conjunction with flushAndClose"); + } + closeConnection(closecode, closemessage, closedremotely); + } + + public void closeConnection(int code, String message) { + closeConnection(code, message, false); + } + + public synchronized void flushAndClose(int code, String message, boolean remote) { + if (flushandclosestate) { + return; + } + closecode = code; + closemessage = message; + closedremotely = remote; + + flushandclosestate = true; + + wsl.onWriteDemand( + this); // ensures that all outgoing frames are flushed before closing the connection + try { + wsl.onWebsocketClosing(this, code, message, remote); + } catch (RuntimeException e) { + log.error("Exception in onWebsocketClosing", e); + wsl.onWebsocketError(this, e); + } + if (draft != null) { + draft.reset(); + } + handshakerequest = null; + } + + public void eot() { + if (readyState == ReadyState.NOT_YET_CONNECTED) { + closeConnection(CloseFrame.NEVER_CONNECTED, true); + } else if (flushandclosestate) { + closeConnection(closecode, closemessage, closedremotely); + } else if (draft.getCloseHandshakeType() == CloseHandshakeType.NONE) { + closeConnection(CloseFrame.NORMAL, true); + } else if (draft.getCloseHandshakeType() == CloseHandshakeType.ONEWAY) { + if (role == Role.SERVER) { + closeConnection(CloseFrame.ABNORMAL_CLOSE, true); + } else { + closeConnection(CloseFrame.NORMAL, true); + } + } else { + closeConnection(CloseFrame.ABNORMAL_CLOSE, true); + } + } + + @Override + public void close(int code) { + close(code, "", false); + } + + public void close(InvalidDataException e) { + close(e.getCloseCode(), e.getMessage(), false); + } + + /** + * Send Text data to the other end. + * + * @throws WebsocketNotConnectedException websocket is not yet connected + */ + @Override + public void send(String text) { + if (text == null) { + throw new IllegalArgumentException("Cannot send 'null' data to a WebSocketImpl."); + } + send(draft.createFrames(text, role == Role.CLIENT)); + } + + /** + * Send Binary data (plain bytes) to the other end. + * + * @throws IllegalArgumentException the data is null + * @throws WebsocketNotConnectedException websocket is not yet connected + */ + @Override + public void send(ByteBuffer bytes) { + if (bytes == null) { + throw new IllegalArgumentException("Cannot send 'null' data to a WebSocketImpl."); + } + send(draft.createFrames(bytes, role == Role.CLIENT)); + } + + @Override + public void send(byte[] bytes) { + send(ByteBuffer.wrap(bytes)); + } + + private void send(Collection frames) { + if (!isOpen()) { + throw new WebsocketNotConnectedException(); + } + if (frames == null) { + throw new IllegalArgumentException(); + } + ArrayList outgoingFrames = new ArrayList<>(); + for (Framedata f : frames) { + log.trace("send frame: {}", f); + outgoingFrames.add(draft.createBinaryFrame(f)); + } + write(outgoingFrames); + } + + @Override + public void sendFragmentedFrame(Opcode op, ByteBuffer buffer, boolean fin) { + send(draft.continuousFrame(op, buffer, fin)); + } + + @Override + public void sendFrame(Collection frames) { + send(frames); + } + + @Override + public void sendFrame(Framedata framedata) { + send(Collections.singletonList(framedata)); + } + + public void sendPing() throws NullPointerException { + // Gets a PingFrame from WebSocketListener(wsl) and sends it. + PingFrame pingFrame = wsl.onPreparePing(this); + if (pingFrame == null) { + throw new NullPointerException( + "onPreparePing(WebSocket) returned null. PingFrame to sent can't be null."); + } + sendFrame(pingFrame); + } + + @Override + public boolean hasBufferedData() { + return !this.outQueue.isEmpty(); + } + + public void startHandshake(ClientHandshakeBuilder handshakedata) + throws InvalidHandshakeException { + // Store the Handshake Request we are about to send + this.handshakerequest = draft.postProcessHandshakeRequestAsClient(handshakedata); + + resourceDescriptor = handshakedata.getResourceDescriptor(); + assert (resourceDescriptor != null); + + // Notify Listener + try { + wsl.onWebsocketHandshakeSentAsClient(this, this.handshakerequest); + } catch (InvalidDataException e) { + // Stop if the client code throws an exception + throw new InvalidHandshakeException("Handshake data rejected by client."); + } catch (RuntimeException e) { + log.error("Exception in startHandshake", e); + wsl.onWebsocketError(this, e); + throw new InvalidHandshakeException("rejected because of " + e); + } + + // Send + write(draft.createHandshake(this.handshakerequest)); + } + + private void write(ByteBuffer buf) { + log.trace("write({}): {}", buf.remaining(), + buf.remaining() > 1000 ? "too big to display" : new String(buf.array())); + + outQueue.add(buf); + wsl.onWriteDemand(this); + } + + /** + * Write a list of bytebuffer (frames in binary form) into the outgoing queue + * + * @param bufs the list of bytebuffer + */ + private void write(List bufs) { + synchronized (synchronizeWriteObject) { + for (ByteBuffer b : bufs) { + write(b); + } + } + } + + private void open(Handshakedata d) { + log.trace("open using draft: {}", draft); + readyState = ReadyState.OPEN; + updateLastPong(); + try { + wsl.onWebsocketOpen(this, d); + } catch (RuntimeException e) { + wsl.onWebsocketError(this, e); + } + } + + @Override + public boolean isOpen() { + return readyState == ReadyState.OPEN; + } + + @Override + public boolean isClosing() { + return readyState == ReadyState.CLOSING; + } + + @Override + public boolean isFlushAndClose() { + return flushandclosestate; + } + + @Override + public boolean isClosed() { + return readyState == ReadyState.CLOSED; + } + + @Override + public ReadyState getReadyState() { + return readyState; + } + + /** + * @param key the selection key of this implementation + */ + public void setSelectionKey(SelectionKey key) { + this.key = key; + } + + /** + * @return the selection key of this implementation + */ + public SelectionKey getSelectionKey() { + return key; + } + + @Override + public String toString() { + return super.toString(); // its nice to be able to set breakpoints here + } + + @Override + public InetSocketAddress getRemoteSocketAddress() { + return wsl.getRemoteSocketAddress(this); + } + + @Override + public InetSocketAddress getLocalSocketAddress() { + return wsl.getLocalSocketAddress(this); + } + + @Override + public Draft getDraft() { + return draft; + } + + @Override + public void close() { + close(CloseFrame.NORMAL); + } + + @Override + public String getResourceDescriptor() { + return resourceDescriptor; + } + + /** + * Getter for the last pong received + * + * @return the timestamp for the last received pong + */ + long getLastPong() { + return lastPong; + } + + /** + * Update the timestamp when the last pong was received + */ + public void updateLastPong() { + this.lastPong = System.nanoTime(); + } + + /** + * Getter for the websocket listener + * + * @return the websocket listener associated with this instance + */ + public WebSocketListener getWebSocketListener() { + return wsl; + } + + @Override + @SuppressWarnings("unchecked") + public T getAttachment() { + return (T) attachment; + } + + @Override + public boolean hasSSLSupport() { + return channel instanceof ISSLChannel; + } + + @Override + public SSLSession getSSLSession() { + if (!hasSSLSupport()) { + throw new IllegalArgumentException( + "This websocket uses ws instead of wss. No SSLSession available."); + } + return ((ISSLChannel) channel).getSSLEngine().getSession(); + } + + @Override + public IProtocol getProtocol() { + if (draft == null) { + return null; + } + if (!(draft instanceof Draft_6455)) { + throw new IllegalArgumentException("This draft does not support Sec-WebSocket-Protocol"); + } + return ((Draft_6455) draft).getProtocol(); + } + + @Override + public void setAttachment(T attachment) { + this.attachment = attachment; + } + + public ByteChannel getChannel() { + return channel; + } + + public void setChannel(ByteChannel channel) { + this.channel = channel; + } + + public WebSocketWorker getWorkerThread() { + return workerThread; + } + + public void setWorkerThread(WebSocketWorker workerThread) { + this.workerThread = workerThread; + } + } diff --git a/src/main/java/org/java_websocket/WebSocketListener.java b/src/main/java/org/java_websocket/WebSocketListener.java index e16f69dd9..f0b21d526 100644 --- a/src/main/java/org/java_websocket/WebSocketListener.java +++ b/src/main/java/org/java_websocket/WebSocketListener.java @@ -1,151 +1,200 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket; import java.net.InetSocketAddress; import java.nio.ByteBuffer; - import org.java_websocket.drafts.Draft; import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.framing.CloseFrame; import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.PingFrame; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.handshake.Handshakedata; import org.java_websocket.handshake.ServerHandshake; import org.java_websocket.handshake.ServerHandshakeBuilder; /** - * Implemented by WebSocketClient and WebSocketServer. - * The methods within are called by WebSocket. - * Almost every method takes a first parameter conn which represents the source of the respective event. + * Implemented by WebSocketClient and WebSocketServer. The methods within are + * called by WebSocket. Almost every method takes a first parameter conn which represents + * the source of the respective event. */ public interface WebSocketListener { - /** - * Called on the server side when the socket connection is first established, and the WebSocket - * handshake has been received. This method allows to deny connections based on the received handshake.
- * By default this method only requires protocol compliance. - * - * @param conn - * The WebSocket related to this event - * @param draft - * The protocol draft the client uses to connect - * @param request - * The opening http message send by the client. Can be used to access additional fields like cookies. - * @return Returns an incomplete handshake containing all optional fields - * @throws InvalidDataException - * Throwing this exception will cause this handshake to be rejected - */ - public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException; - - /** - * Called on the client side when the socket connection is first established, and the WebSocketImpl - * handshake response has been received. - * - * @param conn - * The WebSocket related to this event - * @param request - * The handshake initially send out to the server by this websocket. - * @param response - * The handshake the server sent in response to the request. - * @throws InvalidDataException - * Allows the client to reject the connection with the server in respect of its handshake response. - */ - public void onWebsocketHandshakeReceivedAsClient( WebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException; - - /** - * Called on the client side when the socket connection is first established, and the WebSocketImpl - * handshake has just been sent. - * - * @param conn - * The WebSocket related to this event - * @param request - * The handshake sent to the server by this websocket - * @throws InvalidDataException - * Allows the client to stop the connection from progressing - */ - public void onWebsocketHandshakeSentAsClient( WebSocket conn, ClientHandshake request ) throws InvalidDataException; - - /** - * Called when an entire text frame has been received. Do whatever you want - * here... - * - * @param conn - * The WebSocket instance this event is occurring on. - * @param message - * The UTF-8 decoded message that was received. - */ - public void onWebsocketMessage( WebSocket conn, String message ); - - /** - * Called when an entire binary frame has been received. Do whatever you want - * here... - * - * @param conn - * The WebSocket instance this event is occurring on. - * @param blob - * The binary message that was received. - */ - public void onWebsocketMessage( WebSocket conn, ByteBuffer blob ); - - public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ); - - /** - * Called after onHandshakeReceived returns true. - * Indicates that a complete WebSocket connection has been established, - * and we are ready to send/receive data. - * - * @param conn - * The WebSocket instance this event is occuring on. - */ - public void onWebsocketOpen( WebSocket conn, Handshakedata d ); - - /** - * Called after WebSocket#close is explicity called, or when the - * other end of the WebSocket connection is closed. - * - * @param ws - * The WebSocket instance this event is occuring on. - */ - public void onWebsocketClose( WebSocket ws, int code, String reason, boolean remote ); - - /** called as soon as no further frames are accepted */ - public void onWebsocketClosing( WebSocket ws, int code, String reason, boolean remote ); - - /** send when this peer sends a close handshake */ - public void onWebsocketCloseInitiated( WebSocket ws, int code, String reason ); - - /** - * Called if an exception worth noting occurred. - * If an error causes the connection to fail onClose will be called additionally afterwards. - * - * @param ex - * The exception that occurred.
- * Might be null if the exception is not related to any specific connection. For example if the server port could not be bound. - */ - public void onWebsocketError( WebSocket conn, Exception ex ); - - /** - * Called a ping frame has been received. - * This method must send a corresponding pong by itself. - * - * @param f - * The ping frame. Control frames may contain payload. - */ - public void onWebsocketPing( WebSocket conn, Framedata f ); - - /** - * Called when a pong frame is received. - **/ - public void onWebsocketPong( WebSocket conn, Framedata f ); - - /** - * Gets the XML string that should be returned if a client requests a Flash - * security policy. - * @throws InvalidDataException thrown when some data that is required to generate the flash-policy like the websocket local port could not be obtained. - */ - public String getFlashPolicy( WebSocket conn ) throws InvalidDataException; - - /** This method is used to inform the selector thread that there is data queued to be written to the socket. */ - public void onWriteDemand( WebSocket conn ); - - public InetSocketAddress getLocalSocketAddress( WebSocket conn ); - public InetSocketAddress getRemoteSocketAddress( WebSocket conn ); + /** + * Called on the server side when the socket connection is first established, and the WebSocket + * handshake has been received. This method allows to deny connections based on the received + * handshake.
By default this method only requires protocol compliance. + * + * @param conn The WebSocket related to this event + * @param draft The protocol draft the client uses to connect + * @param request The opening http message send by the client. Can be used to access additional + * fields like cookies. + * @return Returns an incomplete handshake containing all optional fields + * @throws InvalidDataException Throwing this exception will cause this handshake to be rejected + */ + ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer(WebSocket conn, Draft draft, + ClientHandshake request) throws InvalidDataException; + + /** + * Called on the client side when the socket connection is first established, and the + * WebSocketImpl handshake response has been received. + * + * @param conn The WebSocket related to this event + * @param request The handshake initially send out to the server by this websocket. + * @param response The handshake the server sent in response to the request. + * @throws InvalidDataException Allows the client to reject the connection with the server in + * respect of its handshake response. + */ + void onWebsocketHandshakeReceivedAsClient(WebSocket conn, ClientHandshake request, + ServerHandshake response) throws InvalidDataException; + + /** + * Called on the client side when the socket connection is first established, and the + * WebSocketImpl handshake has just been sent. + * + * @param conn The WebSocket related to this event + * @param request The handshake sent to the server by this websocket + * @throws InvalidDataException Allows the client to stop the connection from progressing + */ + void onWebsocketHandshakeSentAsClient(WebSocket conn, ClientHandshake request) + throws InvalidDataException; + + /** + * Called when an entire text frame has been received. Do whatever you want here... + * + * @param conn The WebSocket instance this event is occurring on. + * @param message The UTF-8 decoded message that was received. + */ + void onWebsocketMessage(WebSocket conn, String message); + + /** + * Called when an entire binary frame has been received. Do whatever you want here... + * + * @param conn The WebSocket instance this event is occurring on. + * @param blob The binary message that was received. + */ + void onWebsocketMessage(WebSocket conn, ByteBuffer blob); + + /** + * Called after onHandshakeReceived returns true. Indicates that a complete + * WebSocket connection has been established, and we are ready to send/receive data. + * + * @param conn The WebSocket instance this event is occurring on. + * @param d The handshake of the websocket instance + */ + void onWebsocketOpen(WebSocket conn, Handshakedata d); + + /** + * Called after WebSocket#close is explicity called, or when the other end of the + * WebSocket connection is closed. + * + * @param ws The WebSocket instance this event is occurring on. + * @param code The codes can be looked up here: {@link CloseFrame} + * @param reason Additional information string + * @param remote Returns whether or not the closing of the connection was initiated by the remote + * host. + */ + void onWebsocketClose(WebSocket ws, int code, String reason, boolean remote); + + /** + * Called as soon as no further frames are accepted + * + * @param ws The WebSocket instance this event is occurring on. + * @param code The codes can be looked up here: {@link CloseFrame} + * @param reason Additional information string + * @param remote Returns whether or not the closing of the connection was initiated by the remote + * host. + */ + void onWebsocketClosing(WebSocket ws, int code, String reason, boolean remote); + + /** + * send when this peer sends a close handshake + * + * @param ws The WebSocket instance this event is occurring on. + * @param code The codes can be looked up here: {@link CloseFrame} + * @param reason Additional information string + */ + void onWebsocketCloseInitiated(WebSocket ws, int code, String reason); + + /** + * Called if an exception worth noting occurred. If an error causes the connection to fail onClose + * will be called additionally afterwards. + * + * @param conn The WebSocket instance this event is occurring on. + * @param ex The exception that occurred.
Might be null if the exception is not related to + * any specific connection. For example if the server port could not be bound. + */ + void onWebsocketError(WebSocket conn, Exception ex); + + /** + * Called a ping frame has been received. This method must send a corresponding pong by itself. + * + * @param conn The WebSocket instance this event is occurring on. + * @param f The ping frame. Control frames may contain payload. + */ + void onWebsocketPing(WebSocket conn, Framedata f); + + /** + * Called just before a ping frame is sent, in order to allow users to customize their ping frame + * data. + * + * @param conn The WebSocket connection from which the ping frame will be sent. + * @return PingFrame to be sent. + */ + PingFrame onPreparePing(WebSocket conn); + + /** + * Called when a pong frame is received. + * + * @param conn The WebSocket instance this event is occurring on. + * @param f The pong frame. Control frames may contain payload. + **/ + void onWebsocketPong(WebSocket conn, Framedata f); + + /** + * This method is used to inform the selector thread that there is data queued to be written to + * the socket. + * + * @param conn The WebSocket instance this event is occurring on. + */ + void onWriteDemand(WebSocket conn); + + /** + * @param conn The WebSocket instance this event is occurring on. + * @return Returns the address of the endpoint this socket is bound to. + * @see WebSocket#getLocalSocketAddress() + */ + InetSocketAddress getLocalSocketAddress(WebSocket conn); + + /** + * @param conn The WebSocket instance this event is occurring on. + * @return Returns the address of the endpoint this socket is connected to, or{@code null} if it + * is unconnected. + * @see WebSocket#getRemoteSocketAddress() + */ + InetSocketAddress getRemoteSocketAddress(WebSocket conn); } diff --git a/src/main/java/org/java_websocket/WebSocketServerFactory.java b/src/main/java/org/java_websocket/WebSocketServerFactory.java new file mode 100644 index 000000000..825aa2165 --- /dev/null +++ b/src/main/java/org/java_websocket/WebSocketServerFactory.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket; + +import java.io.IOException; +import java.nio.channels.ByteChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.List; +import org.java_websocket.drafts.Draft; + +/** + * Interface to encapsulate the required methods for a websocket factory + */ +public interface WebSocketServerFactory extends WebSocketFactory { + + @Override + WebSocketImpl createWebSocket(WebSocketAdapter a, Draft d); + + @Override + WebSocketImpl createWebSocket(WebSocketAdapter a, List drafts); + + /** + * Allows to wrap the SocketChannel( key.channel() ) to insert a protocol layer( like ssl or proxy + * authentication) beyond the ws layer. + * + * @param channel The SocketChannel to wrap + * @param key a SelectionKey of an open SocketChannel. + * @return The channel on which the read and write operations will be performed.
+ * @throws IOException may be thrown while writing on the channel + */ + ByteChannel wrapChannel(SocketChannel channel, SelectionKey key) throws IOException; + + /** + * Allows to shutdown the websocket factory for a clean shutdown + */ + void close(); +} diff --git a/src/main/java/org/java_websocket/WrappedByteChannel.java b/src/main/java/org/java_websocket/WrappedByteChannel.java index 83a3290b3..8dee57db0 100644 --- a/src/main/java/org/java_websocket/WrappedByteChannel.java +++ b/src/main/java/org/java_websocket/WrappedByteChannel.java @@ -1,26 +1,76 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; -import javax.net.ssl.SSLException; - public interface WrappedByteChannel extends ByteChannel { - public boolean isNeedWrite(); - public void writeMore() throws IOException; - - /** - * returns whether readMore should be called to fetch data which has been decoded but not yet been returned. - * - * @see #read(ByteBuffer) - * @see #readMore(ByteBuffer) - **/ - public boolean isNeedRead(); - /** - * This function does not read data from the underlying channel at all. It is just a way to fetch data which has already be received or decoded but was but was not yet returned to the user. - * This could be the case when the decoded data did not fit into the buffer the user passed to {@link #read(ByteBuffer)}. - **/ - public int readMore( ByteBuffer dst ) throws SSLException; - public boolean isBlocking(); + + /** + * returns whether writeMore should be called write additional data. + * + * @return is a additional write needed + */ + boolean isNeedWrite(); + + /** + * Gets called when {@link #isNeedWrite()} ()} requires a additional rite + * + * @throws IOException may be thrown due to an error while writing + */ + void writeMore() throws IOException; + + /** + * returns whether readMore should be called to fetch data which has been decoded but not yet been + * returned. + * + * @return is a additional read needed + * @see #read(ByteBuffer) + * @see #readMore(ByteBuffer) + **/ + boolean isNeedRead(); + + /** + * This function does not read data from the underlying channel at all. It is just a way to fetch + * data which has already be received or decoded but was but was not yet returned to the user. + * This could be the case when the decoded data did not fit into the buffer the user passed to + * {@link #read(ByteBuffer)}. + * + * @param dst the destiny of the read + * @return the amount of remaining data + * @throws IOException when a error occurred during unwrapping + **/ + int readMore(ByteBuffer dst) throws IOException; + + /** + * This function returns the blocking state of the channel + * + * @return is the channel blocking + */ + boolean isBlocking(); } diff --git a/src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java b/src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java deleted file mode 100644 index bbac67258..000000000 --- a/src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.java_websocket.client; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.nio.ByteBuffer; -import java.nio.channels.ByteChannel; - -import org.java_websocket.AbstractWrappedByteChannel; - -public abstract class AbstractClientProxyChannel extends AbstractWrappedByteChannel { - protected final ByteBuffer proxyHandshake; - - - /** - * @param towrap - * The channel to the proxy server - **/ - public AbstractClientProxyChannel( ByteChannel towrap ) { - super( towrap ); - try { - proxyHandshake = ByteBuffer.wrap( buildHandShake().getBytes( "ASCII" ) ); - } catch ( UnsupportedEncodingException e ) { - throw new RuntimeException( e ); - } - } - - @Override - public int write( ByteBuffer src ) throws IOException { - if( !proxyHandshake.hasRemaining() ) { - return super.write( src ); - } else { - return super.write( proxyHandshake ); - } - } - - public abstract String buildHandShake(); - -} diff --git a/src/main/java/org/java_websocket/client/DnsResolver.java b/src/main/java/org/java_websocket/client/DnsResolver.java new file mode 100644 index 000000000..ec9f17f06 --- /dev/null +++ b/src/main/java/org/java_websocket/client/DnsResolver.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.client; + +import java.net.InetAddress; +import java.net.URI; +import java.net.UnknownHostException; + +/** + * Users may implement this interface to override the default DNS lookup offered by the OS. + * + * @since 1.4.1 + */ +public interface DnsResolver { + + /** + * Resolves the IP address for the given URI. + *

+ * This method should never return null. If it's not able to resolve the IP address then it should + * throw an UnknownHostException + * + * @param uri The URI to be resolved + * @return The resolved IP address + * @throws UnknownHostException if no IP address for the uri could be found. + */ + InetAddress resolve(URI uri) throws UnknownHostException; + +} diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index 86eff7947..0e38326d3 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -1,454 +1,1025 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.client; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.Socket; import java.net.URI; +import java.net.UnknownHostException; import java.nio.ByteBuffer; -import java.nio.channels.NotYetConnectedException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.Collection; +import java.util.Collections; import java.util.Map; +import java.util.TreeMap; import java.util.concurrent.CountDownLatch; - +import java.util.concurrent.TimeUnit; +import javax.net.SocketFactory; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import org.java_websocket.AbstractWebSocket; import org.java_websocket.WebSocket; -import org.java_websocket.WebSocketAdapter; import org.java_websocket.WebSocketImpl; import org.java_websocket.drafts.Draft; -import org.java_websocket.drafts.Draft_17; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.enums.Opcode; +import org.java_websocket.enums.ReadyState; import org.java_websocket.exceptions.InvalidHandshakeException; import org.java_websocket.framing.CloseFrame; import org.java_websocket.framing.Framedata; -import org.java_websocket.framing.Framedata.Opcode; import org.java_websocket.handshake.HandshakeImpl1Client; import org.java_websocket.handshake.Handshakedata; import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.protocols.IProtocol; /** - * A subclass must implement at least onOpen, onClose, and onMessage to be - * useful. At runtime the user is expected to establish a connection via {@link #connect()}, then receive events like {@link #onMessage(String)} via the overloaded methods and to {@link #send(String)} data to the server. + * A subclass must implement at least onOpen, onClose, and + * onMessage to be useful. At runtime the user is expected to establish a connection via + * {@link #connect()}, then receive events like {@link #onMessage(String)} via the overloaded + * methods and to {@link #send(String)} data to the server. */ -public abstract class WebSocketClient extends WebSocketAdapter implements Runnable, WebSocket { - - /** - * The URI this channel is supposed to connect to. - */ - protected URI uri = null; - - private WebSocketImpl engine = null; - - private Socket socket = null; - - private InputStream istream; - - private OutputStream ostream; - - private Proxy proxy = Proxy.NO_PROXY; - - private Thread writeThread; - - private Draft draft; - - private Map headers; - - private CountDownLatch connectLatch = new CountDownLatch( 1 ); - - private CountDownLatch closeLatch = new CountDownLatch( 1 ); - - private int connectTimeout = 0; - - /** This open a websocket connection as specified by rfc6455 */ - public WebSocketClient( URI serverURI ) { - this( serverURI, new Draft_17() ); - } - - /** - * Constructs a WebSocketClient instance and sets it to the connect to the - * specified URI. The channel does not attampt to connect automatically. The connection - * will be established once you call connect. - */ - public WebSocketClient( URI serverUri , Draft draft ) { - this( serverUri, draft, null, 0 ); - } - - public WebSocketClient( URI serverUri , Draft protocolDraft , Map httpHeaders , int connectTimeout ) { - if( serverUri == null ) { - throw new IllegalArgumentException(); - } else if( protocolDraft == null ) { - throw new IllegalArgumentException( "null as draft is permitted for `WebSocketServer` only!" ); - } - this.uri = serverUri; - this.draft = protocolDraft; - this.headers = httpHeaders; - this.connectTimeout = connectTimeout; - this.engine = new WebSocketImpl( this, protocolDraft ); - } - - /** - * Returns the URI that this WebSocketClient is connected to. - */ - public URI getURI() { - return uri; - } - - /** - * Returns the protocol version this channel uses.
- * For more infos see https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts - */ - public Draft getDraft() { - return draft; - } - - /** - * Initiates the websocket connection. This method does not block. - */ - public void connect() { - if( writeThread != null ) - throw new IllegalStateException( "WebSocketClient objects are not reuseable" ); - writeThread = new Thread( this ); - writeThread.start(); - } - - /** - * Same as connect but blocks until the websocket connected or failed to do so.
- * Returns whether it succeeded or not. - **/ - public boolean connectBlocking() throws InterruptedException { - connect(); - connectLatch.await(); - return engine.isOpen(); - } - - /** - * Initiates the websocket close handshake. This method does not block
- * In oder to make sure the connection is closed use closeBlocking - */ - public void close() { - if( writeThread != null ) { - engine.close( CloseFrame.NORMAL ); - } - } - - public void closeBlocking() throws InterruptedException { - close(); - closeLatch.await(); - } - - /** - * Sends text to the connected websocket server. - * - * @param text - * The string which will be transmitted. - */ - public void send( String text ) throws NotYetConnectedException { - engine.send( text ); - } - - /** - * Sends binary data to the connected webSocket server. - * - * @param data - * The byte-Array of data to send to the WebSocket server. - */ - public void send( byte[] data ) throws NotYetConnectedException { - engine.send( data ); - } - - public void run() { - try { - if( socket == null ) { - socket = new Socket( proxy ); - } else if( socket.isClosed() ) { - throw new IOException(); - } - if( !socket.isBound() ) - socket.connect( new InetSocketAddress( uri.getHost(), getPort() ), connectTimeout ); - istream = socket.getInputStream(); - ostream = socket.getOutputStream(); - - sendHandshake(); - } catch ( /*IOException | SecurityException | UnresolvedAddressException | InvalidHandshakeException | ClosedByInterruptException | SocketTimeoutException */Exception e ) { - onWebsocketError( engine, e ); - engine.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() ); - return; - } - - writeThread = new Thread( new WebsocketWriteThread() ); - writeThread.start(); - - byte[] rawbuffer = new byte[ WebSocketImpl.RCVBUF ]; - int readBytes; - - try { - while ( !isClosed() && ( readBytes = istream.read( rawbuffer ) ) != -1 ) { - engine.decode( ByteBuffer.wrap( rawbuffer, 0, readBytes ) ); - } - engine.eot(); - } catch ( IOException e ) { - engine.eot(); - } catch ( RuntimeException e ) { - // this catch case covers internal errors only and indicates a bug in this websocket implementation - onError( e ); - engine.closeConnection( CloseFrame.ABNORMAL_CLOSE, e.getMessage() ); - } - assert ( socket.isClosed() ); - } - private int getPort() { - int port = uri.getPort(); - if( port == -1 ) { - String scheme = uri.getScheme(); - if( scheme.equals( "wss" ) ) { - return WebSocket.DEFAULT_WSS_PORT; - } else if( scheme.equals( "ws" ) ) { - return WebSocket.DEFAULT_PORT; - } else { - throw new RuntimeException( "unkonow scheme" + scheme ); - } - } - return port; - } - - private void sendHandshake() throws InvalidHandshakeException { - String path; - String part1 = uri.getPath(); - String part2 = uri.getQuery(); - if( part1 == null || part1.length() == 0 ) - path = "/"; - else - path = part1; - if( part2 != null ) - path += "?" + part2; - int port = getPort(); - String host = uri.getHost() + ( port != WebSocket.DEFAULT_PORT ? ":" + port : "" ); - - HandshakeImpl1Client handshake = new HandshakeImpl1Client(); - handshake.setResourceDescriptor( path ); - handshake.put( "Host", host ); - if( headers != null ) { - for( Map.Entry kv : headers.entrySet() ) { - handshake.put( kv.getKey(), kv.getValue() ); - } - } - engine.startHandshake( handshake ); - } - - /** - * This represents the state of the connection. - */ - public READYSTATE getReadyState() { - return engine.getReadyState(); - } - - /** - * Calls subclass' implementation of onMessage. - */ - @Override - public final void onWebsocketMessage( WebSocket conn, String message ) { - onMessage( message ); - } - - @Override - public final void onWebsocketMessage( WebSocket conn, ByteBuffer blob ) { - onMessage( blob ); - } - - @Override - public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { - onFragment( frame ); - } - - /** - * Calls subclass' implementation of onOpen. - */ - @Override - public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) { - connectLatch.countDown(); - onOpen( (ServerHandshake) handshake ); - } - - /** - * Calls subclass' implementation of onClose. - */ - @Override - public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) { - connectLatch.countDown(); - closeLatch.countDown(); - if( writeThread != null ) - writeThread.interrupt(); - try { - if( socket != null ) - socket.close(); - } catch ( IOException e ) { - onWebsocketError( this, e ); - } - onClose( code, reason, remote ); - } - - /** - * Calls subclass' implementation of onIOError. - */ - @Override - public final void onWebsocketError( WebSocket conn, Exception ex ) { - onError( ex ); - } - - @Override - public final void onWriteDemand( WebSocket conn ) { - // nothing to do - } - - @Override - public void onWebsocketCloseInitiated( WebSocket conn, int code, String reason ) { - onCloseInitiated( code, reason ); - } - - @Override - public void onWebsocketClosing( WebSocket conn, int code, String reason, boolean remote ) { - onClosing( code, reason, remote ); - } - - public void onCloseInitiated( int code, String reason ) { - } - - public void onClosing( int code, String reason, boolean remote ) { - } - - public WebSocket getConnection() { - return engine; - } - - @Override - public InetSocketAddress getLocalSocketAddress( WebSocket conn ) { - if( socket != null ) - return (InetSocketAddress) socket.getLocalSocketAddress(); - return null; - } - - @Override - public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) { - if( socket != null ) - return (InetSocketAddress) socket.getRemoteSocketAddress(); - return null; - } - - // ABTRACT METHODS ///////////////////////////////////////////////////////// - public abstract void onOpen( ServerHandshake handshakedata ); - public abstract void onMessage( String message ); - public abstract void onClose( int code, String reason, boolean remote ); - public abstract void onError( Exception ex ); - public void onMessage( ByteBuffer bytes ) { - } - public void onFragment( Framedata frame ) { - } - - private class WebsocketWriteThread implements Runnable { - @Override - public void run() { - Thread.currentThread().setName( "WebsocketWriteThread" ); - try { - while ( !Thread.interrupted() ) { - ByteBuffer buffer = engine.outQueue.take(); - ostream.write( buffer.array(), 0, buffer.limit() ); - ostream.flush(); - } - } catch ( IOException e ) { - engine.eot(); - } catch ( InterruptedException e ) { - // this thread is regularly terminated via an interrupt - } - } - } - - public void setProxy( Proxy proxy ) { - if( proxy == null ) - throw new IllegalArgumentException(); - this.proxy = proxy; - } - - /** - * Accepts bound and unbound sockets.
- * This method must be called before connect. - * If the given socket is not yet bound it will be bound to the uri specified in the constructor. - **/ - public void setSocket( Socket socket ) { - if( this.socket != null ) { - throw new IllegalStateException( "socket has already been set" ); - } - this.socket = socket; - } - - @Override - public void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ) { - engine.sendFragmentedFrame( op, buffer, fin ); - } - - @Override - public boolean isOpen() { - return engine.isOpen(); - } - - @Override - public boolean isFlushAndClose() { - return engine.isFlushAndClose(); - } - - @Override - public boolean isClosed() { - return engine.isClosed(); - } - - @Override - public boolean isClosing() { - return engine.isClosing(); - } - - @Override - public boolean isConnecting() { - return engine.isConnecting(); - } - - @Override - public boolean hasBufferedData() { - return engine.hasBufferedData(); - } - - @Override - public void close( int code ) { - engine.close(); - } - - @Override - public void close( int code, String message ) { - engine.close( code, message ); - } - - @Override - public void closeConnection( int code, String message ) { - engine.closeConnection( code, message ); - } - - @Override - public void send( ByteBuffer bytes ) throws IllegalArgumentException , NotYetConnectedException { - engine.send( bytes ); - } - - @Override - public void sendFrame( Framedata framedata ) { - engine.sendFrame( framedata ); - } - - @Override - public InetSocketAddress getLocalSocketAddress() { - return engine.getLocalSocketAddress(); - } - @Override - public InetSocketAddress getRemoteSocketAddress() { - return engine.getRemoteSocketAddress(); - } - - @Override - public String getResourceDescriptor() { - return uri.getPath(); - } +public abstract class WebSocketClient extends AbstractWebSocket implements Runnable, WebSocket { + + /** + * The URI this channel is supposed to connect to. + */ + protected URI uri = null; + + /** + * The underlying engine + */ + private WebSocketImpl engine = null; + + /** + * The socket for this WebSocketClient + */ + private Socket socket = null; + + /** + * The SocketFactory for this WebSocketClient + * + * @since 1.4.0 + */ + private SocketFactory socketFactory = null; + + /** + * The used OutputStream + */ + private OutputStream ostream; + + /** + * The used proxy, if any + */ + private Proxy proxy = Proxy.NO_PROXY; + + /** + * The thread to write outgoing message + */ + private Thread writeThread; + + /** + * The thread to connect and read message + */ + private Thread connectReadThread; + + /** + * The draft to use + */ + private Draft draft; + + /** + * The additional headers to use + */ + private Map headers; + + /** + * The latch for connectBlocking() + */ + private CountDownLatch connectLatch = new CountDownLatch(1); + + /** + * The latch for closeBlocking() + */ + private CountDownLatch closeLatch = new CountDownLatch(1); + + /** + * The socket timeout value to be used in milliseconds. + */ + private int connectTimeout = 0; + + /** + * DNS resolver that translates a URI to an InetAddress + * + * @see InetAddress + * @since 1.4.1 + */ + private DnsResolver dnsResolver = null; + + /** + * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The + * channel does not attampt to connect automatically. The connection will be established once you + * call connect. + * + * @param serverUri the server URI to connect to + */ + public WebSocketClient(URI serverUri) { + this(serverUri, new Draft_6455()); + } + + /** + * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The + * channel does not attampt to connect automatically. The connection will be established once you + * call connect. + * + * @param serverUri the server URI to connect to + * @param protocolDraft The draft which should be used for this connection + */ + public WebSocketClient(URI serverUri, Draft protocolDraft) { + this(serverUri, protocolDraft, null, 0); + } + + /** + * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The + * channel does not attampt to connect automatically. The connection will be established once you + * call connect. + * + * @param serverUri the server URI to connect to + * @param httpHeaders Additional HTTP-Headers + * @since 1.3.8 + */ + public WebSocketClient(URI serverUri, Map httpHeaders) { + this(serverUri, new Draft_6455(), httpHeaders); + } + + /** + * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The + * channel does not attampt to connect automatically. The connection will be established once you + * call connect. + * + * @param serverUri the server URI to connect to + * @param protocolDraft The draft which should be used for this connection + * @param httpHeaders Additional HTTP-Headers + * @since 1.3.8 + */ + public WebSocketClient(URI serverUri, Draft protocolDraft, Map httpHeaders) { + this(serverUri, protocolDraft, httpHeaders, 0); + } + + /** + * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The + * channel does not attampt to connect automatically. The connection will be established once you + * call connect. + * + * @param serverUri the server URI to connect to + * @param protocolDraft The draft which should be used for this connection + * @param httpHeaders Additional HTTP-Headers + * @param connectTimeout The Timeout for the connection + */ + public WebSocketClient(URI serverUri, Draft protocolDraft, Map httpHeaders, + int connectTimeout) { + if (serverUri == null) { + throw new IllegalArgumentException(); + } else if (protocolDraft == null) { + throw new IllegalArgumentException("null as draft is permitted for `WebSocketServer` only!"); + } + this.uri = serverUri; + this.draft = protocolDraft; + this.dnsResolver = new DnsResolver() { + @Override + public InetAddress resolve(URI uri) throws UnknownHostException { + return InetAddress.getByName(uri.getHost()); + } + }; + if (httpHeaders != null) { + headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + headers.putAll(httpHeaders); + } + this.connectTimeout = connectTimeout; + setTcpNoDelay(false); + setReuseAddr(false); + this.engine = new WebSocketImpl(this, protocolDraft); + } + + /** + * Returns the URI that this WebSocketClient is connected to. + * + * @return the URI connected to + */ + public URI getURI() { + return uri; + } + + /** + * Returns the protocol version this channel uses.
For more infos see + * https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts + * + * @return The draft used for this client + */ + public Draft getDraft() { + return draft; + } + + /** + * Returns the socket to allow Hostname Verification + * + * @return the socket used for this connection + */ + public Socket getSocket() { + return socket; + } + + /** + * @param key Name of the header to add. + * @param value Value of the header to add. + * @since 1.4.1 Adds an additional header to be sent in the handshake.
If the connection is + * already made, adding headers has no effect, unless reconnect is called, which then a new + * handshake is sent.
If a header with the same key already exists, it is overridden. + */ + public void addHeader(String key, String value) { + if (headers == null) { + headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + } + headers.put(key, value); + } + + /** + * @param key Name of the header to remove. + * @return the previous value associated with key, or null if there was no mapping for key. + * @since 1.4.1 Removes a header from the handshake to be sent, if header key exists.
+ */ + public String removeHeader(String key) { + if (headers == null) { + return null; + } + return headers.remove(key); + } + + /** + * @since 1.4.1 Clears all previously put headers. + */ + public void clearHeaders() { + headers = null; + } + + /** + * Sets a custom DNS resolver. + * + * @param dnsResolver The DnsResolver to use. + * @since 1.4.1 + */ + public void setDnsResolver(DnsResolver dnsResolver) { + this.dnsResolver = dnsResolver; + } + + /** + * Reinitiates the websocket connection. This method does not block. + * + * @since 1.3.8 + */ + public void reconnect() { + reset(); + connect(); + } + + /** + * Same as reconnect but blocks until the websocket reconnected or failed to do + * so.
+ * + * @return Returns whether it succeeded or not. + * @throws InterruptedException Thrown when the threads get interrupted + * @since 1.3.8 + */ + public boolean reconnectBlocking() throws InterruptedException { + reset(); + return connectBlocking(); + } + + /** + * Same as reconnect but blocks with a timeout until the websocket connected or failed + * to do so.
+ * + * @param timeout The connect timeout + * @param timeUnit The timeout time unit + * @return Returns whether it succeeded or not. + * @throws InterruptedException Thrown when the threads get interrupted + * @since 1.6.1 + */ + public boolean reconnectBlocking(long timeout, TimeUnit timeUnit) throws InterruptedException { + reset(); + return connectBlocking(timeout, timeUnit); + } + + /** + * Reset everything relevant to allow a reconnect + * + * @since 1.3.8 + */ + private void reset() { + Thread current = Thread.currentThread(); + if (current == writeThread || current == connectReadThread) { + throw new IllegalStateException( + "You cannot initialize a reconnect out of the websocket thread. Use reconnect in another thread to ensure a successful cleanup."); + } + try { + // This socket null check ensures we can reconnect a socket that failed to connect. It's an uncommon edge case, but we want to make sure we support it + if (engine.getReadyState() == ReadyState.NOT_YET_CONNECTED && socket != null) { + // Closing the socket when we have not connected prevents the writeThread from hanging on a write indefinitely during connection teardown + socket.close(); + } + closeBlocking(); + + if (writeThread != null) { + this.writeThread.interrupt(); + this.writeThread.join(); + this.writeThread = null; + } + if (connectReadThread != null) { + this.connectReadThread.interrupt(); + this.connectReadThread.join(); + this.connectReadThread = null; + } + this.draft.reset(); + if (this.socket != null) { + this.socket.close(); + this.socket = null; + } + } catch (Exception e) { + onError(e); + engine.closeConnection(CloseFrame.ABNORMAL_CLOSE, e.getMessage()); + return; + } + connectLatch = new CountDownLatch(1); + closeLatch = new CountDownLatch(1); + this.engine = new WebSocketImpl(this, this.draft); + } + + /** + * Initiates the websocket connection. This method does not block. + */ + public void connect() { + if (connectReadThread != null) { + throw new IllegalStateException("WebSocketClient objects are not reuseable"); + } + connectReadThread = new Thread(this); + connectReadThread.setDaemon(isDaemon()); + connectReadThread.setName("WebSocketConnectReadThread-" + connectReadThread.getId()); + connectReadThread.start(); + } + + /** + * Same as connect but blocks until the websocket connected or failed to do so.
+ * + * @return Returns whether it succeeded or not. + * @throws InterruptedException Thrown when the threads get interrupted + */ + public boolean connectBlocking() throws InterruptedException { + connect(); + connectLatch.await(); + return engine.isOpen(); + } + + /** + * Same as connect but blocks with a timeout until the websocket connected or failed + * to do so.
+ * + * @param timeout The connect timeout + * @param timeUnit The timeout time unit + * @return Returns whether it succeeded or not. + * @throws InterruptedException Thrown when the threads get interrupted + */ + public boolean connectBlocking(long timeout, TimeUnit timeUnit) throws InterruptedException { + connect(); + + boolean connected = connectLatch.await(timeout, timeUnit); + if (!connected) { + reset(); + } + + return connected && engine.isOpen(); + } + + /** + * Initiates the websocket close handshake. This method does not block
In oder to make sure + * the connection is closed use closeBlocking + */ + public void close() { + if (writeThread != null) { + engine.close(CloseFrame.NORMAL); + } + } + + /** + * Same as close but blocks until the websocket closed or failed to do so.
+ * + * @throws InterruptedException Thrown when the threads get interrupted + */ + public void closeBlocking() throws InterruptedException { + close(); + closeLatch.await(); + } + + /** + * Sends text to the connected websocket server. + * + * @param text The string which will be transmitted. + */ + public void send(String text) { + engine.send(text); + } + + /** + * Sends binary data to the connected webSocket server. + * + * @param data The byte-Array of data to send to the WebSocket server. + */ + public void send(byte[] data) { + engine.send(data); + } + + @Override + public T getAttachment() { + return engine.getAttachment(); + } + + @Override + public void setAttachment(T attachment) { + engine.setAttachment(attachment); + } + + @Override + protected Collection getConnections() { + return Collections.singletonList((WebSocket) engine); + } + + @Override + public void sendPing() { + engine.sendPing(); + } + + public void run() { + InputStream istream; + try { + boolean upgradeSocketToSSLSocket = prepareSocket(); + + socket.setTcpNoDelay(isTcpNoDelay()); + socket.setReuseAddress(isReuseAddr()); + int receiveBufferSize = getReceiveBufferSize(); + if (receiveBufferSize > 0) { + socket.setReceiveBufferSize(receiveBufferSize); + } + + if (!socket.isConnected()) { + InetSocketAddress addr = dnsResolver == null ? InetSocketAddress.createUnresolved(uri.getHost(), getPort()) : new InetSocketAddress(dnsResolver.resolve(uri), this.getPort()); + socket.connect(addr, connectTimeout); + } + + // if the socket is set by others we don't apply any TLS wrapper + if (upgradeSocketToSSLSocket && "wss".equals(uri.getScheme())) { + upgradeSocketToSSL(); + } + + if (socket instanceof SSLSocket) { + SSLSocket sslSocket = (SSLSocket) socket; + SSLParameters sslParameters = sslSocket.getSSLParameters(); + onSetSSLParameters(sslParameters); + sslSocket.setSSLParameters(sslParameters); + } + + istream = socket.getInputStream(); + ostream = socket.getOutputStream(); + + sendHandshake(); + } catch (/*IOException | SecurityException | UnresolvedAddressException | InvalidHandshakeException | ClosedByInterruptException | SocketTimeoutException */Exception e) { + onWebsocketError(engine, e); + engine.closeConnection(CloseFrame.NEVER_CONNECTED, e.getMessage()); + return; + } catch (InternalError e) { + // https://bugs.openjdk.java.net/browse/JDK-8173620 + if (e.getCause() instanceof InvocationTargetException && e.getCause() + .getCause() instanceof IOException) { + IOException cause = (IOException) e.getCause().getCause(); + onWebsocketError(engine, cause); + engine.closeConnection(CloseFrame.NEVER_CONNECTED, cause.getMessage()); + return; + } + throw e; + } + + if (writeThread != null) { + writeThread.interrupt(); + try { + writeThread.join(); + } catch (InterruptedException e) { + /* ignore */ + } + } + writeThread = new Thread(new WebsocketWriteThread(this)); + writeThread.setDaemon(isDaemon()); + writeThread.start(); + + int receiveBufferSize = getReceiveBufferSize(); + byte[] rawbuffer = new byte[receiveBufferSize > 0 ? receiveBufferSize : DEFAULT_READ_BUFFER_SIZE]; + int readBytes; + + try { + while (!isClosing() && !isClosed() && (readBytes = istream.read(rawbuffer)) != -1) { + engine.decode(ByteBuffer.wrap(rawbuffer, 0, readBytes)); + } + engine.eot(); + } catch (IOException e) { + handleIOException(e); + } catch (RuntimeException e) { + // this catch case covers internal errors only and indicates a bug in this websocket implementation + onError(e); + engine.closeConnection(CloseFrame.ABNORMAL_CLOSE, e.getMessage()); + } + } + + private void upgradeSocketToSSL() + throws NoSuchAlgorithmException, KeyManagementException, IOException { + SSLSocketFactory factory; + // Prioritise the provided socketfactory + // Helps when using web debuggers like Fiddler Classic + if (socketFactory instanceof SSLSocketFactory) { + factory = (SSLSocketFactory) socketFactory; + } else { + factory = (SSLSocketFactory) SSLSocketFactory.getDefault(); + } + socket = factory.createSocket(socket, uri.getHost(), getPort(), true); + } + + private boolean prepareSocket() throws IOException { + boolean upgradeSocketToSSLSocket = false; + // Prioritise a proxy over a socket factory and apply the socketfactory later + if (proxy != Proxy.NO_PROXY) { + socket = new Socket(proxy); + upgradeSocketToSSLSocket = true; + } else if (socketFactory != null) { + socket = socketFactory.createSocket(); + } else if (socket == null) { + socket = new Socket(proxy); + upgradeSocketToSSLSocket = true; + } else if (socket.isClosed()) { + throw new IOException(); + } + return upgradeSocketToSSLSocket; + } + + /** + * Apply specific SSLParameters If you override this method make sure to always call + * super.onSetSSLParameters() to ensure the hostname validation is active + * + * @param sslParameters the SSLParameters which will be used for the SSLSocket + */ + protected void onSetSSLParameters(SSLParameters sslParameters) { + // If you run into problem on Android (NoSuchMethodException), check out the wiki https://github.com/TooTallNate/Java-WebSocket/wiki/No-such-method-error-setEndpointIdentificationAlgorithm + // Perform hostname validation + sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); + } + + /** + * Extract the specified port + * + * @return the specified port or the default port for the specific scheme + */ + private int getPort() { + int port = uri.getPort(); + String scheme = uri.getScheme(); + if ("wss".equals(scheme)) { + return port == -1 ? WebSocketImpl.DEFAULT_WSS_PORT : port; + } else if ("ws".equals(scheme)) { + return port == -1 ? WebSocketImpl.DEFAULT_PORT : port; + } else { + throw new IllegalArgumentException("unknown scheme: " + scheme); + } + } + + /** + * Create and send the handshake to the other endpoint + * + * @throws InvalidHandshakeException a invalid handshake was created + */ + private void sendHandshake() throws InvalidHandshakeException { + String path; + String part1 = uri.getRawPath(); + String part2 = uri.getRawQuery(); + if (part1 == null || part1.length() == 0) { + path = "/"; + } else { + path = part1; + } + if (part2 != null) { + path += '?' + part2; + } + int port = getPort(); + String host = uri.getHost() + ( + (port != WebSocketImpl.DEFAULT_PORT && port != WebSocketImpl.DEFAULT_WSS_PORT) + ? ":" + port + : ""); + + HandshakeImpl1Client handshake = new HandshakeImpl1Client(); + handshake.setResourceDescriptor(path); + handshake.put("Host", host); + if (headers != null) { + for (Map.Entry kv : headers.entrySet()) { + handshake.put(kv.getKey(), kv.getValue()); + } + } + engine.startHandshake(handshake); + } + + /** + * This represents the state of the connection. + */ + public ReadyState getReadyState() { + return engine.getReadyState(); + } + + /** + * Calls subclass' implementation of onMessage. + */ + @Override + public final void onWebsocketMessage(WebSocket conn, String message) { + onMessage(message); + } + + @Override + public final void onWebsocketMessage(WebSocket conn, ByteBuffer blob) { + onMessage(blob); + } + + /** + * Calls subclass' implementation of onOpen. + */ + @Override + public final void onWebsocketOpen(WebSocket conn, Handshakedata handshake) { + startConnectionLostTimer(); + onOpen((ServerHandshake) handshake); + connectLatch.countDown(); + } + + /** + * Calls subclass' implementation of onClose. + */ + @Override + public final void onWebsocketClose(WebSocket conn, int code, String reason, boolean remote) { + stopConnectionLostTimer(); + if (writeThread != null) { + writeThread.interrupt(); + } + onClose(code, reason, remote); + connectLatch.countDown(); + closeLatch.countDown(); + } + + /** + * Calls subclass' implementation of onIOError. + */ + @Override + public final void onWebsocketError(WebSocket conn, Exception ex) { + onError(ex); + } + + @Override + public final void onWriteDemand(WebSocket conn) { + // nothing to do + } + + @Override + public void onWebsocketCloseInitiated(WebSocket conn, int code, String reason) { + onCloseInitiated(code, reason); + } + + @Override + public void onWebsocketClosing(WebSocket conn, int code, String reason, boolean remote) { + onClosing(code, reason, remote); + } + + /** + * Send when this peer sends a close handshake + * + * @param code The codes can be looked up here: {@link CloseFrame} + * @param reason Additional information string + */ + public void onCloseInitiated(int code, String reason) { + //To overwrite + } + + /** + * Called as soon as no further frames are accepted + * + * @param code The codes can be looked up here: {@link CloseFrame} + * @param reason Additional information string + * @param remote Returns whether or not the closing of the connection was initiated by the remote + * host. + */ + public void onClosing(int code, String reason, boolean remote) { + //To overwrite + } + + /** + * Getter for the engine + * + * @return the engine + */ + public WebSocket getConnection() { + return engine; + } + + @Override + public InetSocketAddress getLocalSocketAddress(WebSocket conn) { + if (socket != null) { + return (InetSocketAddress) socket.getLocalSocketAddress(); + } + return null; + } + + @Override + public InetSocketAddress getRemoteSocketAddress(WebSocket conn) { + if (socket != null) { + return (InetSocketAddress) socket.getRemoteSocketAddress(); + } + return null; + } + + // ABSTRACT METHODS ///////////////////////////////////////////////////////// + + /** + * Called after an opening handshake has been performed and the given websocket is ready to be + * written on. + * + * @param handshakedata The handshake of the websocket instance + */ + public abstract void onOpen(ServerHandshake handshakedata); + + /** + * Callback for string messages received from the remote host + * + * @param message The UTF-8 decoded message that was received. + * @see #onMessage(ByteBuffer) + **/ + public abstract void onMessage(String message); + + /** + * Called after the websocket connection has been closed. + * + * @param code The codes can be looked up here: {@link CloseFrame} + * @param reason Additional information string + * @param remote Returns whether or not the closing of the connection was initiated by the remote + * host. + **/ + public abstract void onClose(int code, String reason, boolean remote); + + /** + * Called when errors occurs. If an error causes the websocket connection to fail {@link + * #onClose(int, String, boolean)} will be called additionally.
This method will be called + * primarily because of IO or protocol errors.
If the given exception is an RuntimeException + * that probably means that you encountered a bug.
+ * + * @param ex The exception causing this error + **/ + public abstract void onError(Exception ex); + + /** + * Callback for binary messages received from the remote host + * + * @param bytes The binary message that was received. + * @see #onMessage(String) + **/ + public void onMessage(ByteBuffer bytes) { + //To overwrite + } + + + private class WebsocketWriteThread implements Runnable { + + private final WebSocketClient webSocketClient; + + WebsocketWriteThread(WebSocketClient webSocketClient) { + this.webSocketClient = webSocketClient; + } + + @Override + public void run() { + Thread.currentThread().setName("WebSocketWriteThread-" + Thread.currentThread().getId()); + try { + runWriteData(); + } catch (IOException e) { + handleIOException(e); + } finally { + closeSocket(); + } + } + + /** + * Write the data into the outstream + * + * @throws IOException if write or flush did not work + */ + private void runWriteData() throws IOException { + try { + while (!Thread.interrupted()) { + ByteBuffer buffer = engine.outQueue.take(); + ostream.write(buffer.array(), 0, buffer.limit()); + ostream.flush(); + } + } catch (InterruptedException e) { + for (ByteBuffer buffer : engine.outQueue) { + ostream.write(buffer.array(), 0, buffer.limit()); + ostream.flush(); + } + Thread.currentThread().interrupt(); + } + } + + /** + * Closing the socket + */ + private void closeSocket() { + try { + if (socket != null) { + socket.close(); + } + } catch (IOException ex) { + onWebsocketError(webSocketClient, ex); + } + } + } + + + /** + * Method to set a proxy for this connection + * + * @param proxy the proxy to use for this websocket client + */ + public void setProxy(Proxy proxy) { + if (proxy == null) { + throw new IllegalArgumentException(); + } + this.proxy = proxy; + } + + /** + * Accepts bound and unbound sockets.
This method must be called before connect. + * If the given socket is not yet bound it will be bound to the uri specified in the constructor. + * + * @param socket The socket which should be used for the connection + * @deprecated use setSocketFactory + */ + @Deprecated + public void setSocket(Socket socket) { + if (this.socket != null) { + throw new IllegalStateException("socket has already been set"); + } + this.socket = socket; + } + + /** + * Accepts a SocketFactory.
This method must be called before connect. The socket + * will be bound to the uri specified in the constructor. + * + * @param socketFactory The socket factory which should be used for the connection. + */ + public void setSocketFactory(SocketFactory socketFactory) { + this.socketFactory = socketFactory; + } + + @Override + public void sendFragmentedFrame(Opcode op, ByteBuffer buffer, boolean fin) { + engine.sendFragmentedFrame(op, buffer, fin); + } + + @Override + public boolean isOpen() { + return engine.isOpen(); + } + + @Override + public boolean isFlushAndClose() { + return engine.isFlushAndClose(); + } + + @Override + public boolean isClosed() { + return engine.isClosed(); + } + + @Override + public boolean isClosing() { + return engine.isClosing(); + } + + @Override + public boolean hasBufferedData() { + return engine.hasBufferedData(); + } + + @Override + public void close(int code) { + engine.close(code); + } + + @Override + public void close(int code, String message) { + engine.close(code, message); + } + + @Override + public void closeConnection(int code, String message) { + engine.closeConnection(code, message); + } + + @Override + public void send(ByteBuffer bytes) { + engine.send(bytes); + } + + @Override + public void sendFrame(Framedata framedata) { + engine.sendFrame(framedata); + } + + @Override + public void sendFrame(Collection frames) { + engine.sendFrame(frames); + } + + @Override + public InetSocketAddress getLocalSocketAddress() { + return engine.getLocalSocketAddress(); + } + + @Override + public InetSocketAddress getRemoteSocketAddress() { + return engine.getRemoteSocketAddress(); + } + + @Override + public String getResourceDescriptor() { + return uri.getPath(); + } + + @Override + public boolean hasSSLSupport() { + return socket instanceof SSLSocket; + } + + @Override + public SSLSession getSSLSession() { + if (!hasSSLSupport()) { + throw new IllegalArgumentException( + "This websocket uses ws instead of wss. No SSLSession available."); + } + return ((SSLSocket)socket).getSession(); + } + + @Override + public IProtocol getProtocol() { + return engine.getProtocol(); + } + + /** + * Method to give some additional info for specific IOExceptions + * + * @param e the IOException causing a eot. + */ + private void handleIOException(IOException e) { + if (e instanceof SSLException) { + onError(e); + } + engine.eot(); + } } diff --git a/src/main/java/org/java_websocket/client/package-info.java b/src/main/java/org/java_websocket/client/package-info.java new file mode 100644 index 000000000..e6d799d5f --- /dev/null +++ b/src/main/java/org/java_websocket/client/package-info.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +/** + * This package encapsulates all implementations in relation with the WebSocketClient. + */ +package org.java_websocket.client; \ No newline at end of file diff --git a/src/main/java/org/java_websocket/drafts/Draft.java b/src/main/java/org/java_websocket/drafts/Draft.java index 65b34de8f..2cda1e5ca 100644 --- a/src/main/java/org/java_websocket/drafts/Draft.java +++ b/src/main/java/org/java_websocket/drafts/Draft.java @@ -1,3 +1,28 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.drafts; import java.nio.ByteBuffer; @@ -5,17 +30,20 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; - -import org.java_websocket.WebSocket.Role; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.enums.CloseHandshakeType; +import org.java_websocket.enums.HandshakeState; +import org.java_websocket.enums.Opcode; +import org.java_websocket.enums.Role; import org.java_websocket.exceptions.IncompleteHandshakeException; import org.java_websocket.exceptions.InvalidDataException; import org.java_websocket.exceptions.InvalidHandshakeException; -import org.java_websocket.exceptions.LimitExedeedException; +import org.java_websocket.framing.BinaryFrame; import org.java_websocket.framing.CloseFrame; -import org.java_websocket.framing.FrameBuilder; +import org.java_websocket.framing.ContinuousFrame; +import org.java_websocket.framing.DataFrame; import org.java_websocket.framing.Framedata; -import org.java_websocket.framing.Framedata.Opcode; -import org.java_websocket.framing.FramedataImpl1; +import org.java_websocket.framing.TextFrame; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.handshake.ClientHandshakeBuilder; import org.java_websocket.handshake.HandshakeBuilder; @@ -27,202 +55,301 @@ import org.java_websocket.util.Charsetfunctions; /** - * Base class for everything of a websocket specification which is not common such as the way the handshake is read or frames are transfered. + * Base class for everything of a websocket specification which is not common such as the way the + * handshake is read or frames are transferred. **/ public abstract class Draft { - public enum HandshakeState { - /** Handshake matched this Draft successfully */ - MATCHED, - /** Handshake is does not match this Draft */ - NOT_MATCHED - } - public enum CloseHandshakeType { - NONE, ONEWAY, TWOWAY - } - - public static int MAX_FAME_SIZE = 1000 * 1; - public static int INITIAL_FAMESIZE = 64; - - public static final byte[] FLASH_POLICY_REQUEST = Charsetfunctions.utf8Bytes( "\0" ); - - /** In some cases the handshake will be parsed different depending on whether */ - protected Role role = null; - - protected Opcode continuousFrameType = null; - - public static ByteBuffer readLine( ByteBuffer buf ) { - ByteBuffer sbuf = ByteBuffer.allocate( buf.remaining() ); - byte prev = '0'; - byte cur = '0'; - while ( buf.hasRemaining() ) { - prev = cur; - cur = buf.get(); - sbuf.put( cur ); - if( prev == (byte) '\r' && cur == (byte) '\n' ) { - sbuf.limit( sbuf.position() - 2 ); - sbuf.position( 0 ); - return sbuf; - - } - } - // ensure that there wont be any bytes skipped - buf.position( buf.position() - sbuf.position() ); - return null; - } - - public static String readStringLine( ByteBuffer buf ) { - ByteBuffer b = readLine( buf ); - return b == null ? null : Charsetfunctions.stringAscii( b.array(), 0, b.limit() ); - } - - public static HandshakeBuilder translateHandshakeHttp( ByteBuffer buf, Role role ) throws InvalidHandshakeException , IncompleteHandshakeException { - HandshakeBuilder handshake; - - String line = readStringLine( buf ); - if( line == null ) - throw new IncompleteHandshakeException( buf.capacity() + 128 ); - - String[] firstLineTokens = line.split( " ", 3 );// eg. HTTP/1.1 101 Switching the Protocols - if( firstLineTokens.length != 3 ) { - throw new InvalidHandshakeException(); - } - - if( role == Role.CLIENT ) { - // translating/parsing the response from the SERVER - handshake = new HandshakeImpl1Server(); - ServerHandshakeBuilder serverhandshake = (ServerHandshakeBuilder) handshake; - serverhandshake.setHttpStatus( Short.parseShort( firstLineTokens[ 1 ] ) ); - serverhandshake.setHttpStatusMessage( firstLineTokens[ 2 ] ); - } else { - // translating/parsing the request from the CLIENT - ClientHandshakeBuilder clienthandshake = new HandshakeImpl1Client(); - clienthandshake.setResourceDescriptor( firstLineTokens[ 1 ] ); - handshake = clienthandshake; - } - - line = readStringLine( buf ); - while ( line != null && line.length() > 0 ) { - String[] pair = line.split( ":", 2 ); - if( pair.length != 2 ) - throw new InvalidHandshakeException( "not an http header" ); - handshake.put( pair[ 0 ], pair[ 1 ].replaceFirst( "^ +", "" ) ); - line = readStringLine( buf ); - } - if( line == null ) - throw new IncompleteHandshakeException(); - return handshake; - } - - public abstract HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) throws InvalidHandshakeException; - - public abstract HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException; - - protected boolean basicAccept( Handshakedata handshakedata ) { - return handshakedata.getFieldValue( "Upgrade" ).equalsIgnoreCase( "websocket" ) && handshakedata.getFieldValue( "Connection" ).toLowerCase( Locale.ENGLISH ).contains( "upgrade" ); - } - - public abstract ByteBuffer createBinaryFrame( Framedata framedata ); // TODO Allow to send data on the base of an Iterator or InputStream - - public abstract List createFrames( ByteBuffer binary, boolean mask ); - - public abstract List createFrames( String text, boolean mask ); - - public List continuousFrame( Opcode op, ByteBuffer buffer, boolean fin ) { - if( op != Opcode.BINARY && op != Opcode.TEXT && op != Opcode.TEXT ) { - throw new IllegalArgumentException( "Only Opcode.BINARY or Opcode.TEXT are allowed" ); - } - - if( continuousFrameType != null ) { - continuousFrameType = Opcode.CONTINUOUS; - } else { - continuousFrameType = op; - } - - FrameBuilder bui = new FramedataImpl1( continuousFrameType ); - try { - bui.setPayload( buffer ); - } catch ( InvalidDataException e ) { - throw new RuntimeException( e ); // can only happen when one builds close frames(Opcode.Close) - } - bui.setFin( fin ); - if( fin ) { - continuousFrameType = null; - } else { - continuousFrameType = op; - } - return Collections.singletonList( (Framedata) bui ); - } - - public abstract void reset(); - - public List createHandshake( Handshakedata handshakedata, Role ownrole ) { - return createHandshake( handshakedata, ownrole, true ); - } - - public List createHandshake( Handshakedata handshakedata, Role ownrole, boolean withcontent ) { - StringBuilder bui = new StringBuilder( 100 ); - if( handshakedata instanceof ClientHandshake ) { - bui.append( "GET " ); - bui.append( ( (ClientHandshake) handshakedata ).getResourceDescriptor() ); - bui.append( " HTTP/1.1" ); - } else if( handshakedata instanceof ServerHandshake ) { - bui.append( "HTTP/1.1 101 " + ( (ServerHandshake) handshakedata ).getHttpStatusMessage() ); - } else { - throw new RuntimeException( "unknow role" ); - } - bui.append( "\r\n" ); - Iterator it = handshakedata.iterateHttpFields(); - while ( it.hasNext() ) { - String fieldname = it.next(); - String fieldvalue = handshakedata.getFieldValue( fieldname ); - bui.append( fieldname ); - bui.append( ": " ); - bui.append( fieldvalue ); - bui.append( "\r\n" ); - } - bui.append( "\r\n" ); - byte[] httpheader = Charsetfunctions.asciiBytes( bui.toString() ); - - byte[] content = withcontent ? handshakedata.getContent() : null; - ByteBuffer bytebuffer = ByteBuffer.allocate( ( content == null ? 0 : content.length ) + httpheader.length ); - bytebuffer.put( httpheader ); - if( content != null ) - bytebuffer.put( content ); - bytebuffer.flip(); - return Collections.singletonList( bytebuffer ); - } - - public abstract ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) throws InvalidHandshakeException; - - public abstract HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException; - - public abstract List translateFrame( ByteBuffer buffer ) throws InvalidDataException; - - public abstract CloseHandshakeType getCloseHandshakeType(); - - /** - * Drafts must only be by one websocket at all. To prevent drafts to be used more than once the Websocket implementation should call this method in order to create a new usable version of a given draft instance.
- * The copy can be safely used in conjunction with a new websocket connection. - * */ - public abstract Draft copyInstance(); - - public Handshakedata translateHandshake( ByteBuffer buf ) throws InvalidHandshakeException { - return translateHandshakeHttp( buf, role ); - } - - public int checkAlloc( int bytecount ) throws LimitExedeedException , InvalidDataException { - if( bytecount < 0 ) - throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Negative count" ); - return bytecount; - } - - public void setParseMode( Role role ) { - this.role = role; - } - - public Role getRole() { - return role; - } + /** + * In some cases the handshake will be parsed different depending on whether + */ + protected Role role = null; + + protected Opcode continuousFrameType = null; + + public static ByteBuffer readLine(ByteBuffer buf) { + ByteBuffer sbuf = ByteBuffer.allocate(buf.remaining()); + byte prev; + byte cur = '0'; + while (buf.hasRemaining()) { + prev = cur; + cur = buf.get(); + sbuf.put(cur); + if (prev == (byte) '\r' && cur == (byte) '\n') { + sbuf.limit(sbuf.position() - 2); + sbuf.position(0); + return sbuf; + + } + } + // ensure that there wont be any bytes skipped + buf.position(buf.position() - sbuf.position()); + return null; + } + + public static String readStringLine(ByteBuffer buf) { + ByteBuffer b = readLine(buf); + return b == null ? null : Charsetfunctions.stringAscii(b.array(), 0, b.limit()); + } + + public static HandshakeBuilder translateHandshakeHttp(ByteBuffer buf, Role role) + throws InvalidHandshakeException { + HandshakeBuilder handshake; + + String line = readStringLine(buf); + if (line == null) { + throw new IncompleteHandshakeException(buf.capacity() + 128); + } + + String[] firstLineTokens = line.split(" ", 3);// eg. HTTP/1.1 101 Switching the Protocols + if (firstLineTokens.length != 3) { + throw new InvalidHandshakeException(); + } + if (role == Role.CLIENT) { + handshake = translateHandshakeHttpClient(firstLineTokens, line); + } else { + handshake = translateHandshakeHttpServer(firstLineTokens, line); + } + line = readStringLine(buf); + while (line != null && line.length() > 0) { + String[] pair = line.split(":", 2); + if (pair.length != 2) { + throw new InvalidHandshakeException("not an http header"); + } + // If the handshake contains already a specific key, append the new value + if (handshake.hasFieldValue(pair[0])) { + handshake.put(pair[0], + handshake.getFieldValue(pair[0]) + "; " + pair[1].replaceFirst("^ +", "")); + } else { + handshake.put(pair[0], pair[1].replaceFirst("^ +", "")); + } + line = readStringLine(buf); + } + if (line == null) { + throw new IncompleteHandshakeException(); + } + return handshake; + } + + /** + * Checking the handshake for the role as server + * + * @param firstLineTokens the token of the first line split as as an string array + * @param line the whole line + * @return a handshake + */ + private static HandshakeBuilder translateHandshakeHttpServer(String[] firstLineTokens, + String line) throws InvalidHandshakeException { + // translating/parsing the request from the CLIENT + if (!"GET".equalsIgnoreCase(firstLineTokens[0])) { + throw new InvalidHandshakeException(String + .format("Invalid request method received: %s Status line: %s", firstLineTokens[0], line)); + } + if (!"HTTP/1.1".equalsIgnoreCase(firstLineTokens[2])) { + throw new InvalidHandshakeException(String + .format("Invalid status line received: %s Status line: %s", firstLineTokens[2], line)); + } + ClientHandshakeBuilder clienthandshake = new HandshakeImpl1Client(); + clienthandshake.setResourceDescriptor(firstLineTokens[1]); + return clienthandshake; + } + + /** + * Checking the handshake for the role as client + * + * @param firstLineTokens the token of the first line split as as an string array + * @param line the whole line + * @return a handshake + */ + private static HandshakeBuilder translateHandshakeHttpClient(String[] firstLineTokens, + String line) throws InvalidHandshakeException { + // translating/parsing the response from the SERVER + if (!"101".equals(firstLineTokens[1])) { + throw new InvalidHandshakeException(String + .format("Invalid status code received: %s Status line: %s", firstLineTokens[1], line)); + } + if (!"HTTP/1.1".equalsIgnoreCase(firstLineTokens[0])) { + throw new InvalidHandshakeException(String + .format("Invalid status line received: %s Status line: %s", firstLineTokens[0], line)); + } + HandshakeBuilder handshake = new HandshakeImpl1Server(); + ServerHandshakeBuilder serverhandshake = (ServerHandshakeBuilder) handshake; + serverhandshake.setHttpStatus(Short.parseShort(firstLineTokens[1])); + serverhandshake.setHttpStatusMessage(firstLineTokens[2]); + return handshake; + } + + public abstract HandshakeState acceptHandshakeAsClient(ClientHandshake request, + ServerHandshake response) throws InvalidHandshakeException; + + public abstract HandshakeState acceptHandshakeAsServer(ClientHandshake handshakedata) + throws InvalidHandshakeException; + + protected boolean basicAccept(Handshakedata handshakedata) { + return handshakedata.getFieldValue("Upgrade").equalsIgnoreCase("websocket") && handshakedata + .getFieldValue("Connection").toLowerCase(Locale.ENGLISH).contains("upgrade"); + } + + public abstract ByteBuffer createBinaryFrame(Framedata framedata); + + public abstract List createFrames(ByteBuffer binary, boolean mask); + + public abstract List createFrames(String text, boolean mask); + + + /** + * Handle the frame specific to the draft + * + * @param webSocketImpl the websocketimpl used for this draft + * @param frame the frame which is supposed to be handled + * @throws InvalidDataException will be thrown on invalid data + */ + public abstract void processFrame(WebSocketImpl webSocketImpl, Framedata frame) + throws InvalidDataException; + + public List continuousFrame(Opcode op, ByteBuffer buffer, boolean fin) { + if (op != Opcode.BINARY && op != Opcode.TEXT) { + throw new IllegalArgumentException("Only Opcode.BINARY or Opcode.TEXT are allowed"); + } + DataFrame bui = null; + if (continuousFrameType != null) { + bui = new ContinuousFrame(); + } else { + continuousFrameType = op; + if (op == Opcode.BINARY) { + bui = new BinaryFrame(); + } else if (op == Opcode.TEXT) { + bui = new TextFrame(); + } + } + bui.setPayload(buffer); + bui.setFin(fin); + try { + bui.isValid(); + } catch (InvalidDataException e) { + throw new IllegalArgumentException( + e); // can only happen when one builds close frames(Opcode.Close) + } + if (fin) { + continuousFrameType = null; + } else { + continuousFrameType = op; + } + return Collections.singletonList((Framedata) bui); + } + + public abstract void reset(); + + /** + * @deprecated use createHandshake without the role + */ + @Deprecated + public List createHandshake(Handshakedata handshakedata, Role ownrole) { + return createHandshake(handshakedata); + } + + public List createHandshake(Handshakedata handshakedata) { + return createHandshake(handshakedata, true); + } + + /** + * @deprecated use createHandshake without the role since it does not have any effect + */ + @Deprecated + public List createHandshake(Handshakedata handshakedata, Role ownrole, + boolean withcontent) { + return createHandshake(handshakedata, withcontent); + } + + public List createHandshake(Handshakedata handshakedata, boolean withcontent) { + StringBuilder bui = new StringBuilder(100); + if (handshakedata instanceof ClientHandshake) { + bui.append("GET ").append(((ClientHandshake) handshakedata).getResourceDescriptor()) + .append(" HTTP/1.1"); + } else if (handshakedata instanceof ServerHandshake) { + bui.append("HTTP/1.1 101 ").append(((ServerHandshake) handshakedata).getHttpStatusMessage()); + } else { + throw new IllegalArgumentException("unknown role"); + } + bui.append("\r\n"); + Iterator it = handshakedata.iterateHttpFields(); + while (it.hasNext()) { + String fieldname = it.next(); + String fieldvalue = handshakedata.getFieldValue(fieldname); + bui.append(fieldname); + bui.append(": "); + bui.append(fieldvalue); + bui.append("\r\n"); + } + bui.append("\r\n"); + byte[] httpheader = Charsetfunctions.asciiBytes(bui.toString()); + + byte[] content = withcontent ? handshakedata.getContent() : null; + ByteBuffer bytebuffer = ByteBuffer + .allocate((content == null ? 0 : content.length) + httpheader.length); + bytebuffer.put(httpheader); + if (content != null) { + bytebuffer.put(content); + } + bytebuffer.flip(); + return Collections.singletonList(bytebuffer); + } + + public abstract ClientHandshakeBuilder postProcessHandshakeRequestAsClient( + ClientHandshakeBuilder request) throws InvalidHandshakeException; + + public abstract HandshakeBuilder postProcessHandshakeResponseAsServer(ClientHandshake request, + ServerHandshakeBuilder response) throws InvalidHandshakeException; + + public abstract List translateFrame(ByteBuffer buffer) throws InvalidDataException; + + public abstract CloseHandshakeType getCloseHandshakeType(); + + /** + * Drafts must only be by one websocket at all. To prevent drafts to be used more than once the + * Websocket implementation should call this method in order to create a new usable version of a + * given draft instance.
The copy can be safely used in conjunction with a new websocket + * connection. + * + * @return a copy of the draft + */ + public abstract Draft copyInstance(); + + public Handshakedata translateHandshake(ByteBuffer buf) throws InvalidHandshakeException { + return translateHandshakeHttp(buf, role); + } + + public int checkAlloc(int bytecount) throws InvalidDataException { + if (bytecount < 0) { + throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "Negative count"); + } + return bytecount; + } + + int readVersion(Handshakedata handshakedata) { + String vers = handshakedata.getFieldValue("Sec-WebSocket-Version"); + if (vers.length() > 0) { + int v; + try { + v = Integer.parseInt(vers.trim()); + return v; + } catch (NumberFormatException e) { + return -1; + } + } + return -1; + } + + public void setParseMode(Role role) { + this.role = role; + } + + public Role getRole() { + return role; + } + + public String toString() { + return getClass().getSimpleName(); + } } diff --git a/src/main/java/org/java_websocket/drafts/Draft_10.java b/src/main/java/org/java_websocket/drafts/Draft_10.java deleted file mode 100644 index 305460a52..000000000 --- a/src/main/java/org/java_websocket/drafts/Draft_10.java +++ /dev/null @@ -1,397 +0,0 @@ -package org.java_websocket.drafts; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Random; - -import org.java_websocket.WebSocket.Role; -import org.java_websocket.exceptions.InvalidDataException; -import org.java_websocket.exceptions.InvalidFrameException; -import org.java_websocket.exceptions.InvalidHandshakeException; -import org.java_websocket.exceptions.LimitExedeedException; -import org.java_websocket.exceptions.NotSendableException; -import org.java_websocket.framing.CloseFrameBuilder; -import org.java_websocket.framing.FrameBuilder; -import org.java_websocket.framing.Framedata; -import org.java_websocket.framing.Framedata.Opcode; -import org.java_websocket.framing.FramedataImpl1; -import org.java_websocket.handshake.ClientHandshake; -import org.java_websocket.handshake.ClientHandshakeBuilder; -import org.java_websocket.handshake.HandshakeBuilder; -import org.java_websocket.handshake.Handshakedata; -import org.java_websocket.handshake.ServerHandshake; -import org.java_websocket.handshake.ServerHandshakeBuilder; -import org.java_websocket.util.Base64; -import org.java_websocket.util.Charsetfunctions; - -public class Draft_10 extends Draft { - - private class IncompleteException extends Throwable { - - /** - * It's Serializable. - */ - private static final long serialVersionUID = 7330519489840500997L; - - private int preferedsize; - public IncompleteException( int preferedsize ) { - this.preferedsize = preferedsize; - } - public int getPreferedSize() { - return preferedsize; - } - } - - public static int readVersion( Handshakedata handshakedata ) { - String vers = handshakedata.getFieldValue( "Sec-WebSocket-Version" ); - if( vers.length() > 0 ) { - int v; - try { - v = new Integer( vers.trim() ); - return v; - } catch ( NumberFormatException e ) { - return -1; - } - } - return -1; - } - - private ByteBuffer incompleteframe; - private Framedata fragmentedframe = null; - - private final Random reuseableRandom = new Random(); - - @Override - public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) throws InvalidHandshakeException { - if( !request.hasFieldValue( "Sec-WebSocket-Key" ) || !response.hasFieldValue( "Sec-WebSocket-Accept" ) ) - return HandshakeState.NOT_MATCHED; - - String seckey_answere = response.getFieldValue( "Sec-WebSocket-Accept" ); - String seckey_challenge = request.getFieldValue( "Sec-WebSocket-Key" ); - seckey_challenge = generateFinalKey( seckey_challenge ); - - if( seckey_challenge.equals( seckey_answere ) ) - return HandshakeState.MATCHED; - return HandshakeState.NOT_MATCHED; - } - - @Override - public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException { - // Sec-WebSocket-Origin is only required for browser clients - int v = readVersion( handshakedata ); - if( v == 7 || v == 8 )// g - return basicAccept( handshakedata ) ? HandshakeState.MATCHED : HandshakeState.NOT_MATCHED; - return HandshakeState.NOT_MATCHED; - } - - @Override - public ByteBuffer createBinaryFrame( Framedata framedata ) { - ByteBuffer mes = framedata.getPayloadData(); - boolean mask = role == Role.CLIENT; // framedata.getTransfereMasked(); - int sizebytes = mes.remaining() <= 125 ? 1 : mes.remaining() <= 65535 ? 2 : 8; - ByteBuffer buf = ByteBuffer.allocate( 1 + ( sizebytes > 1 ? sizebytes + 1 : sizebytes ) + ( mask ? 4 : 0 ) + mes.remaining() ); - byte optcode = fromOpcode( framedata.getOpcode() ); - byte one = (byte) ( framedata.isFin() ? -128 : 0 ); - one |= optcode; - buf.put( one ); - byte[] payloadlengthbytes = toByteArray( mes.remaining(), sizebytes ); - assert ( payloadlengthbytes.length == sizebytes ); - - if( sizebytes == 1 ) { - buf.put( (byte) ( (byte) payloadlengthbytes[ 0 ] | ( mask ? (byte) -128 : 0 ) ) ); - } else if( sizebytes == 2 ) { - buf.put( (byte) ( (byte) 126 | ( mask ? (byte) -128 : 0 ) ) ); - buf.put( payloadlengthbytes ); - } else if( sizebytes == 8 ) { - buf.put( (byte) ( (byte) 127 | ( mask ? (byte) -128 : 0 ) ) ); - buf.put( payloadlengthbytes ); - } else - throw new RuntimeException( "Size representation not supported/specified" ); - - if( mask ) { - ByteBuffer maskkey = ByteBuffer.allocate( 4 ); - maskkey.putInt( reuseableRandom.nextInt() ); - buf.put( maskkey.array() ); - for( int i = 0 ; mes.hasRemaining() ; i++ ) { - buf.put( (byte) ( mes.get() ^ maskkey.get( i % 4 ) ) ); - } - } else - buf.put( mes ); - // translateFrame ( buf.array () , buf.array ().length ); - assert ( buf.remaining() == 0 ) : buf.remaining(); - buf.flip(); - - return buf; - } - - @Override - public List createFrames( ByteBuffer binary, boolean mask ) { - FrameBuilder curframe = new FramedataImpl1(); - try { - curframe.setPayload( binary ); - } catch ( InvalidDataException e ) { - throw new NotSendableException( e ); - } - curframe.setFin( true ); - curframe.setOptcode( Opcode.BINARY ); - curframe.setTransferemasked( mask ); - return Collections.singletonList( (Framedata) curframe ); - } - - @Override - public List createFrames( String text, boolean mask ) { - FrameBuilder curframe = new FramedataImpl1(); - try { - curframe.setPayload( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( text ) ) ); - } catch ( InvalidDataException e ) { - throw new NotSendableException( e ); - } - curframe.setFin( true ); - curframe.setOptcode( Opcode.TEXT ); - curframe.setTransferemasked( mask ); - return Collections.singletonList( (Framedata) curframe ); - } - - private byte fromOpcode( Opcode opcode ) { - if( opcode == Opcode.CONTINUOUS ) - return 0; - else if( opcode == Opcode.TEXT ) - return 1; - else if( opcode == Opcode.BINARY ) - return 2; - else if( opcode == Opcode.CLOSING ) - return 8; - else if( opcode == Opcode.PING ) - return 9; - else if( opcode == Opcode.PONG ) - return 10; - throw new RuntimeException( "Don't know how to handle " + opcode.toString() ); - } - - private String generateFinalKey( String in ) { - String seckey = in.trim(); - String acc = seckey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - MessageDigest sh1; - try { - sh1 = MessageDigest.getInstance( "SHA1" ); - } catch ( NoSuchAlgorithmException e ) { - throw new RuntimeException( e ); - } - return Base64.encodeBytes( sh1.digest( acc.getBytes() ) ); - } - - @Override - public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) { - request.put( "Upgrade", "websocket" ); - request.put( "Connection", "Upgrade" ); // to respond to a Connection keep alives - request.put( "Sec-WebSocket-Version", "8" ); - - byte[] random = new byte[ 16 ]; - reuseableRandom.nextBytes( random ); - request.put( "Sec-WebSocket-Key", Base64.encodeBytes( random ) ); - - return request; - } - - @Override - public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException { - response.put( "Upgrade", "websocket" ); - response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alives - response.setHttpStatusMessage( "Switching Protocols" ); - String seckey = request.getFieldValue( "Sec-WebSocket-Key" ); - if( seckey == null ) - throw new InvalidHandshakeException( "missing Sec-WebSocket-Key" ); - response.put( "Sec-WebSocket-Accept", generateFinalKey( seckey ) ); - return response; - } - - private byte[] toByteArray( long val, int bytecount ) { - byte[] buffer = new byte[ bytecount ]; - int highest = 8 * bytecount - 8; - for( int i = 0 ; i < bytecount ; i++ ) { - buffer[ i ] = (byte) ( val >>> ( highest - 8 * i ) ); - } - return buffer; - } - - private Opcode toOpcode( byte opcode ) throws InvalidFrameException { - switch ( opcode ) { - case 0: - return Opcode.CONTINUOUS; - case 1: - return Opcode.TEXT; - case 2: - return Opcode.BINARY; - // 3-7 are not yet defined - case 8: - return Opcode.CLOSING; - case 9: - return Opcode.PING; - case 10: - return Opcode.PONG; - // 11-15 are not yet defined - default : - throw new InvalidFrameException( "unknow optcode " + (short) opcode ); - } - } - - @Override - public List translateFrame( ByteBuffer buffer ) throws LimitExedeedException , InvalidDataException { - List frames = new LinkedList(); - Framedata cur; - - if( incompleteframe != null ) { - // complete an incomplete frame - while ( true ) { - try { - buffer.mark(); - int available_next_byte_count = buffer.remaining();// The number of bytes received - int expected_next_byte_count = incompleteframe.remaining();// The number of bytes to complete the incomplete frame - - if( expected_next_byte_count > available_next_byte_count ) { - // did not receive enough bytes to complete the frame - incompleteframe.put( buffer.array(), buffer.position(), available_next_byte_count ); - buffer.position( buffer.position() + available_next_byte_count ); - return Collections.emptyList(); - } - incompleteframe.put( buffer.array(), buffer.position(), expected_next_byte_count ); - buffer.position( buffer.position() + expected_next_byte_count ); - - cur = translateSingleFrame( (ByteBuffer) incompleteframe.duplicate().position( 0 ) ); - frames.add( cur ); - incompleteframe = null; - break; // go on with the normal frame receival - } catch ( IncompleteException e ) { - // extending as much as suggested - int oldsize = incompleteframe.limit(); - ByteBuffer extendedframe = ByteBuffer.allocate( checkAlloc( e.getPreferedSize() ) ); - assert ( extendedframe.limit() > incompleteframe.limit() ); - incompleteframe.rewind(); - extendedframe.put( incompleteframe ); - incompleteframe = extendedframe; - - return translateFrame( buffer ); - } - } - } - - while ( buffer.hasRemaining() ) {// Read as much as possible full frames - buffer.mark(); - try { - cur = translateSingleFrame( buffer ); - frames.add( cur ); - } catch ( IncompleteException e ) { - // remember the incomplete data - buffer.reset(); - int pref = e.getPreferedSize(); - incompleteframe = ByteBuffer.allocate( checkAlloc( pref ) ); - incompleteframe.put( buffer ); - break; - } - } - return frames; - } - - public Framedata translateSingleFrame( ByteBuffer buffer ) throws IncompleteException , InvalidDataException { - int maxpacketsize = buffer.remaining(); - int realpacketsize = 2; - if( maxpacketsize < realpacketsize ) - throw new IncompleteException( realpacketsize ); - byte b1 = buffer.get( /*0*/); - boolean FIN = b1 >> 8 != 0; - byte rsv = (byte) ( ( b1 & ~(byte) 128 ) >> 4 ); - if( rsv != 0 ) - throw new InvalidFrameException( "bad rsv " + rsv ); - byte b2 = buffer.get( /*1*/); - boolean MASK = ( b2 & -128 ) != 0; - int payloadlength = (byte) ( b2 & ~(byte) 128 ); - Opcode optcode = toOpcode( (byte) ( b1 & 15 ) ); - - if( !FIN ) { - if( optcode == Opcode.PING || optcode == Opcode.PONG || optcode == Opcode.CLOSING ) { - throw new InvalidFrameException( "control frames may no be fragmented" ); - } - } - - if( payloadlength >= 0 && payloadlength <= 125 ) { - } else { - if( optcode == Opcode.PING || optcode == Opcode.PONG || optcode == Opcode.CLOSING ) { - throw new InvalidFrameException( "more than 125 octets" ); - } - if( payloadlength == 126 ) { - realpacketsize += 2; // additional length bytes - if( maxpacketsize < realpacketsize ) - throw new IncompleteException( realpacketsize ); - byte[] sizebytes = new byte[ 3 ]; - sizebytes[ 1 ] = buffer.get( /*1 + 1*/); - sizebytes[ 2 ] = buffer.get( /*1 + 2*/); - payloadlength = new BigInteger( sizebytes ).intValue(); - } else { - realpacketsize += 8; // additional length bytes - if( maxpacketsize < realpacketsize ) - throw new IncompleteException( realpacketsize ); - byte[] bytes = new byte[ 8 ]; - for( int i = 0 ; i < 8 ; i++ ) { - bytes[ i ] = buffer.get( /*1 + i*/); - } - long length = new BigInteger( bytes ).longValue(); - if( length > Integer.MAX_VALUE ) { - throw new LimitExedeedException( "Payloadsize is to big..." ); - } else { - payloadlength = (int) length; - } - } - } - - // int maskskeystart = foff + realpacketsize; - realpacketsize += ( MASK ? 4 : 0 ); - // int payloadstart = foff + realpacketsize; - realpacketsize += payloadlength; - - if( maxpacketsize < realpacketsize ) - throw new IncompleteException( realpacketsize ); - - ByteBuffer payload = ByteBuffer.allocate( checkAlloc( payloadlength ) ); - if( MASK ) { - byte[] maskskey = new byte[ 4 ]; - buffer.get( maskskey ); - for( int i = 0 ; i < payloadlength ; i++ ) { - payload.put( (byte) ( (byte) buffer.get( /*payloadstart + i*/) ^ (byte) maskskey[ i % 4 ] ) ); - } - } else { - payload.put( buffer.array(), buffer.position(), payload.limit() ); - buffer.position( buffer.position() + payload.limit() ); - } - - FrameBuilder frame; - if( optcode == Opcode.CLOSING ) { - frame = new CloseFrameBuilder(); - } else { - frame = new FramedataImpl1(); - frame.setFin( FIN ); - frame.setOptcode( optcode ); - } - payload.flip(); - frame.setPayload( payload ); - return frame; - } - - @Override - public void reset() { - incompleteframe = null; - } - - @Override - public Draft copyInstance() { - return new Draft_10(); - } - - @Override - public CloseHandshakeType getCloseHandshakeType() { - return CloseHandshakeType.TWOWAY; - } -} diff --git a/src/main/java/org/java_websocket/drafts/Draft_17.java b/src/main/java/org/java_websocket/drafts/Draft_17.java deleted file mode 100644 index 5c4088f73..000000000 --- a/src/main/java/org/java_websocket/drafts/Draft_17.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.java_websocket.drafts; - -import org.java_websocket.exceptions.InvalidHandshakeException; -import org.java_websocket.handshake.ClientHandshake; -import org.java_websocket.handshake.ClientHandshakeBuilder; - -public class Draft_17 extends Draft_10 { - @Override - public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException { - int v = readVersion( handshakedata ); - if( v == 13 ) - return HandshakeState.MATCHED; - return HandshakeState.NOT_MATCHED; - } - - @Override - public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) { - super.postProcessHandshakeRequestAsClient( request ); - request.put( "Sec-WebSocket-Version", "13" );// overwriting the previous - return request; - } - - @Override - public Draft copyInstance() { - return new Draft_17(); - } - -} diff --git a/src/main/java/org/java_websocket/drafts/Draft_6455.java b/src/main/java/org/java_websocket/drafts/Draft_6455.java new file mode 100644 index 000000000..eb4879976 --- /dev/null +++ b/src/main/java/org/java_websocket/drafts/Draft_6455.java @@ -0,0 +1,1213 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.drafts; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.enums.CloseHandshakeType; +import org.java_websocket.enums.HandshakeState; +import org.java_websocket.enums.Opcode; +import org.java_websocket.enums.ReadyState; +import org.java_websocket.enums.Role; +import org.java_websocket.exceptions.IncompleteException; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.exceptions.LimitExceededException; +import org.java_websocket.exceptions.NotSendableException; +import org.java_websocket.extensions.DefaultExtension; +import org.java_websocket.extensions.IExtension; +import org.java_websocket.framing.BinaryFrame; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.FramedataImpl1; +import org.java_websocket.framing.TextFrame; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; +import org.java_websocket.handshake.HandshakeBuilder; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; +import org.java_websocket.protocols.IProtocol; +import org.java_websocket.protocols.Protocol; +import org.java_websocket.util.Base64; +import org.java_websocket.util.Charsetfunctions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation for the RFC 6455 websocket protocol This is the recommended class for your + * websocket connection + */ +public class Draft_6455 extends Draft { + + /** + * Handshake specific field for the key + */ + private static final String SEC_WEB_SOCKET_KEY = "Sec-WebSocket-Key"; + + /** + * Handshake specific field for the protocol + */ + private static final String SEC_WEB_SOCKET_PROTOCOL = "Sec-WebSocket-Protocol"; + + /** + * Handshake specific field for the extension + */ + private static final String SEC_WEB_SOCKET_EXTENSIONS = "Sec-WebSocket-Extensions"; + + /** + * Handshake specific field for the accept + */ + private static final String SEC_WEB_SOCKET_ACCEPT = "Sec-WebSocket-Accept"; + + /** + * Handshake specific field for the upgrade + */ + private static final String UPGRADE = "Upgrade"; + + /** + * Handshake specific field for the connection + */ + private static final String CONNECTION = "Connection"; + + /** + * Logger instance + * + * @since 1.4.0 + */ + private final Logger log = LoggerFactory.getLogger(Draft_6455.class); + + /** + * Attribute for the used extension in this draft + */ + private IExtension negotiatedExtension = new DefaultExtension(); + + /** + * Attribute for the default extension + */ + private IExtension defaultExtension = new DefaultExtension(); + + /** + * Attribute for all available extension in this draft + */ + private List knownExtensions; + + /** + * Current active extension used to decode messages + */ + private IExtension currentDecodingExtension; + + /** + * Attribute for the used protocol in this draft + */ + private IProtocol protocol; + + /** + * Attribute for all available protocols in this draft + */ + private List knownProtocols; + + /** + * Attribute for the current continuous frame + */ + private Framedata currentContinuousFrame; + + /** + * Attribute for the payload of the current continuous frame + */ + private final List byteBufferList; + + /** + * Attribute for the current incomplete frame + */ + private ByteBuffer incompleteframe; + + /** + * Attribute for the reusable random instance + */ + private final SecureRandom reuseableRandom = new SecureRandom(); + + /** + * Attribute for the maximum allowed size of a frame + * + * @since 1.4.0 + */ + private int maxFrameSize; + + /** + * Constructor for the websocket protocol specified by RFC 6455 with default extensions + * + * @since 1.3.5 + */ + public Draft_6455() { + this(Collections.emptyList()); + } + + /** + * Constructor for the websocket protocol specified by RFC 6455 with custom extensions + * + * @param inputExtension the extension which should be used for this draft + * @since 1.3.5 + */ + public Draft_6455(IExtension inputExtension) { + this(Collections.singletonList(inputExtension)); + } + + /** + * Constructor for the websocket protocol specified by RFC 6455 with custom extensions + * + * @param inputExtensions the extensions which should be used for this draft + * @since 1.3.5 + */ + public Draft_6455(List inputExtensions) { + this(inputExtensions, Collections.singletonList(new Protocol(""))); + } + + /** + * Constructor for the websocket protocol specified by RFC 6455 with custom extensions and + * protocols + * + * @param inputExtensions the extensions which should be used for this draft + * @param inputProtocols the protocols which should be used for this draft + * @since 1.3.7 + */ + public Draft_6455(List inputExtensions, List inputProtocols) { + this(inputExtensions, inputProtocols, Integer.MAX_VALUE); + } + + /** + * Constructor for the websocket protocol specified by RFC 6455 with custom extensions and + * protocols + * + * @param inputExtensions the extensions which should be used for this draft + * @param inputMaxFrameSize the maximum allowed size of a frame (the real payload size, decoded + * frames can be bigger) + * @since 1.4.0 + */ + public Draft_6455(List inputExtensions, int inputMaxFrameSize) { + this(inputExtensions, Collections.singletonList(new Protocol("")), + inputMaxFrameSize); + } + + /** + * Constructor for the websocket protocol specified by RFC 6455 with custom extensions and + * protocols + * + * @param inputExtensions the extensions which should be used for this draft + * @param inputProtocols the protocols which should be used for this draft + * @param inputMaxFrameSize the maximum allowed size of a frame (the real payload size, decoded + * frames can be bigger) + * @since 1.4.0 + */ + public Draft_6455(List inputExtensions, List inputProtocols, + int inputMaxFrameSize) { + if (inputExtensions == null || inputProtocols == null || inputMaxFrameSize < 1) { + throw new IllegalArgumentException(); + } + knownExtensions = new ArrayList<>(inputExtensions.size()); + knownProtocols = new ArrayList<>(inputProtocols.size()); + boolean hasDefault = false; + byteBufferList = new ArrayList<>(); + for (IExtension inputExtension : inputExtensions) { + if (inputExtension.getClass().equals(DefaultExtension.class)) { + hasDefault = true; + } + } + knownExtensions.addAll(inputExtensions); + //We always add the DefaultExtension to implement the normal RFC 6455 specification + if (!hasDefault) { + knownExtensions.add(this.knownExtensions.size(), negotiatedExtension); + } + knownProtocols.addAll(inputProtocols); + maxFrameSize = inputMaxFrameSize; + currentDecodingExtension = null; + } + + @Override + public HandshakeState acceptHandshakeAsServer(ClientHandshake handshakedata) + throws InvalidHandshakeException { + int v = readVersion(handshakedata); + if (v != 13) { + log.trace("acceptHandshakeAsServer - Wrong websocket version."); + return HandshakeState.NOT_MATCHED; + } + HandshakeState extensionState = HandshakeState.NOT_MATCHED; + String requestedExtension = handshakedata.getFieldValue(SEC_WEB_SOCKET_EXTENSIONS); + for (IExtension knownExtension : knownExtensions) { + if (knownExtension.acceptProvidedExtensionAsServer(requestedExtension)) { + negotiatedExtension = knownExtension; + extensionState = HandshakeState.MATCHED; + log.trace("acceptHandshakeAsServer - Matching extension found: {}", negotiatedExtension); + break; + } + } + HandshakeState protocolState = containsRequestedProtocol( + handshakedata.getFieldValue(SEC_WEB_SOCKET_PROTOCOL)); + if (protocolState == HandshakeState.MATCHED && extensionState == HandshakeState.MATCHED) { + return HandshakeState.MATCHED; + } + log.trace("acceptHandshakeAsServer - No matching extension or protocol found."); + return HandshakeState.NOT_MATCHED; + } + + /** + * Check if the requested protocol is part of this draft + * + * @param requestedProtocol the requested protocol + * @return MATCHED if it is matched, otherwise NOT_MATCHED + */ + private HandshakeState containsRequestedProtocol(String requestedProtocol) { + for (IProtocol knownProtocol : knownProtocols) { + if (knownProtocol.acceptProvidedProtocol(requestedProtocol)) { + protocol = knownProtocol; + log.trace("acceptHandshake - Matching protocol found: {}", protocol); + return HandshakeState.MATCHED; + } + } + return HandshakeState.NOT_MATCHED; + } + + @Override + public HandshakeState acceptHandshakeAsClient(ClientHandshake request, ServerHandshake response) + throws InvalidHandshakeException { + if (!basicAccept(response)) { + log.trace("acceptHandshakeAsClient - Missing/wrong upgrade or connection in handshake."); + return HandshakeState.NOT_MATCHED; + } + if (!request.hasFieldValue(SEC_WEB_SOCKET_KEY) || !response + .hasFieldValue(SEC_WEB_SOCKET_ACCEPT)) { + log.trace("acceptHandshakeAsClient - Missing Sec-WebSocket-Key or Sec-WebSocket-Accept"); + return HandshakeState.NOT_MATCHED; + } + + String seckeyAnswer = response.getFieldValue(SEC_WEB_SOCKET_ACCEPT); + String seckeyChallenge = request.getFieldValue(SEC_WEB_SOCKET_KEY); + seckeyChallenge = generateFinalKey(seckeyChallenge); + + if (!seckeyChallenge.equals(seckeyAnswer)) { + log.trace("acceptHandshakeAsClient - Wrong key for Sec-WebSocket-Key."); + return HandshakeState.NOT_MATCHED; + } + HandshakeState extensionState = HandshakeState.NOT_MATCHED; + String requestedExtension = response.getFieldValue(SEC_WEB_SOCKET_EXTENSIONS); + for (IExtension knownExtension : knownExtensions) { + if (knownExtension.acceptProvidedExtensionAsClient(requestedExtension)) { + negotiatedExtension = knownExtension; + extensionState = HandshakeState.MATCHED; + log.trace("acceptHandshakeAsClient - Matching extension found: {}", negotiatedExtension); + break; + } + } + HandshakeState protocolState = containsRequestedProtocol( + response.getFieldValue(SEC_WEB_SOCKET_PROTOCOL)); + if (protocolState == HandshakeState.MATCHED && extensionState == HandshakeState.MATCHED) { + return HandshakeState.MATCHED; + } + log.trace("acceptHandshakeAsClient - No matching extension or protocol found."); + return HandshakeState.NOT_MATCHED; + } + + /** + * Getter for the extension which is used by this draft + * + * @return the extension which is used or null, if handshake is not yet done + */ + public IExtension getExtension() { + return negotiatedExtension; + } + + /** + * Getter for all available extensions for this draft + * + * @return the extensions which are enabled for this draft + */ + public List getKnownExtensions() { + return knownExtensions; + } + + /** + * Getter for the protocol which is used by this draft + * + * @return the protocol which is used or null, if handshake is not yet done or no valid protocols + * @since 1.3.7 + */ + public IProtocol getProtocol() { + return protocol; + } + + + /** + * Getter for the maximum allowed payload size which is used by this draft + * + * @return the size, which is allowed for the payload + * @since 1.4.0 + */ + public int getMaxFrameSize() { + return maxFrameSize; + } + + /** + * Getter for all available protocols for this draft + * + * @return the protocols which are enabled for this draft + * @since 1.3.7 + */ + public List getKnownProtocols() { + return knownProtocols; + } + + @Override + public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( + ClientHandshakeBuilder request) { + request.put(UPGRADE, "websocket"); + request.put(CONNECTION, UPGRADE); // to respond to a Connection keep alives + byte[] random = new byte[16]; + reuseableRandom.nextBytes(random); + request.put(SEC_WEB_SOCKET_KEY, Base64.encodeBytes(random)); + request.put("Sec-WebSocket-Version", "13");// overwriting the previous + StringBuilder requestedExtensions = new StringBuilder(); + for (IExtension knownExtension : knownExtensions) { + if (knownExtension.getProvidedExtensionAsClient() != null + && knownExtension.getProvidedExtensionAsClient().length() != 0) { + if (requestedExtensions.length() > 0) { + requestedExtensions.append(", "); + } + requestedExtensions.append(knownExtension.getProvidedExtensionAsClient()); + } + } + if (requestedExtensions.length() != 0) { + request.put(SEC_WEB_SOCKET_EXTENSIONS, requestedExtensions.toString()); + } + StringBuilder requestedProtocols = new StringBuilder(); + for (IProtocol knownProtocol : knownProtocols) { + if (knownProtocol.getProvidedProtocol().length() != 0) { + if (requestedProtocols.length() > 0) { + requestedProtocols.append(", "); + } + requestedProtocols.append(knownProtocol.getProvidedProtocol()); + } + } + if (requestedProtocols.length() != 0) { + request.put(SEC_WEB_SOCKET_PROTOCOL, requestedProtocols.toString()); + } + return request; + } + + @Override + public HandshakeBuilder postProcessHandshakeResponseAsServer(ClientHandshake request, + ServerHandshakeBuilder response) throws InvalidHandshakeException { + response.put(UPGRADE, "websocket"); + response.put(CONNECTION, + request.getFieldValue(CONNECTION)); // to respond to a Connection keep alives + String seckey = request.getFieldValue(SEC_WEB_SOCKET_KEY); + if (seckey == null || "".equals(seckey)) { + throw new InvalidHandshakeException("missing Sec-WebSocket-Key"); + } + response.put(SEC_WEB_SOCKET_ACCEPT, generateFinalKey(seckey)); + if (getExtension().getProvidedExtensionAsServer().length() != 0) { + response.put(SEC_WEB_SOCKET_EXTENSIONS, getExtension().getProvidedExtensionAsServer()); + } + if (getProtocol() != null && getProtocol().getProvidedProtocol().length() != 0) { + response.put(SEC_WEB_SOCKET_PROTOCOL, getProtocol().getProvidedProtocol()); + } + response.setHttpStatusMessage("Web Socket Protocol Handshake"); + response.put("Server", "TooTallNate Java-WebSocket"); + response.put("Date", getServerTime()); + return response; + } + + @Override + public Draft copyInstance() { + ArrayList newExtensions = new ArrayList<>(); + for (IExtension knownExtension : getKnownExtensions()) { + newExtensions.add(knownExtension.copyInstance()); + } + ArrayList newProtocols = new ArrayList<>(); + for (IProtocol knownProtocol : getKnownProtocols()) { + newProtocols.add(knownProtocol.copyInstance()); + } + return new Draft_6455(newExtensions, newProtocols, maxFrameSize); + } + + @Override + public ByteBuffer createBinaryFrame(Framedata framedata) { + getExtension().encodeFrame(framedata); + if (log.isTraceEnabled()) { + log.trace("afterEnconding({}): {}", framedata.getPayloadData().remaining(), + (framedata.getPayloadData().remaining() > 1000 ? "too big to display" + : new String(framedata.getPayloadData().array()))); + } + return createByteBufferFromFramedata(framedata); + } + + private ByteBuffer createByteBufferFromFramedata(Framedata framedata) { + ByteBuffer mes = framedata.getPayloadData(); + boolean mask = role == Role.CLIENT; + int sizebytes = getSizeBytes(mes); + ByteBuffer buf = ByteBuffer.allocate( + 1 + (sizebytes > 1 ? sizebytes + 1 : sizebytes) + (mask ? 4 : 0) + mes.remaining()); + byte optcode = fromOpcode(framedata.getOpcode()); + byte one = (byte) (framedata.isFin() ? -128 : 0); + one |= optcode; + if (framedata.isRSV1()) { + one |= getRSVByte(1); + } + if (framedata.isRSV2()) { + one |= getRSVByte(2); + } + if (framedata.isRSV3()) { + one |= getRSVByte(3); + } + buf.put(one); + byte[] payloadlengthbytes = toByteArray(mes.remaining(), sizebytes); + assert (payloadlengthbytes.length == sizebytes); + + if (sizebytes == 1) { + buf.put((byte) (payloadlengthbytes[0] | getMaskByte(mask))); + } else if (sizebytes == 2) { + buf.put((byte) ((byte) 126 | getMaskByte(mask))); + buf.put(payloadlengthbytes); + } else if (sizebytes == 8) { + buf.put((byte) ((byte) 127 | getMaskByte(mask))); + buf.put(payloadlengthbytes); + } else { + throw new IllegalStateException("Size representation not supported/specified"); + } + if (mask) { + ByteBuffer maskkey = ByteBuffer.allocate(4); + maskkey.putInt(reuseableRandom.nextInt()); + buf.put(maskkey.array()); + for (int i = 0; mes.hasRemaining(); i++) { + buf.put((byte) (mes.get() ^ maskkey.get(i % 4))); + } + } else { + buf.put(mes); + //Reset the position of the bytebuffer e.g. for additional use + mes.flip(); + } + assert (buf.remaining() == 0) : buf.remaining(); + buf.flip(); + return buf; + } + + private Framedata translateSingleFrame(ByteBuffer buffer) + throws IncompleteException, InvalidDataException { + if (buffer == null) { + throw new IllegalArgumentException(); + } + int maxpacketsize = buffer.remaining(); + int realpacketsize = 2; + translateSingleFrameCheckPacketSize(maxpacketsize, realpacketsize); + byte b1 = buffer.get(/*0*/); + boolean fin = b1 >> 8 != 0; + boolean rsv1 = (b1 & 0x40) != 0; + boolean rsv2 = (b1 & 0x20) != 0; + boolean rsv3 = (b1 & 0x10) != 0; + byte b2 = buffer.get(/*1*/); + boolean mask = (b2 & -128) != 0; + int payloadlength = (byte) (b2 & ~(byte) 128); + Opcode optcode = toOpcode((byte) (b1 & 15)); + + if (!(payloadlength >= 0 && payloadlength <= 125)) { + TranslatedPayloadMetaData payloadData = translateSingleFramePayloadLength(buffer, optcode, + payloadlength, maxpacketsize, realpacketsize); + payloadlength = payloadData.getPayloadLength(); + realpacketsize = payloadData.getRealPackageSize(); + } + translateSingleFrameCheckLengthLimit(payloadlength); + realpacketsize += (mask ? 4 : 0); + realpacketsize += payloadlength; + translateSingleFrameCheckPacketSize(maxpacketsize, realpacketsize); + + ByteBuffer payload = ByteBuffer.allocate(checkAlloc(payloadlength)); + if (mask) { + byte[] maskskey = new byte[4]; + buffer.get(maskskey); + for (int i = 0; i < payloadlength; i++) { + payload.put((byte) (buffer.get(/*payloadstart + i*/) ^ maskskey[i % 4])); + } + } else { + payload.put(buffer.array(), buffer.position(), payload.limit()); + buffer.position(buffer.position() + payload.limit()); + } + + FramedataImpl1 frame = FramedataImpl1.get(optcode); + frame.setFin(fin); + frame.setRSV1(rsv1); + frame.setRSV2(rsv2); + frame.setRSV3(rsv3); + payload.flip(); + frame.setPayload(payload); + if (frame.getOpcode() != Opcode.CONTINUOUS) { + // Prioritize the negotiated extension + if (frame.isRSV1() || frame.isRSV2() || frame.isRSV3()) { + currentDecodingExtension = getExtension(); + } else { + // No encoded message, so we can use the default one + currentDecodingExtension = defaultExtension; + } + } + if (currentDecodingExtension == null) { + currentDecodingExtension = defaultExtension; + } + currentDecodingExtension.isFrameValid(frame); + currentDecodingExtension.decodeFrame(frame); + if (log.isTraceEnabled()) { + log.trace("afterDecoding({}): {}", frame.getPayloadData().remaining(), + (frame.getPayloadData().remaining() > 1000 ? "too big to display" + : new String(frame.getPayloadData().array()))); + } + frame.isValid(); + return frame; + } + + /** + * Translate the buffer depending when it has an extended payload length (126 or 127) + * + * @param buffer the buffer to read from + * @param optcode the decoded optcode + * @param oldPayloadlength the old payload length + * @param maxpacketsize the max packet size allowed + * @param oldRealpacketsize the real packet size + * @return the new payload data containing new payload length and new packet size + * @throws InvalidFrameException thrown if a control frame has an invalid length + * @throws IncompleteException if the maxpacketsize is smaller than the realpackagesize + * @throws LimitExceededException if the payload length is to big + */ + private TranslatedPayloadMetaData translateSingleFramePayloadLength(ByteBuffer buffer, + Opcode optcode, int oldPayloadlength, int maxpacketsize, int oldRealpacketsize) + throws InvalidFrameException, IncompleteException, LimitExceededException { + int payloadlength = oldPayloadlength; + int realpacketsize = oldRealpacketsize; + if (optcode == Opcode.PING || optcode == Opcode.PONG || optcode == Opcode.CLOSING) { + log.trace("Invalid frame: more than 125 octets"); + throw new InvalidFrameException("more than 125 octets"); + } + if (payloadlength == 126) { + realpacketsize += 2; // additional length bytes + translateSingleFrameCheckPacketSize(maxpacketsize, realpacketsize); + byte[] sizebytes = new byte[3]; + sizebytes[1] = buffer.get(/*1 + 1*/); + sizebytes[2] = buffer.get(/*1 + 2*/); + payloadlength = new BigInteger(sizebytes).intValue(); + } else { + realpacketsize += 8; // additional length bytes + translateSingleFrameCheckPacketSize(maxpacketsize, realpacketsize); + byte[] bytes = new byte[8]; + for (int i = 0; i < 8; i++) { + bytes[i] = buffer.get(/*1 + i*/); + } + long length = new BigInteger(bytes).longValue(); + translateSingleFrameCheckLengthLimit(length); + payloadlength = (int) length; + } + return new TranslatedPayloadMetaData(payloadlength, realpacketsize); + } + + /** + * Check if the frame size exceeds the allowed limit + * + * @param length the current payload length + * @throws LimitExceededException if the payload length is to big + */ + private void translateSingleFrameCheckLengthLimit(long length) throws LimitExceededException { + if (length > Integer.MAX_VALUE) { + log.trace("Limit exedeed: Payloadsize is to big..."); + throw new LimitExceededException("Payloadsize is to big..."); + } + if (length > maxFrameSize) { + log.trace("Payload limit reached. Allowed: {} Current: {}", maxFrameSize, length); + throw new LimitExceededException("Payload limit reached.", maxFrameSize); + } + if (length < 0) { + log.trace("Limit underflow: Payloadsize is to little..."); + throw new LimitExceededException("Payloadsize is to little..."); + } + } + + /** + * Check if the max packet size is smaller than the real packet size + * + * @param maxpacketsize the max packet size + * @param realpacketsize the real packet size + * @throws IncompleteException if the maxpacketsize is smaller than the realpackagesize + */ + private void translateSingleFrameCheckPacketSize(int maxpacketsize, int realpacketsize) + throws IncompleteException { + if (maxpacketsize < realpacketsize) { + log.trace("Incomplete frame: maxpacketsize < realpacketsize"); + throw new IncompleteException(realpacketsize); + } + } + + /** + * Get a byte that can set RSV bits when OR(|)'d. 0 1 2 3 4 5 6 7 +-+-+-+-+-------+ |F|R|R|R| + * opcode| |I|S|S|S| (4) | |N|V|V|V| | | |1|2|3| | + * + * @param rsv Can only be {0, 1, 2, 3} + * @return byte that represents which RSV bit is set. + */ + private byte getRSVByte(int rsv) { + switch (rsv) { + case 1 : // 0100 0000 + return 0x40; + case 2 : // 0010 0000 + return 0x20; + case 3 : // 0001 0000 + return 0x10; + default: + return 0; + } + } + + /** + * Get the mask byte if existing + * + * @param mask is mask active or not + * @return -128 for true, 0 for false + */ + private byte getMaskByte(boolean mask) { + return mask ? (byte) -128 : 0; + } + + /** + * Get the size bytes for the byte buffer + * + * @param mes the current buffer + * @return the size bytes + */ + private int getSizeBytes(ByteBuffer mes) { + if (mes.remaining() <= 125) { + return 1; + } else if (mes.remaining() <= 65535) { + return 2; + } + return 8; + } + + @Override + public List translateFrame(ByteBuffer buffer) throws InvalidDataException { + while (true) { + List frames = new LinkedList<>(); + Framedata cur; + if (incompleteframe != null) { + // complete an incomplete frame + try { + buffer.mark(); + int availableNextByteCount = buffer.remaining();// The number of bytes received + int expectedNextByteCount = incompleteframe + .remaining();// The number of bytes to complete the incomplete frame + + if (expectedNextByteCount > availableNextByteCount) { + // did not receive enough bytes to complete the frame + incompleteframe.put(buffer.array(), buffer.position(), availableNextByteCount); + buffer.position(buffer.position() + availableNextByteCount); + return Collections.emptyList(); + } + incompleteframe.put(buffer.array(), buffer.position(), expectedNextByteCount); + buffer.position(buffer.position() + expectedNextByteCount); + cur = translateSingleFrame((ByteBuffer) incompleteframe.duplicate().position(0)); + frames.add(cur); + incompleteframe = null; + } catch (IncompleteException e) { + // extending as much as suggested + ByteBuffer extendedframe = ByteBuffer.allocate(checkAlloc(e.getPreferredSize())); + assert (extendedframe.limit() > incompleteframe.limit()); + incompleteframe.rewind(); + extendedframe.put(incompleteframe); + incompleteframe = extendedframe; + continue; + } + } + + // Read as much as possible full frames + while (buffer.hasRemaining()) { + buffer.mark(); + try { + cur = translateSingleFrame(buffer); + frames.add(cur); + } catch (IncompleteException e) { + // remember the incomplete data + buffer.reset(); + int pref = e.getPreferredSize(); + incompleteframe = ByteBuffer.allocate(checkAlloc(pref)); + incompleteframe.put(buffer); + break; + } + } + return frames; + } + } + + @Override + public List createFrames(ByteBuffer binary, boolean mask) { + BinaryFrame curframe = new BinaryFrame(); + curframe.setPayload(binary); + curframe.setTransferemasked(mask); + try { + curframe.isValid(); + } catch (InvalidDataException e) { + throw new NotSendableException(e); + } + return Collections.singletonList((Framedata) curframe); + } + + @Override + public List createFrames(String text, boolean mask) { + TextFrame curframe = new TextFrame(); + curframe.setPayload(ByteBuffer.wrap(Charsetfunctions.utf8Bytes(text))); + curframe.setTransferemasked(mask); + try { + curframe.isValid(); + } catch (InvalidDataException e) { + throw new NotSendableException(e); + } + return Collections.singletonList((Framedata) curframe); + } + + @Override + public void reset() { + incompleteframe = null; + if (negotiatedExtension != null) { + negotiatedExtension.reset(); + } + negotiatedExtension = new DefaultExtension(); + protocol = null; + } + + /** + * Generate a date for for the date-header + * + * @return the server time + */ + private String getServerTime() { + Calendar calendar = Calendar.getInstance(); + SimpleDateFormat dateFormat = new SimpleDateFormat( + "EEE, dd MMM yyyy HH:mm:ss z", Locale.US); + dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); + return dateFormat.format(calendar.getTime()); + } + + /** + * Generate a final key from a input string + * + * @param in the input string + * @return a final key + */ + private String generateFinalKey(String in) { + String seckey = in.trim(); + String acc = seckey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + MessageDigest sh1; + try { + sh1 = MessageDigest.getInstance("SHA1"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + return Base64.encodeBytes(sh1.digest(acc.getBytes())); + } + + private byte[] toByteArray(long val, int bytecount) { + byte[] buffer = new byte[bytecount]; + int highest = 8 * bytecount - 8; + for (int i = 0; i < bytecount; i++) { + buffer[i] = (byte) (val >>> (highest - 8 * i)); + } + return buffer; + } + + + private byte fromOpcode(Opcode opcode) { + if (opcode == Opcode.CONTINUOUS) { + return 0; + } else if (opcode == Opcode.TEXT) { + return 1; + } else if (opcode == Opcode.BINARY) { + return 2; + } else if (opcode == Opcode.CLOSING) { + return 8; + } else if (opcode == Opcode.PING) { + return 9; + } else if (opcode == Opcode.PONG) { + return 10; + } + throw new IllegalArgumentException("Don't know how to handle " + opcode.toString()); + } + + private Opcode toOpcode(byte opcode) throws InvalidFrameException { + switch (opcode) { + case 0: + return Opcode.CONTINUOUS; + case 1: + return Opcode.TEXT; + case 2: + return Opcode.BINARY; + // 3-7 are not yet defined + case 8: + return Opcode.CLOSING; + case 9: + return Opcode.PING; + case 10: + return Opcode.PONG; + // 11-15 are not yet defined + default: + throw new InvalidFrameException("Unknown opcode " + (short) opcode); + } + } + + @Override + public void processFrame(WebSocketImpl webSocketImpl, Framedata frame) + throws InvalidDataException { + Opcode curop = frame.getOpcode(); + if (curop == Opcode.CLOSING) { + processFrameClosing(webSocketImpl, frame); + } else if (curop == Opcode.PING) { + webSocketImpl.getWebSocketListener().onWebsocketPing(webSocketImpl, frame); + } else if (curop == Opcode.PONG) { + webSocketImpl.updateLastPong(); + webSocketImpl.getWebSocketListener().onWebsocketPong(webSocketImpl, frame); + } else if (!frame.isFin() || curop == Opcode.CONTINUOUS) { + processFrameContinuousAndNonFin(webSocketImpl, frame, curop); + } else if (currentContinuousFrame != null) { + log.error("Protocol error: Continuous frame sequence not completed."); + throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR, + "Continuous frame sequence not completed."); + } else if (curop == Opcode.TEXT) { + processFrameText(webSocketImpl, frame); + } else if (curop == Opcode.BINARY) { + processFrameBinary(webSocketImpl, frame); + } else { + log.error("non control or continious frame expected"); + throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR, + "non control or continious frame expected"); + } + } + + /** + * Process the frame if it is a continuous frame or the fin bit is not set + * + * @param webSocketImpl the websocket implementation to use + * @param frame the current frame + * @param curop the current Opcode + * @throws InvalidDataException if there is a protocol error + */ + private void processFrameContinuousAndNonFin(WebSocketImpl webSocketImpl, Framedata frame, + Opcode curop) throws InvalidDataException { + if (curop != Opcode.CONTINUOUS) { + processFrameIsNotFin(frame); + } else if (frame.isFin()) { + processFrameIsFin(webSocketImpl, frame); + } else if (currentContinuousFrame == null) { + log.error("Protocol error: Continuous frame sequence was not started."); + throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR, + "Continuous frame sequence was not started."); + } + //Check if the whole payload is valid utf8, when the opcode indicates a text + if (curop == Opcode.TEXT && !Charsetfunctions.isValidUTF8(frame.getPayloadData())) { + log.error("Protocol error: Payload is not UTF8"); + throw new InvalidDataException(CloseFrame.NO_UTF8); + } + //Checking if the current continuous frame contains a correct payload with the other frames combined + if (curop == Opcode.CONTINUOUS && currentContinuousFrame != null) { + addToBufferList(frame.getPayloadData()); + } + } + + /** + * Process the frame if it is a binary frame + * + * @param webSocketImpl the websocket impl + * @param frame the frame + */ + private void processFrameBinary(WebSocketImpl webSocketImpl, Framedata frame) { + try { + webSocketImpl.getWebSocketListener() + .onWebsocketMessage(webSocketImpl, frame.getPayloadData()); + } catch (RuntimeException e) { + logRuntimeException(webSocketImpl, e); + } + } + + /** + * Log the runtime exception to the specific WebSocketImpl + * + * @param webSocketImpl the implementation of the websocket + * @param e the runtime exception + */ + private void logRuntimeException(WebSocketImpl webSocketImpl, RuntimeException e) { + log.error("Runtime exception during onWebsocketMessage", e); + webSocketImpl.getWebSocketListener().onWebsocketError(webSocketImpl, e); + } + + /** + * Process the frame if it is a text frame + * + * @param webSocketImpl the websocket impl + * @param frame the frame + */ + private void processFrameText(WebSocketImpl webSocketImpl, Framedata frame) + throws InvalidDataException { + try { + webSocketImpl.getWebSocketListener() + .onWebsocketMessage(webSocketImpl, Charsetfunctions.stringUtf8(frame.getPayloadData())); + } catch (RuntimeException e) { + logRuntimeException(webSocketImpl, e); + } + } + + /** + * Process the frame if it is the last frame + * + * @param webSocketImpl the websocket impl + * @param frame the frame + * @throws InvalidDataException if there is a protocol error + */ + private void processFrameIsFin(WebSocketImpl webSocketImpl, Framedata frame) + throws InvalidDataException { + if (currentContinuousFrame == null) { + log.trace("Protocol error: Previous continuous frame sequence not completed."); + throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR, + "Continuous frame sequence was not started."); + } + addToBufferList(frame.getPayloadData()); + checkBufferLimit(); + if (currentContinuousFrame.getOpcode() == Opcode.TEXT) { + ((FramedataImpl1) currentContinuousFrame).setPayload(getPayloadFromByteBufferList()); + ((FramedataImpl1) currentContinuousFrame).isValid(); + try { + webSocketImpl.getWebSocketListener().onWebsocketMessage(webSocketImpl, + Charsetfunctions.stringUtf8(currentContinuousFrame.getPayloadData())); + } catch (RuntimeException e) { + logRuntimeException(webSocketImpl, e); + } + } else if (currentContinuousFrame.getOpcode() == Opcode.BINARY) { + ((FramedataImpl1) currentContinuousFrame).setPayload(getPayloadFromByteBufferList()); + ((FramedataImpl1) currentContinuousFrame).isValid(); + try { + webSocketImpl.getWebSocketListener() + .onWebsocketMessage(webSocketImpl, currentContinuousFrame.getPayloadData()); + } catch (RuntimeException e) { + logRuntimeException(webSocketImpl, e); + } + } + currentContinuousFrame = null; + clearBufferList(); + } + + /** + * Process the frame if it is not the last frame + * + * @param frame the frame + * @throws InvalidDataException if there is a protocol error + */ + private void processFrameIsNotFin(Framedata frame) throws InvalidDataException { + if (currentContinuousFrame != null) { + log.trace("Protocol error: Previous continuous frame sequence not completed."); + throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR, + "Previous continuous frame sequence not completed."); + } + currentContinuousFrame = frame; + addToBufferList(frame.getPayloadData()); + checkBufferLimit(); + } + + /** + * Process the frame if it is a closing frame + * + * @param webSocketImpl the websocket impl + * @param frame the frame + */ + private void processFrameClosing(WebSocketImpl webSocketImpl, Framedata frame) { + int code = CloseFrame.NOCODE; + String reason = ""; + if (frame instanceof CloseFrame) { + CloseFrame cf = (CloseFrame) frame; + code = cf.getCloseCode(); + reason = cf.getMessage(); + } + if (webSocketImpl.getReadyState() == ReadyState.CLOSING) { + // complete the close handshake by disconnecting + webSocketImpl.closeConnection(code, reason, true); + } else { + // echo close handshake + if (getCloseHandshakeType() == CloseHandshakeType.TWOWAY) { + webSocketImpl.close(code, reason, true); + } else { + webSocketImpl.flushAndClose(code, reason, false); + } + } + } + + /** + * Clear the current bytebuffer list + */ + private void clearBufferList() { + synchronized (byteBufferList) { + byteBufferList.clear(); + } + } + + /** + * Add a payload to the current bytebuffer list + * + * @param payloadData the new payload + */ + private void addToBufferList(ByteBuffer payloadData) { + synchronized (byteBufferList) { + byteBufferList.add(payloadData); + } + } + + /** + * Check the current size of the buffer and throw an exception if the size is bigger than the max + * allowed frame size + * + * @throws LimitExceededException if the current size is bigger than the allowed size + */ + private void checkBufferLimit() throws LimitExceededException { + long totalSize = getByteBufferListSize(); + if (totalSize > maxFrameSize) { + clearBufferList(); + log.trace("Payload limit reached. Allowed: {} Current: {}", maxFrameSize, totalSize); + throw new LimitExceededException(maxFrameSize); + } + } + + @Override + public CloseHandshakeType getCloseHandshakeType() { + return CloseHandshakeType.TWOWAY; + } + + @Override + public String toString() { + String result = super.toString(); + if (getExtension() != null) { + result += " extension: " + getExtension().toString(); + } + if (getProtocol() != null) { + result += " protocol: " + getProtocol().toString(); + } + result += " max frame size: " + this.maxFrameSize; + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Draft_6455 that = (Draft_6455) o; + + if (maxFrameSize != that.getMaxFrameSize()) { + return false; + } + if (negotiatedExtension != null ? !negotiatedExtension.equals(that.getExtension()) : that.getExtension() != null) { + return false; + } + return protocol != null ? protocol.equals(that.getProtocol()) : that.getProtocol() == null; + } + + @Override + public int hashCode() { + int result = negotiatedExtension != null ? negotiatedExtension.hashCode() : 0; + result = 31 * result + (protocol != null ? protocol.hashCode() : 0); + result = 31 * result + (maxFrameSize ^ (maxFrameSize >>> 32)); + return result; + } + + /** + * Method to generate a full bytebuffer out of all the fragmented frame payload + * + * @return a bytebuffer containing all the data + * @throws LimitExceededException will be thrown when the totalSize is bigger then + * Integer.MAX_VALUE due to not being able to allocate more + */ + private ByteBuffer getPayloadFromByteBufferList() throws LimitExceededException { + long totalSize = 0; + ByteBuffer resultingByteBuffer; + synchronized (byteBufferList) { + for (ByteBuffer buffer : byteBufferList) { + totalSize += buffer.limit(); + } + checkBufferLimit(); + resultingByteBuffer = ByteBuffer.allocate((int) totalSize); + for (ByteBuffer buffer : byteBufferList) { + resultingByteBuffer.put(buffer); + } + } + resultingByteBuffer.flip(); + return resultingByteBuffer; + } + + /** + * Get the current size of the resulting bytebuffer in the bytebuffer list + * + * @return the size as long (to not get an integer overflow) + */ + private long getByteBufferListSize() { + long totalSize = 0; + synchronized (byteBufferList) { + for (ByteBuffer buffer : byteBufferList) { + totalSize += buffer.limit(); + } + } + return totalSize; + } + + private class TranslatedPayloadMetaData { + + private int payloadLength; + private int realPackageSize; + + private int getPayloadLength() { + return payloadLength; + } + + private int getRealPackageSize() { + return realPackageSize; + } + + TranslatedPayloadMetaData(int newPayloadLength, int newRealPackageSize) { + this.payloadLength = newPayloadLength; + this.realPackageSize = newRealPackageSize; + } + } +} diff --git a/src/main/java/org/java_websocket/drafts/Draft_75.java b/src/main/java/org/java_websocket/drafts/Draft_75.java deleted file mode 100644 index 947a35ece..000000000 --- a/src/main/java/org/java_websocket/drafts/Draft_75.java +++ /dev/null @@ -1,206 +0,0 @@ -package org.java_websocket.drafts; - -import java.nio.ByteBuffer; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Random; - -import org.java_websocket.exceptions.InvalidDataException; -import org.java_websocket.exceptions.InvalidFrameException; -import org.java_websocket.exceptions.InvalidHandshakeException; -import org.java_websocket.exceptions.LimitExedeedException; -import org.java_websocket.exceptions.NotSendableException; -import org.java_websocket.framing.CloseFrame; -import org.java_websocket.framing.FrameBuilder; -import org.java_websocket.framing.Framedata; -import org.java_websocket.framing.Framedata.Opcode; -import org.java_websocket.framing.FramedataImpl1; -import org.java_websocket.handshake.ClientHandshake; -import org.java_websocket.handshake.ClientHandshakeBuilder; -import org.java_websocket.handshake.HandshakeBuilder; -import org.java_websocket.handshake.ServerHandshake; -import org.java_websocket.handshake.ServerHandshakeBuilder; -import org.java_websocket.util.Charsetfunctions; - -public class Draft_75 extends Draft { - - /** - * The byte representing CR, or Carriage Return, or \r - */ - public static final byte CR = (byte) 0x0D; - /** - * The byte representing LF, or Line Feed, or \n - */ - public static final byte LF = (byte) 0x0A; - /** - * The byte representing the beginning of a WebSocket text frame. - */ - public static final byte START_OF_FRAME = (byte) 0x00; - /** - * The byte representing the end of a WebSocket text frame. - */ - public static final byte END_OF_FRAME = (byte) 0xFF; - - /** Is only used to detect protocol violations */ - protected boolean readingState = false; - - protected List readyframes = new LinkedList(); - protected ByteBuffer currentFrame; - - private final Random reuseableRandom = new Random(); - - @Override - public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) { - return request.getFieldValue( "WebSocket-Origin" ).equals( response.getFieldValue( "Origin" ) ) && basicAccept( response ) ? HandshakeState.MATCHED : HandshakeState.NOT_MATCHED; - } - - @Override - public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) { - if( handshakedata.hasFieldValue( "Origin" ) && basicAccept( handshakedata ) ) { - return HandshakeState.MATCHED; - } - return HandshakeState.NOT_MATCHED; - } - - @Override - public ByteBuffer createBinaryFrame( Framedata framedata ) { - if( framedata.getOpcode() != Opcode.TEXT ) { - throw new RuntimeException( "only text frames supported" ); - } - - ByteBuffer pay = framedata.getPayloadData(); - ByteBuffer b = ByteBuffer.allocate( pay.remaining() + 2 ); - b.put( START_OF_FRAME ); - pay.mark(); - b.put( pay ); - pay.reset(); - b.put( END_OF_FRAME ); - b.flip(); - return b; - } - - @Override - public List createFrames( ByteBuffer binary, boolean mask ) { - throw new RuntimeException( "not yet implemented" ); - } - - @Override - public List createFrames( String text, boolean mask ) { - FrameBuilder frame = new FramedataImpl1(); - try { - frame.setPayload( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( text ) ) ); - } catch ( InvalidDataException e ) { - throw new NotSendableException( e ); - } - frame.setFin( true ); - frame.setOptcode( Opcode.TEXT ); - frame.setTransferemasked( mask ); - return Collections.singletonList( (Framedata) frame ); - } - - @Override - public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) throws InvalidHandshakeException { - request.put( "Upgrade", "WebSocket" ); - request.put( "Connection", "Upgrade" ); - if( !request.hasFieldValue( "Origin" ) ) { - request.put( "Origin", "random" + reuseableRandom.nextInt() ); - } - - return request; - } - - @Override - public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException { - response.setHttpStatusMessage( "Web Socket Protocol Handshake" ); - response.put( "Upgrade", "WebSocket" ); - response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alive - response.put( "WebSocket-Origin", request.getFieldValue( "Origin" ) ); - String location = "ws://" + request.getFieldValue( "Host" ) + request.getResourceDescriptor(); - response.put( "WebSocket-Location", location ); - // TODO handle Sec-WebSocket-Protocol and Set-Cookie - return response; - } - - protected List translateRegularFrame( ByteBuffer buffer ) throws InvalidDataException { - - while ( buffer.hasRemaining() ) { - byte newestByte = buffer.get(); - if( newestByte == START_OF_FRAME ) { // Beginning of Frame - if( readingState ) - throw new InvalidFrameException( "unexpected START_OF_FRAME" ); - readingState = true; - } else if( newestByte == END_OF_FRAME ) { // End of Frame - if( !readingState ) - throw new InvalidFrameException( "unexpected END_OF_FRAME" ); - // currentFrame will be null if END_OF_FRAME was send directly after - // START_OF_FRAME, thus we will send 'null' as the sent message. - if( this.currentFrame != null ) { - currentFrame.flip(); - FramedataImpl1 curframe = new FramedataImpl1(); - curframe.setPayload( currentFrame ); - curframe.setFin( true ); - curframe.setOptcode( Opcode.TEXT ); - readyframes.add( curframe ); - this.currentFrame = null; - buffer.mark(); - } - readingState = false; - } else if( readingState ) { // Regular frame data, add to current frame buffer //TODO This code is very expensive and slow - if( currentFrame == null ) { - currentFrame = createBuffer(); - } else if( !currentFrame.hasRemaining() ) { - currentFrame = increaseBuffer( currentFrame ); - } - currentFrame.put( newestByte ); - } else { - return null; - } - } - - // if no error occurred this block will be reached - /*if( readingState ) { - checkAlloc(currentFrame.position()+1); - }*/ - - List frames = readyframes; - readyframes = new LinkedList(); - return frames; - } - - @Override - public List translateFrame( ByteBuffer buffer ) throws InvalidDataException { - List frames = translateRegularFrame( buffer ); - if( frames == null ) { - throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR ); - } - return frames; - } - - @Override - public void reset() { - readingState = false; - this.currentFrame = null; - } - - @Override - public CloseHandshakeType getCloseHandshakeType() { - return CloseHandshakeType.NONE; - } - - public ByteBuffer createBuffer() { - return ByteBuffer.allocate( INITIAL_FAMESIZE ); - } - - public ByteBuffer increaseBuffer( ByteBuffer full ) throws LimitExedeedException , InvalidDataException { - full.flip(); - ByteBuffer newbuffer = ByteBuffer.allocate( checkAlloc( full.capacity() * 2 ) ); - newbuffer.put( full ); - return newbuffer; - } - - @Override - public Draft copyInstance() { - return new Draft_75(); - } -} diff --git a/src/main/java/org/java_websocket/drafts/Draft_76.java b/src/main/java/org/java_websocket/drafts/Draft_76.java deleted file mode 100644 index 26f23531e..000000000 --- a/src/main/java/org/java_websocket/drafts/Draft_76.java +++ /dev/null @@ -1,242 +0,0 @@ -package org.java_websocket.drafts; - -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.Random; - -import org.java_websocket.WebSocket.Role; -import org.java_websocket.exceptions.IncompleteHandshakeException; -import org.java_websocket.exceptions.InvalidDataException; -import org.java_websocket.exceptions.InvalidFrameException; -import org.java_websocket.exceptions.InvalidHandshakeException; -import org.java_websocket.framing.CloseFrame; -import org.java_websocket.framing.CloseFrameBuilder; -import org.java_websocket.framing.Framedata; -import org.java_websocket.framing.Framedata.Opcode; -import org.java_websocket.handshake.ClientHandshake; -import org.java_websocket.handshake.ClientHandshakeBuilder; -import org.java_websocket.handshake.HandshakeBuilder; -import org.java_websocket.handshake.Handshakedata; -import org.java_websocket.handshake.ServerHandshake; -import org.java_websocket.handshake.ServerHandshakeBuilder; - -public class Draft_76 extends Draft_75 { - private boolean failed = false; - private static final byte[] closehandshake = { -1, 0 }; - - private final Random reuseableRandom = new Random(); - - - public static byte[] createChallenge( String key1, String key2, byte[] key3 ) throws InvalidHandshakeException { - byte[] part1 = getPart( key1 ); - byte[] part2 = getPart( key2 ); - byte[] challenge = new byte[ 16 ]; - challenge[ 0 ] = part1[ 0 ]; - challenge[ 1 ] = part1[ 1 ]; - challenge[ 2 ] = part1[ 2 ]; - challenge[ 3 ] = part1[ 3 ]; - challenge[ 4 ] = part2[ 0 ]; - challenge[ 5 ] = part2[ 1 ]; - challenge[ 6 ] = part2[ 2 ]; - challenge[ 7 ] = part2[ 3 ]; - challenge[ 8 ] = key3[ 0 ]; - challenge[ 9 ] = key3[ 1 ]; - challenge[ 10 ] = key3[ 2 ]; - challenge[ 11 ] = key3[ 3 ]; - challenge[ 12 ] = key3[ 4 ]; - challenge[ 13 ] = key3[ 5 ]; - challenge[ 14 ] = key3[ 6 ]; - challenge[ 15 ] = key3[ 7 ]; - MessageDigest md5; - try { - md5 = MessageDigest.getInstance( "MD5" ); - } catch ( NoSuchAlgorithmException e ) { - throw new RuntimeException( e ); - } - return md5.digest( challenge ); - } - - private static String generateKey() { - Random r = new Random(); - long maxNumber = 4294967295L; - long spaces = r.nextInt( 12 ) + 1; - int max = new Long( maxNumber / spaces ).intValue(); - max = Math.abs( max ); - int number = r.nextInt( max ) + 1; - long product = number * spaces; - String key = Long.toString( product ); - // always insert atleast one random character - int numChars = r.nextInt( 12 ) + 1; - for( int i = 0 ; i < numChars ; i++ ) { - int position = r.nextInt( key.length() ); - position = Math.abs( position ); - char randChar = (char) ( r.nextInt( 95 ) + 33 ); - // exclude numbers here - if( randChar >= 48 && randChar <= 57 ) { - randChar -= 15; - } - key = new StringBuilder( key ).insert( position, randChar ).toString(); - } - for( int i = 0 ; i < spaces ; i++ ) { - int position = r.nextInt( key.length() - 1 ) + 1; - position = Math.abs( position ); - key = new StringBuilder( key ).insert( position, "\u0020" ).toString(); - } - return key; - } - - private static byte[] getPart( String key ) throws InvalidHandshakeException { - try { - long keyNumber = Long.parseLong( key.replaceAll( "[^0-9]", "" ) ); - long keySpace = key.split( "\u0020" ).length - 1; - if( keySpace == 0 ) { - throw new InvalidHandshakeException( "invalid Sec-WebSocket-Key (/key2/)" ); - } - long part = new Long( keyNumber / keySpace ); - return new byte[]{ (byte) ( part >> 24 ), (byte) ( ( part << 8 ) >> 24 ), (byte) ( ( part << 16 ) >> 24 ), (byte) ( ( part << 24 ) >> 24 ) }; - } catch ( NumberFormatException e ) { - throw new InvalidHandshakeException( "invalid Sec-WebSocket-Key (/key1/ or /key2/)" ); - } - } - - @Override - public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) { - if( failed ) { - return HandshakeState.NOT_MATCHED; - } - - try { - if( !response.getFieldValue( "Sec-WebSocket-Origin" ).equals( request.getFieldValue( "Origin" ) ) || !basicAccept( response ) ) { - return HandshakeState.NOT_MATCHED; - } - byte[] content = response.getContent(); - if( content == null || content.length == 0 ) { - throw new IncompleteHandshakeException(); - } - if( Arrays.equals( content, createChallenge( request.getFieldValue( "Sec-WebSocket-Key1" ), request.getFieldValue( "Sec-WebSocket-Key2" ), request.getContent() ) ) ) { - return HandshakeState.MATCHED; - } else { - return HandshakeState.NOT_MATCHED; - } - } catch ( InvalidHandshakeException e ) { - throw new RuntimeException( "bad handshakerequest", e ); - } - } - - @Override - public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) { - if( handshakedata.getFieldValue( "Upgrade" ).equals( "WebSocket" ) && handshakedata.getFieldValue( "Connection" ).contains( "Upgrade" ) && handshakedata.getFieldValue( "Sec-WebSocket-Key1" ).length() > 0 && !handshakedata.getFieldValue( "Sec-WebSocket-Key2" ).isEmpty() && handshakedata.hasFieldValue( "Origin" ) ) - return HandshakeState.MATCHED; - return HandshakeState.NOT_MATCHED; - } - - @Override - public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) { - request.put( "Upgrade", "WebSocket" ); - request.put( "Connection", "Upgrade" ); - request.put( "Sec-WebSocket-Key1", generateKey() ); - request.put( "Sec-WebSocket-Key2", generateKey() ); - - if( !request.hasFieldValue( "Origin" ) ) { - request.put( "Origin", "random" + reuseableRandom.nextInt() ); - } - - byte[] key3 = new byte[ 8 ]; - reuseableRandom.nextBytes( key3 ); - request.setContent( key3 ); - return request; - - } - - @Override - public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException { - response.setHttpStatusMessage( "WebSocket Protocol Handshake" ); - response.put( "Upgrade", "WebSocket" ); - response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alive - response.put( "Sec-WebSocket-Origin", request.getFieldValue( "Origin" ) ); - String location = "ws://" + request.getFieldValue( "Host" ) + request.getResourceDescriptor(); - response.put( "Sec-WebSocket-Location", location ); - String key1 = request.getFieldValue( "Sec-WebSocket-Key1" ); - String key2 = request.getFieldValue( "Sec-WebSocket-Key2" ); - byte[] key3 = request.getContent(); - if( key1 == null || key2 == null || key3 == null || key3.length != 8 ) { - throw new InvalidHandshakeException( "Bad keys" ); - } - response.setContent( createChallenge( key1, key2, key3 ) ); - return response; - } - - @Override - public Handshakedata translateHandshake( ByteBuffer buf ) throws InvalidHandshakeException { - - HandshakeBuilder bui = translateHandshakeHttp( buf, role ); - // the first drafts are lacking a protocol number which makes them difficult to distinguish. Sec-WebSocket-Key1 is typical for draft76 - if( ( bui.hasFieldValue( "Sec-WebSocket-Key1" ) || role == Role.CLIENT ) && !bui.hasFieldValue( "Sec-WebSocket-Version" ) ) { - byte[] key3 = new byte[ role == Role.SERVER ? 8 : 16 ]; - try { - buf.get( key3 ); - } catch ( BufferUnderflowException e ) { - throw new IncompleteHandshakeException( buf.capacity() + 16 ); - } - bui.setContent( key3 ); - - } - return bui; - } - - @Override - public List translateFrame( ByteBuffer buffer ) throws InvalidDataException { - buffer.mark(); - List frames = super.translateRegularFrame( buffer ); - if( frames == null ) { - buffer.reset(); - frames = readyframes; - readingState = true; - if( currentFrame == null ) - currentFrame = ByteBuffer.allocate( 2 ); - else { - throw new InvalidFrameException(); - } - if( buffer.remaining() > currentFrame.remaining() ) { - throw new InvalidFrameException(); - } else { - currentFrame.put( buffer ); - } - if( !currentFrame.hasRemaining() ) { - if( Arrays.equals( currentFrame.array(), closehandshake ) ) { - frames.add( new CloseFrameBuilder( CloseFrame.NORMAL ) ); - return frames; - } - else{ - throw new InvalidFrameException(); - } - } else { - readyframes = new LinkedList(); - return frames; - } - } else { - return frames; - } - } - @Override - public ByteBuffer createBinaryFrame( Framedata framedata ) { - if( framedata.getOpcode() == Opcode.CLOSING ) - return ByteBuffer.wrap( closehandshake ); - return super.createBinaryFrame( framedata ); - } - - @Override - public CloseHandshakeType getCloseHandshakeType() { - return CloseHandshakeType.ONEWAY; - } - - @Override - public Draft copyInstance() { - return new Draft_76(); - } -} diff --git a/src/main/java/org/java_websocket/drafts/package-info.java b/src/main/java/org/java_websocket/drafts/package-info.java new file mode 100644 index 000000000..13114293e --- /dev/null +++ b/src/main/java/org/java_websocket/drafts/package-info.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +/** + * This package encapsulates all implementations in relation with the WebSocket drafts. + */ +package org.java_websocket.drafts; \ No newline at end of file diff --git a/src/main/java/org/java_websocket/enums/CloseHandshakeType.java b/src/main/java/org/java_websocket/enums/CloseHandshakeType.java new file mode 100644 index 000000000..0bd1f94d5 --- /dev/null +++ b/src/main/java/org/java_websocket/enums/CloseHandshakeType.java @@ -0,0 +1,8 @@ +package org.java_websocket.enums; + +/** + * Enum which represents type of handshake is required for a close + */ +public enum CloseHandshakeType { + NONE, ONEWAY, TWOWAY +} \ No newline at end of file diff --git a/src/main/java/org/java_websocket/enums/HandshakeState.java b/src/main/java/org/java_websocket/enums/HandshakeState.java new file mode 100644 index 000000000..c87750601 --- /dev/null +++ b/src/main/java/org/java_websocket/enums/HandshakeState.java @@ -0,0 +1,15 @@ +package org.java_websocket.enums; + +/** + * Enum which represents the states a handshake may be in + */ +public enum HandshakeState { + /** + * Handshake matched this Draft successfully + */ + MATCHED, + /** + * Handshake is does not match this Draft + */ + NOT_MATCHED +} \ No newline at end of file diff --git a/src/main/java/org/java_websocket/enums/Opcode.java b/src/main/java/org/java_websocket/enums/Opcode.java new file mode 100644 index 000000000..cc65e31a0 --- /dev/null +++ b/src/main/java/org/java_websocket/enums/Opcode.java @@ -0,0 +1,9 @@ +package org.java_websocket.enums; + +/** + * Enum which contains the different valid opcodes + */ +public enum Opcode { + CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING + // more to come +} \ No newline at end of file diff --git a/src/main/java/org/java_websocket/enums/ReadyState.java b/src/main/java/org/java_websocket/enums/ReadyState.java new file mode 100644 index 000000000..15bd5cc8d --- /dev/null +++ b/src/main/java/org/java_websocket/enums/ReadyState.java @@ -0,0 +1,8 @@ +package org.java_websocket.enums; + +/** + * Enum which represents the state a websocket may be in + */ +public enum ReadyState { + NOT_YET_CONNECTED, OPEN, CLOSING, CLOSED +} \ No newline at end of file diff --git a/src/main/java/org/java_websocket/enums/Role.java b/src/main/java/org/java_websocket/enums/Role.java new file mode 100644 index 000000000..8a057a308 --- /dev/null +++ b/src/main/java/org/java_websocket/enums/Role.java @@ -0,0 +1,8 @@ +package org.java_websocket.enums; + +/** + * Enum which represents the states a websocket may be in + */ +public enum Role { + CLIENT, SERVER +} \ No newline at end of file diff --git a/src/main/java/org/java_websocket/enums/package-info.java b/src/main/java/org/java_websocket/enums/package-info.java new file mode 100644 index 000000000..a5c997ea9 --- /dev/null +++ b/src/main/java/org/java_websocket/enums/package-info.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +/** + * This package encapsulates all enums. + */ +package org.java_websocket.enums; \ No newline at end of file diff --git a/src/main/java/org/java_websocket/exceptions/IncompleteException.java b/src/main/java/org/java_websocket/exceptions/IncompleteException.java new file mode 100644 index 000000000..d3e83834d --- /dev/null +++ b/src/main/java/org/java_websocket/exceptions/IncompleteException.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.exceptions; + +/** + * Exception which indicates that the frame is not yet complete + */ +public class IncompleteException extends Exception { + + /** + * It's Serializable. + */ + private static final long serialVersionUID = 7330519489840500997L; + + /** + * The preferred size + */ + private final int preferredSize; + + /** + * Constructor for the preferred size of a frame + * + * @param preferredSize the preferred size of a frame + */ + public IncompleteException(int preferredSize) { + this.preferredSize = preferredSize; + } + + /** + * Getter for the preferredSize + * + * @return the value of the preferred size + */ + public int getPreferredSize() { + return preferredSize; + } +} diff --git a/src/main/java/org/java_websocket/exceptions/IncompleteHandshakeException.java b/src/main/java/org/java_websocket/exceptions/IncompleteHandshakeException.java index 2fdb5eae2..17307c379 100644 --- a/src/main/java/org/java_websocket/exceptions/IncompleteHandshakeException.java +++ b/src/main/java/org/java_websocket/exceptions/IncompleteHandshakeException.java @@ -1,20 +1,71 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.exceptions; +/** + * exception which indicates that a incomplete handshake was received + */ public class IncompleteHandshakeException extends RuntimeException { - private static final long serialVersionUID = 7906596804233893092L; - private int newsize; + /** + * Serializable + */ + private static final long serialVersionUID = 7906596804233893092L; + + /** + * attribute which size of handshake would have been preferred + */ + private final int preferredSize; - public IncompleteHandshakeException( int newsize ) { - this.newsize = newsize; - } + /** + * constructor for a IncompleteHandshakeException + *

+ * + * @param preferredSize the preferred size + */ + public IncompleteHandshakeException(int preferredSize) { + this.preferredSize = preferredSize; + } - public IncompleteHandshakeException() { - this.newsize = 0; - } + /** + * constructor for a IncompleteHandshakeException + *

+ * preferredSize will be 0 + */ + public IncompleteHandshakeException() { + this.preferredSize = 0; + } - public int getPreferedSize() { - return newsize; - } + /** + * Getter preferredSize + * + * @return the preferredSize + */ + public int getPreferredSize() { + return preferredSize; + } } diff --git a/src/main/java/org/java_websocket/exceptions/InvalidDataException.java b/src/main/java/org/java_websocket/exceptions/InvalidDataException.java index 2ab9d328b..c34c8c941 100644 --- a/src/main/java/org/java_websocket/exceptions/InvalidDataException.java +++ b/src/main/java/org/java_websocket/exceptions/InvalidDataException.java @@ -1,34 +1,95 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.exceptions; +/** + * exception which indicates that a invalid data was received + */ public class InvalidDataException extends Exception { - /** - * Serializable - */ - private static final long serialVersionUID = 3731842424390998726L; - - private int closecode; - - public InvalidDataException( int closecode ) { - this.closecode = closecode; - } - - public InvalidDataException( int closecode , String s ) { - super( s ); - this.closecode = closecode; - } - - public InvalidDataException( int closecode , Throwable t ) { - super( t ); - this.closecode = closecode; - } - - public InvalidDataException( int closecode , String s , Throwable t ) { - super( s, t ); - this.closecode = closecode; - } - - public int getCloseCode() { - return closecode; - } + + /** + * Serializable + */ + private static final long serialVersionUID = 3731842424390998726L; + + /** + * attribute which closecode will be returned + */ + private final int closecode; + + /** + * constructor for a InvalidDataException + * + * @param closecode the closecode which will be returned + */ + public InvalidDataException(int closecode) { + this.closecode = closecode; + } + + /** + * constructor for a InvalidDataException. + * + * @param closecode the closecode which will be returned. + * @param s the detail message. + */ + public InvalidDataException(int closecode, String s) { + super(s); + this.closecode = closecode; + } + + /** + * constructor for a InvalidDataException. + * + * @param closecode the closecode which will be returned. + * @param t the throwable causing this exception. + */ + public InvalidDataException(int closecode, Throwable t) { + super(t); + this.closecode = closecode; + } + + /** + * constructor for a InvalidDataException. + * + * @param closecode the closecode which will be returned. + * @param s the detail message. + * @param t the throwable causing this exception. + */ + public InvalidDataException(int closecode, String s, Throwable t) { + super(s, t); + this.closecode = closecode; + } + + /** + * Getter closecode + * + * @return the closecode + */ + public int getCloseCode() { + return closecode; + } } diff --git a/src/main/java/org/java_websocket/exceptions/InvalidEncodingException.java b/src/main/java/org/java_websocket/exceptions/InvalidEncodingException.java new file mode 100644 index 000000000..8fdbd1a38 --- /dev/null +++ b/src/main/java/org/java_websocket/exceptions/InvalidEncodingException.java @@ -0,0 +1,37 @@ +package org.java_websocket.exceptions; + +import java.io.UnsupportedEncodingException; + +/** + * The Character Encoding is not supported. + * + * @since 1.4.0 + */ +public class InvalidEncodingException extends RuntimeException { + + /** + * attribute for the encoding exception + */ + private final UnsupportedEncodingException encodingException; + + /** + * constructor for InvalidEncodingException + * + * @param encodingException the cause for this exception + */ + public InvalidEncodingException(UnsupportedEncodingException encodingException) { + if (encodingException == null) { + throw new IllegalArgumentException(); + } + this.encodingException = encodingException; + } + + /** + * Get the exception which includes more information on the unsupported encoding + * + * @return an UnsupportedEncodingException + */ + public UnsupportedEncodingException getEncodingException() { + return encodingException; + } +} diff --git a/src/main/java/org/java_websocket/exceptions/InvalidFrameException.java b/src/main/java/org/java_websocket/exceptions/InvalidFrameException.java index c7fe41012..7de2034da 100644 --- a/src/main/java/org/java_websocket/exceptions/InvalidFrameException.java +++ b/src/main/java/org/java_websocket/exceptions/InvalidFrameException.java @@ -1,27 +1,82 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.exceptions; import org.java_websocket.framing.CloseFrame; +/** + * exception which indicates that a invalid frame was received (CloseFrame.PROTOCOL_ERROR) + */ public class InvalidFrameException extends InvalidDataException { - /** - * Serializable - */ - private static final long serialVersionUID = -9016496369828887591L; + /** + * Serializable + */ + private static final long serialVersionUID = -9016496369828887591L; - public InvalidFrameException() { - super( CloseFrame.PROTOCOL_ERROR ); - } + /** + * constructor for a InvalidFrameException + *

+ * calling InvalidDataException with closecode PROTOCOL_ERROR + */ + public InvalidFrameException() { + super(CloseFrame.PROTOCOL_ERROR); + } - public InvalidFrameException( String arg0 ) { - super( CloseFrame.PROTOCOL_ERROR, arg0 ); - } + /** + * constructor for a InvalidFrameException + *

+ * calling InvalidDataException with closecode PROTOCOL_ERROR + * + * @param s the detail message. + */ + public InvalidFrameException(String s) { + super(CloseFrame.PROTOCOL_ERROR, s); + } - public InvalidFrameException( Throwable arg0 ) { - super( CloseFrame.PROTOCOL_ERROR, arg0 ); - } + /** + * constructor for a InvalidFrameException + *

+ * calling InvalidDataException with closecode PROTOCOL_ERROR + * + * @param t the throwable causing this exception. + */ + public InvalidFrameException(Throwable t) { + super(CloseFrame.PROTOCOL_ERROR, t); + } - public InvalidFrameException( String arg0 , Throwable arg1 ) { - super( CloseFrame.PROTOCOL_ERROR, arg0, arg1 ); - } + /** + * constructor for a InvalidFrameException + *

+ * calling InvalidDataException with closecode PROTOCOL_ERROR + * + * @param s the detail message. + * @param t the throwable causing this exception. + */ + public InvalidFrameException(String s, Throwable t) { + super(CloseFrame.PROTOCOL_ERROR, s, t); + } } diff --git a/src/main/java/org/java_websocket/exceptions/InvalidHandshakeException.java b/src/main/java/org/java_websocket/exceptions/InvalidHandshakeException.java index 4d0baec86..af1fd2157 100644 --- a/src/main/java/org/java_websocket/exceptions/InvalidHandshakeException.java +++ b/src/main/java/org/java_websocket/exceptions/InvalidHandshakeException.java @@ -1,28 +1,83 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.exceptions; import org.java_websocket.framing.CloseFrame; +/** + * exception which indicates that a invalid handshake was received (CloseFrame.PROTOCOL_ERROR) + */ public class InvalidHandshakeException extends InvalidDataException { - /** - * Serializable - */ - private static final long serialVersionUID = -1426533877490484964L; + /** + * Serializable + */ + private static final long serialVersionUID = -1426533877490484964L; - public InvalidHandshakeException() { - super( CloseFrame.PROTOCOL_ERROR ); - } + /** + * constructor for a InvalidHandshakeException + *

+ * calling InvalidDataException with closecode PROTOCOL_ERROR + */ + public InvalidHandshakeException() { + super(CloseFrame.PROTOCOL_ERROR); + } - public InvalidHandshakeException( String arg0 , Throwable arg1 ) { - super( CloseFrame.PROTOCOL_ERROR, arg0, arg1 ); - } + /** + * constructor for a InvalidHandshakeException + *

+ * calling InvalidDataException with closecode PROTOCOL_ERROR + * + * @param s the detail message. + * @param t the throwable causing this exception. + */ + public InvalidHandshakeException(String s, Throwable t) { + super(CloseFrame.PROTOCOL_ERROR, s, t); + } - public InvalidHandshakeException( String arg0 ) { - super( CloseFrame.PROTOCOL_ERROR, arg0 ); - } + /** + * constructor for a InvalidHandshakeException + *

+ * calling InvalidDataException with closecode PROTOCOL_ERROR + * + * @param s the detail message. + */ + public InvalidHandshakeException(String s) { + super(CloseFrame.PROTOCOL_ERROR, s); + } - public InvalidHandshakeException( Throwable arg0 ) { - super( CloseFrame.PROTOCOL_ERROR, arg0 ); - } + /** + * constructor for a InvalidHandshakeException + *

+ * calling InvalidDataException with closecode PROTOCOL_ERROR + * + * @param t the throwable causing this exception. + */ + public InvalidHandshakeException(Throwable t) { + super(CloseFrame.PROTOCOL_ERROR, t); + } } diff --git a/src/main/java/org/java_websocket/exceptions/LimitExceededException.java b/src/main/java/org/java_websocket/exceptions/LimitExceededException.java new file mode 100644 index 000000000..0d4a81825 --- /dev/null +++ b/src/main/java/org/java_websocket/exceptions/LimitExceededException.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.exceptions; + +import org.java_websocket.framing.CloseFrame; + +/** + * exception which indicates that the message limited was exceeded (CloseFrame.TOOBIG) + */ +public class LimitExceededException extends InvalidDataException { + + /** + * Serializable + */ + private static final long serialVersionUID = 6908339749836826785L; + + /** + * A closer indication about the limit + */ + private final int limit; + + /** + * constructor for a LimitExceededException + *

+ * calling LimitExceededException with closecode TOOBIG + */ + public LimitExceededException() { + this(Integer.MAX_VALUE); + } + + /** + * constructor for a LimitExceededException + *

+ * calling InvalidDataException with closecode TOOBIG + * @param limit the allowed size which was not enough + */ + public LimitExceededException(int limit) { + super(CloseFrame.TOOBIG); + this.limit = limit; + } + + /** + * constructor for a LimitExceededException + *

+ * calling InvalidDataException with closecode TOOBIG + * @param s the detail message. + * @param limit the allowed size which was not enough + */ + public LimitExceededException(String s, int limit) { + super(CloseFrame.TOOBIG, s); + this.limit = limit; + } + + /** + * constructor for a LimitExceededException + *

+ * calling InvalidDataException with closecode TOOBIG + * + * @param s the detail message. + */ + public LimitExceededException(String s) { + this(s, Integer.MAX_VALUE); + } + + /** + * Get the limit which was hit so this exception was caused + * + * @return the limit as int + */ + public int getLimit() { + return limit; + } +} diff --git a/src/main/java/org/java_websocket/exceptions/LimitExedeedException.java b/src/main/java/org/java_websocket/exceptions/LimitExedeedException.java deleted file mode 100644 index 1ac7f8c52..000000000 --- a/src/main/java/org/java_websocket/exceptions/LimitExedeedException.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.java_websocket.exceptions; - -import org.java_websocket.framing.CloseFrame; - -public class LimitExedeedException extends InvalidDataException { - - /** - * Serializable - */ - private static final long serialVersionUID = 6908339749836826785L; - - public LimitExedeedException() { - super( CloseFrame.TOOBIG ); - } - - public LimitExedeedException( String s ) { - super( CloseFrame.TOOBIG, s ); - } - -} diff --git a/src/main/java/org/java_websocket/exceptions/NotSendableException.java b/src/main/java/org/java_websocket/exceptions/NotSendableException.java index 2b2e2293c..fbacca9c7 100644 --- a/src/main/java/org/java_websocket/exceptions/NotSendableException.java +++ b/src/main/java/org/java_websocket/exceptions/NotSendableException.java @@ -1,25 +1,66 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.exceptions; +/** + * exception which indicates the frame payload is not sendable + */ public class NotSendableException extends RuntimeException { - /** - * Serializable - */ - private static final long serialVersionUID = -6468967874576651628L; - - public NotSendableException() { - } + /** + * Serializable + */ + private static final long serialVersionUID = -6468967874576651628L; - public NotSendableException( String message ) { - super( message ); - } + /** + * constructor for a NotSendableException + * + * @param s the detail message. + */ + public NotSendableException(String s) { + super(s); + } - public NotSendableException( Throwable cause ) { - super( cause ); - } + /** + * constructor for a NotSendableException + * + * @param t the throwable causing this exception. + */ + public NotSendableException(Throwable t) { + super(t); + } - public NotSendableException( String message , Throwable cause ) { - super( message, cause ); - } + /** + * constructor for a NotSendableException + * + * @param s the detail message. + * @param t the throwable causing this exception. + */ + public NotSendableException(String s, Throwable t) { + super(s, t); + } } diff --git a/src/main/java/org/java_websocket/exceptions/WebsocketNotConnectedException.java b/src/main/java/org/java_websocket/exceptions/WebsocketNotConnectedException.java index 45c5f4dfd..082f0bd5f 100644 --- a/src/main/java/org/java_websocket/exceptions/WebsocketNotConnectedException.java +++ b/src/main/java/org/java_websocket/exceptions/WebsocketNotConnectedException.java @@ -1,5 +1,37 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.exceptions; +/** + * exception which indicates the websocket is not yet connected (ReadyState.OPEN) + */ public class WebsocketNotConnectedException extends RuntimeException { + /** + * Serializable + */ + private static final long serialVersionUID = -785314021592982715L; } diff --git a/src/main/java/org/java_websocket/exceptions/WrappedIOException.java b/src/main/java/org/java_websocket/exceptions/WrappedIOException.java new file mode 100644 index 000000000..3ec31773d --- /dev/null +++ b/src/main/java/org/java_websocket/exceptions/WrappedIOException.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + * + */ + +package org.java_websocket.exceptions; + +import java.io.IOException; +import org.java_websocket.WebSocket; + +/** + * Exception to wrap an IOException and include information about the websocket which had the + * exception + * + * @since 1.4.1 + */ +public class WrappedIOException extends Exception { + + /** + * The websocket where the IOException happened + */ + private final transient WebSocket connection; + + /** + * The IOException + */ + private final IOException ioException; + + /** + * Wrapp an IOException and include the websocket + * + * @param connection the websocket where the IOException happened + * @param ioException the IOException + */ + public WrappedIOException(WebSocket connection, IOException ioException) { + this.connection = connection; + this.ioException = ioException; + } + + /** + * The websocket where the IOException happened + * + * @return the websocket for the wrapped IOException + */ + public WebSocket getConnection() { + return connection; + } + + /** + * The wrapped IOException + * + * @return IOException which is wrapped + */ + public IOException getIOException() { + return ioException; + } +} diff --git a/src/main/java/org/java_websocket/exceptions/package-info.java b/src/main/java/org/java_websocket/exceptions/package-info.java new file mode 100644 index 000000000..2972d3c8a --- /dev/null +++ b/src/main/java/org/java_websocket/exceptions/package-info.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +/** + * This package encapsulates all implementations in relation with the exceptions thrown in this + * lib. + */ +package org.java_websocket.exceptions; \ No newline at end of file diff --git a/src/main/java/org/java_websocket/extensions/CompressionExtension.java b/src/main/java/org/java_websocket/extensions/CompressionExtension.java new file mode 100644 index 000000000..408a1588a --- /dev/null +++ b/src/main/java/org/java_websocket/extensions/CompressionExtension.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.extensions; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.framing.ControlFrame; +import org.java_websocket.framing.DataFrame; +import org.java_websocket.framing.Framedata; + +/** + * Implementation for a compression extension specified by https://tools.ietf.org/html/rfc7692 + * + * @since 1.3.5 + */ +public abstract class CompressionExtension extends DefaultExtension { + + @Override + public void isFrameValid(Framedata inputFrame) throws InvalidDataException { + if ((inputFrame instanceof DataFrame) && (inputFrame.isRSV2() || inputFrame.isRSV3())) { + throw new InvalidFrameException( + "bad rsv RSV1: " + inputFrame.isRSV1() + " RSV2: " + inputFrame.isRSV2() + " RSV3: " + + inputFrame.isRSV3()); + } + if ((inputFrame instanceof ControlFrame) && (inputFrame.isRSV1() || inputFrame.isRSV2() + || inputFrame.isRSV3())) { + throw new InvalidFrameException( + "bad rsv RSV1: " + inputFrame.isRSV1() + " RSV2: " + inputFrame.isRSV2() + " RSV3: " + + inputFrame.isRSV3()); + } + } +} diff --git a/src/main/java/org/java_websocket/extensions/DefaultExtension.java b/src/main/java/org/java_websocket/extensions/DefaultExtension.java new file mode 100644 index 000000000..3892990c1 --- /dev/null +++ b/src/main/java/org/java_websocket/extensions/DefaultExtension.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.extensions; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.framing.Framedata; + +/** + * Class which represents the normal websocket implementation specified by rfc6455. + *

+ * This is a fallback and will always be available for a Draft_6455 + * + * @since 1.3.5 + */ +public class DefaultExtension implements IExtension { + + @Override + public void decodeFrame(Framedata inputFrame) throws InvalidDataException { + //Nothing to do here + } + + @Override + public void encodeFrame(Framedata inputFrame) { + //Nothing to do here + } + + @Override + public boolean acceptProvidedExtensionAsServer(String inputExtension) { + return true; + } + + @Override + public boolean acceptProvidedExtensionAsClient(String inputExtension) { + return true; + } + + @Override + public void isFrameValid(Framedata inputFrame) throws InvalidDataException { + if (inputFrame.isRSV1() || inputFrame.isRSV2() || inputFrame.isRSV3()) { + throw new InvalidFrameException( + "bad rsv RSV1: " + inputFrame.isRSV1() + " RSV2: " + inputFrame.isRSV2() + " RSV3: " + + inputFrame.isRSV3()); + } + } + + @Override + public String getProvidedExtensionAsClient() { + return ""; + } + + @Override + public String getProvidedExtensionAsServer() { + return ""; + } + + @Override + public IExtension copyInstance() { + return new DefaultExtension(); + } + + public void reset() { + //Nothing to do here. No internal stats. + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } + + @Override + public boolean equals(Object o) { + return this == o || o != null && getClass() == o.getClass(); + } +} diff --git a/src/main/java/org/java_websocket/extensions/ExtensionRequestData.java b/src/main/java/org/java_websocket/extensions/ExtensionRequestData.java new file mode 100644 index 000000000..37bc9de20 --- /dev/null +++ b/src/main/java/org/java_websocket/extensions/ExtensionRequestData.java @@ -0,0 +1,53 @@ +package org.java_websocket.extensions; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class ExtensionRequestData { + + public static final String EMPTY_VALUE = ""; + + private Map extensionParameters; + private String extensionName; + + private ExtensionRequestData() { + extensionParameters = new LinkedHashMap<>(); + } + + public static ExtensionRequestData parseExtensionRequest(String extensionRequest) { + ExtensionRequestData extensionData = new ExtensionRequestData(); + String[] parts = extensionRequest.split(";"); + extensionData.extensionName = parts[0].trim(); + + for (int i = 1; i < parts.length; i++) { + String[] keyValue = parts[i].split("="); + String value = EMPTY_VALUE; + + // Some parameters don't take a value. For those that do, parse the value. + if (keyValue.length > 1) { + String tempValue = keyValue[1].trim(); + + // If the value is wrapped in quotes, just get the data between them. + if ((tempValue.startsWith("\"") && tempValue.endsWith("\"")) + || (tempValue.startsWith("'") && tempValue.endsWith("'")) + && tempValue.length() > 2) { + tempValue = tempValue.substring(1, tempValue.length() - 1); + } + + value = tempValue; + } + + extensionData.extensionParameters.put(keyValue[0].trim(), value); + } + + return extensionData; + } + + public String getExtensionName() { + return extensionName; + } + + public Map getExtensionParameters() { + return extensionParameters; + } +} diff --git a/src/main/java/org/java_websocket/extensions/IExtension.java b/src/main/java/org/java_websocket/extensions/IExtension.java new file mode 100644 index 000000000..02bf581a4 --- /dev/null +++ b/src/main/java/org/java_websocket/extensions/IExtension.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.extensions; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.framing.Framedata; + +/** + * Interface which specifies all required methods to develop a websocket extension. + * + * @since 1.3.5 + */ +public interface IExtension { + + /** + * Decode a frame with a extension specific algorithm. The algorithm is subject to be implemented + * by the specific extension. The resulting frame will be used in the application + * + * @param inputFrame the frame, which has do be decoded to be used in the application + * @throws InvalidDataException Throw InvalidDataException if the received frame is not correctly + * implemented by the other endpoint or there are other protocol + * errors/decoding errors + * @since 1.3.5 + */ + void decodeFrame(Framedata inputFrame) throws InvalidDataException; + + /** + * Encode a frame with a extension specific algorithm. The algorithm is subject to be implemented + * by the specific extension. The resulting frame will be send to the other endpoint. + * + * @param inputFrame the frame, which has do be encoded to be used on the other endpoint + * @since 1.3.5 + */ + void encodeFrame(Framedata inputFrame); + + /** + * Check if the received Sec-WebSocket-Extensions header field contains a offer for the specific + * extension if the endpoint is in the role of a server + * + * @param inputExtensionHeader the received Sec-WebSocket-Extensions header field offered by the + * other endpoint + * @return true, if the offer does fit to this specific extension + * @since 1.3.5 + */ + boolean acceptProvidedExtensionAsServer(String inputExtensionHeader); + + /** + * Check if the received Sec-WebSocket-Extensions header field contains a offer for the specific + * extension if the endpoint is in the role of a client + * + * @param inputExtensionHeader the received Sec-WebSocket-Extensions header field offered by the + * other endpoint + * @return true, if the offer does fit to this specific extension + * @since 1.3.5 + */ + boolean acceptProvidedExtensionAsClient(String inputExtensionHeader); + + /** + * Check if the received frame is correctly implemented by the other endpoint and there are no + * specification errors (like wrongly set RSV) + * + * @param inputFrame the received frame + * @throws InvalidDataException Throw InvalidDataException if the received frame is not correctly + * implementing the specification for the specific extension + * @since 1.3.5 + */ + void isFrameValid(Framedata inputFrame) throws InvalidDataException; + + /** + * Return the specific Sec-WebSocket-Extensions header offer for this extension if the endpoint is + * in the role of a client. If the extension returns an empty string (""), the offer will not be + * included in the handshake. + * + * @return the specific Sec-WebSocket-Extensions header for this extension + * @since 1.3.5 + */ + String getProvidedExtensionAsClient(); + + /** + * Return the specific Sec-WebSocket-Extensions header offer for this extension if the endpoint is + * in the role of a server. If the extension returns an empty string (""), the offer will not be + * included in the handshake. + * + * @return the specific Sec-WebSocket-Extensions header for this extension + * @since 1.3.5 + */ + String getProvidedExtensionAsServer(); + + /** + * Extensions must only be by one websocket at all. To prevent extensions to be used more than + * once the Websocket implementation should call this method in order to create a new usable + * version of a given extension instance.
The copy can be safely used in conjunction with a + * new websocket connection. + * + * @return a copy of the extension + * @since 1.3.5 + */ + IExtension copyInstance(); + + /** + * Cleaning up internal stats when the draft gets reset. + * + * @since 1.3.5 + */ + void reset(); + + /** + * Return a string which should contain the class name as well as additional information about the + * current configurations for this extension (DEBUG purposes) + * + * @return a string containing the class name as well as additional information + * @since 1.3.5 + */ + String toString(); +} diff --git a/src/main/java/org/java_websocket/extensions/package-info.java b/src/main/java/org/java_websocket/extensions/package-info.java new file mode 100644 index 000000000..251cbdf9f --- /dev/null +++ b/src/main/java/org/java_websocket/extensions/package-info.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +/** + * This package encapsulates all interfaces and implementations in relation with the WebSocket + * Sec-WebSocket-Extensions. + */ +package org.java_websocket.extensions; \ No newline at end of file diff --git a/src/main/java/org/java_websocket/extensions/permessage_deflate/PerMessageDeflateExtension.java b/src/main/java/org/java_websocket/extensions/permessage_deflate/PerMessageDeflateExtension.java new file mode 100644 index 000000000..9eb16ca15 --- /dev/null +++ b/src/main/java/org/java_websocket/extensions/permessage_deflate/PerMessageDeflateExtension.java @@ -0,0 +1,372 @@ +package org.java_websocket.extensions.permessage_deflate; + +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import java.util.zip.Inflater; +import org.java_websocket.enums.Opcode; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.extensions.CompressionExtension; +import org.java_websocket.extensions.ExtensionRequestData; +import org.java_websocket.extensions.IExtension; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.ContinuousFrame; +import org.java_websocket.framing.DataFrame; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.FramedataImpl1; + +/** + * PerMessage Deflate Extension (7. The + * "permessage-deflate" Extension in + * RFC 7692). + * + * @see 7. The "permessage-deflate" + * Extension in RFC 7692 + */ +public class PerMessageDeflateExtension extends CompressionExtension { + + // Name of the extension as registered by IETF https://tools.ietf.org/html/rfc7692#section-9. + private static final String EXTENSION_REGISTERED_NAME = "permessage-deflate"; + // Below values are defined for convenience. They are not used in the compression/decompression phase. + // They may be needed during the extension-negotiation offer in the future. + private static final String SERVER_NO_CONTEXT_TAKEOVER = "server_no_context_takeover"; + private static final String CLIENT_NO_CONTEXT_TAKEOVER = "client_no_context_takeover"; + private static final String SERVER_MAX_WINDOW_BITS = "server_max_window_bits"; + private static final String CLIENT_MAX_WINDOW_BITS = "client_max_window_bits"; + private static final int serverMaxWindowBits = 1 << 15; + private static final int clientMaxWindowBits = 1 << 15; + private static final byte[] TAIL_BYTES = {(byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF}; + private static final int BUFFER_SIZE = 1 << 10; + + private int threshold = 1024; + + private boolean serverNoContextTakeover = true; + private boolean clientNoContextTakeover = false; + + // For WebSocketServers, this variable holds the extension parameters that the peer client has requested. + // For WebSocketClients, this variable holds the extension parameters that client himself has requested. + private Map requestedParameters = new LinkedHashMap<>(); + + private final int compressionLevel; + + private final Inflater inflater; + private final Deflater deflater; + + /** + * Constructor for the PerMessage Deflate Extension (7. Thepermessage-deflate" Extension) + * + * Uses {@link java.util.zip.Deflater#DEFAULT_COMPRESSION} as the compression level for the {@link java.util.zip.Deflater#Deflater(int)} + */ + public PerMessageDeflateExtension() { + this(Deflater.DEFAULT_COMPRESSION); + } + + /** + * Constructor for the PerMessage Deflate Extension (7. Thepermessage-deflate" Extension) + * + * @param compressionLevel The compression level passed to the {@link java.util.zip.Deflater#Deflater(int)} + */ + public PerMessageDeflateExtension(int compressionLevel) { + this.compressionLevel = compressionLevel; + this.deflater = new Deflater(this.compressionLevel, true); + this.inflater = new Inflater(true); + } + + /** + * Get the compression level used for the compressor. + * @return the compression level + */ + public int getCompressionLevel() { + return this.compressionLevel; + } + + /** + * Get the size threshold for doing the compression + * @return Size (in bytes) below which messages will not be compressed + * @since 1.5.3 + */ + public int getThreshold() { + return threshold; + } + + /** + * Set the size when payloads smaller than this will not be compressed. + * @param threshold the size in bytes + * @since 1.5.3 + */ + public void setThreshold(int threshold) { + this.threshold = threshold; + } + + /** + * Access the "server_no_context_takeover" extension parameter + * + * @see The "server_no_context_takeover" Extension Parameter + * @return serverNoContextTakeover is the server no context parameter active + */ + public boolean isServerNoContextTakeover() { + return serverNoContextTakeover; + } + + /** + * Setter for the "server_no_context_takeover" extension parameter + * @see The "server_no_context_takeover" Extension Parameter + * @param serverNoContextTakeover set the server no context parameter + */ + public void setServerNoContextTakeover(boolean serverNoContextTakeover) { + this.serverNoContextTakeover = serverNoContextTakeover; + } + + /** + * Access the "client_no_context_takeover" extension parameter + * + * @see The "client_no_context_takeover" Extension Parameter + * @return clientNoContextTakeover is the client no context parameter active + */ + public boolean isClientNoContextTakeover() { + return clientNoContextTakeover; + } + + /** + * Setter for the "client_no_context_takeover" extension parameter + * @see The "client_no_context_takeover" Extension Parameter + * @param clientNoContextTakeover set the client no context parameter + */ + public void setClientNoContextTakeover(boolean clientNoContextTakeover) { + this.clientNoContextTakeover = clientNoContextTakeover; + } + + /* + An endpoint uses the following algorithm to decompress a message. + 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the + payload of the message. + 2. Decompress the resulting data using DEFLATE. + See, https://tools.ietf.org/html/rfc7692#section-7.2.2 + */ + @Override + public void decodeFrame(Framedata inputFrame) throws InvalidDataException { + // Only DataFrames can be decompressed. + if (!(inputFrame instanceof DataFrame)) { + return; + } + + if (!inputFrame.isRSV1() && inputFrame.getOpcode() != Opcode.CONTINUOUS) { + return; + } + + // RSV1 bit must be set only for the first frame. + if (inputFrame.getOpcode() == Opcode.CONTINUOUS && inputFrame.isRSV1()) { + throw new InvalidDataException(CloseFrame.POLICY_VALIDATION, + "RSV1 bit can only be set for the first frame."); + } + + // Decompressed output buffer. + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try { + decompress(inputFrame.getPayloadData().array(), output); + + /* + If a message is "first fragmented and then compressed", as this project does, then the inflater + can not inflate fragments except the first one. + This behavior occurs most likely because those fragments end with "final deflate blocks". + We can check the getRemaining() method to see whether the data we supplied has been decompressed or not. + And if not, we just reset the inflater and decompress again. + Note that this behavior doesn't occur if the message is "first compressed and then fragmented". + */ + if (inflater.getRemaining() > 0) { + inflater.reset(); + decompress(inputFrame.getPayloadData().array(), output); + } + + if (inputFrame.isFin()) { + decompress(TAIL_BYTES, output); + // If context takeover is disabled, inflater can be reset. + if (clientNoContextTakeover) { + inflater.reset(); + } + } + } catch (DataFormatException e) { + throw new InvalidDataException(CloseFrame.POLICY_VALIDATION, e.getMessage()); + } + + // Set frames payload to the new decompressed data. + ((FramedataImpl1) inputFrame) + .setPayload(ByteBuffer.wrap(output.toByteArray(), 0, output.size())); + } + + /** + * @param data the bytes of data + * @param outputBuffer the output stream + * @throws DataFormatException + */ + private void decompress(byte[] data, ByteArrayOutputStream outputBuffer) + throws DataFormatException { + inflater.setInput(data); + byte[] buffer = new byte[BUFFER_SIZE]; + + int bytesInflated; + while ((bytesInflated = inflater.inflate(buffer)) > 0) { + outputBuffer.write(buffer, 0, bytesInflated); + } + } + + @Override + public void encodeFrame(Framedata inputFrame) { + // Only DataFrames can be decompressed. + if (!(inputFrame instanceof DataFrame)) { + return; + } + + byte[] payloadData = inputFrame.getPayloadData().array(); + if (payloadData.length < threshold) { + return; + } + // Only the first frame's RSV1 must be set. + if (!(inputFrame instanceof ContinuousFrame)) { + ((DataFrame) inputFrame).setRSV1(true); + } + + deflater.setInput(payloadData); + // Compressed output buffer. + ByteArrayOutputStream output = new ByteArrayOutputStream(); + // Temporary buffer to hold compressed output. + byte[] buffer = new byte[1024]; + int bytesCompressed; + while ((bytesCompressed = deflater.deflate(buffer, 0, buffer.length, Deflater.SYNC_FLUSH)) + > 0) { + output.write(buffer, 0, bytesCompressed); + } + + byte[] outputBytes = output.toByteArray(); + int outputLength = outputBytes.length; + + /* + https://tools.ietf.org/html/rfc7692#section-7.2.1 states that if the final fragment's compressed + payload ends with 0x00 0x00 0xff 0xff, they should be removed. + To simulate removal, we just pass 4 bytes less to the new payload + if the frame is final and outputBytes ends with 0x00 0x00 0xff 0xff. + */ + if (inputFrame.isFin()) { + if (endsWithTail(outputBytes)) { + outputLength -= TAIL_BYTES.length; + } + + if (serverNoContextTakeover) { + deflater.reset(); + } + } + + // Set frames payload to the new compressed data. + ((FramedataImpl1) inputFrame).setPayload(ByteBuffer.wrap(outputBytes, 0, outputLength)); + } + + /** + * @param data the bytes of data + * @return true if the data is OK + */ + private static boolean endsWithTail(byte[] data) { + if (data.length < 4) { + return false; + } + + int length = data.length; + for (int i = 0; i < TAIL_BYTES.length; i++) { + if (TAIL_BYTES[i] != data[length - TAIL_BYTES.length + i]) { + return false; + } + } + + return true; + } + + @Override + public boolean acceptProvidedExtensionAsServer(String inputExtension) { + String[] requestedExtensions = inputExtension.split(","); + for (String extension : requestedExtensions) { + ExtensionRequestData extensionData = ExtensionRequestData.parseExtensionRequest(extension); + if (!EXTENSION_REGISTERED_NAME.equalsIgnoreCase(extensionData.getExtensionName())) { + continue; + } + + // Holds parameters that peer client has sent. + Map headers = extensionData.getExtensionParameters(); + requestedParameters.putAll(headers); + if (requestedParameters.containsKey(CLIENT_NO_CONTEXT_TAKEOVER)) { + clientNoContextTakeover = true; + } + + return true; + } + + return false; + } + + @Override + public boolean acceptProvidedExtensionAsClient(String inputExtension) { + String[] requestedExtensions = inputExtension.split(","); + for (String extension : requestedExtensions) { + ExtensionRequestData extensionData = ExtensionRequestData.parseExtensionRequest(extension); + if (!EXTENSION_REGISTERED_NAME.equalsIgnoreCase(extensionData.getExtensionName())) { + continue; + } + + // Holds parameters that are sent by the server, as a response to our initial extension request. + Map headers = extensionData.getExtensionParameters(); + // After this point, parameters that the server sent back can be configured, but we don't use them for now. + return true; + } + + return false; + } + + @Override + public String getProvidedExtensionAsClient() { + requestedParameters.put(CLIENT_NO_CONTEXT_TAKEOVER, ExtensionRequestData.EMPTY_VALUE); + requestedParameters.put(SERVER_NO_CONTEXT_TAKEOVER, ExtensionRequestData.EMPTY_VALUE); + + return EXTENSION_REGISTERED_NAME + "; " + SERVER_NO_CONTEXT_TAKEOVER + "; " + + CLIENT_NO_CONTEXT_TAKEOVER; + } + + @Override + public String getProvidedExtensionAsServer() { + return EXTENSION_REGISTERED_NAME + + "; " + SERVER_NO_CONTEXT_TAKEOVER + + (clientNoContextTakeover ? "; " + CLIENT_NO_CONTEXT_TAKEOVER : ""); + } + + @Override + public IExtension copyInstance() { + PerMessageDeflateExtension clone = new PerMessageDeflateExtension(this.getCompressionLevel()); + clone.setThreshold(this.getThreshold()); + clone.setClientNoContextTakeover(this.isClientNoContextTakeover()); + clone.setServerNoContextTakeover(this.isServerNoContextTakeover()); + return clone; + } + + /** + * This extension requires the RSV1 bit to be set only for the first frame. If the frame is type + * is CONTINUOUS, RSV1 bit must be unset. + */ + @Override + public void isFrameValid(Framedata inputFrame) throws InvalidDataException { + if ((inputFrame instanceof ContinuousFrame) && (inputFrame.isRSV1() || inputFrame.isRSV2() + || inputFrame.isRSV3())) { + throw new InvalidFrameException( + "bad rsv RSV1: " + inputFrame.isRSV1() + " RSV2: " + inputFrame.isRSV2() + " RSV3: " + + inputFrame.isRSV3()); + } + super.isFrameValid(inputFrame); + } + + @Override + public String toString() { + return "PerMessageDeflateExtension"; + } + + +} diff --git a/src/main/java/org/java_websocket/framing/BinaryFrame.java b/src/main/java/org/java_websocket/framing/BinaryFrame.java new file mode 100644 index 000000000..dc0544954 --- /dev/null +++ b/src/main/java/org/java_websocket/framing/BinaryFrame.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.framing; + +import org.java_websocket.enums.Opcode; + +/** + * Class to represent a binary frame + */ +public class BinaryFrame extends DataFrame { + + /** + * constructor which sets the opcode of this frame to binary + */ + public BinaryFrame() { + super(Opcode.BINARY); + } +} diff --git a/src/main/java/org/java_websocket/framing/CloseFrame.java b/src/main/java/org/java_websocket/framing/CloseFrame.java index f253b8dd6..5a63d8ae5 100644 --- a/src/main/java/org/java_websocket/framing/CloseFrame.java +++ b/src/main/java/org/java_websocket/framing/CloseFrame.java @@ -1,98 +1,341 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.framing; +import java.nio.ByteBuffer; +import org.java_websocket.enums.Opcode; import org.java_websocket.exceptions.InvalidDataException; import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.util.ByteBufferUtils; +import org.java_websocket.util.Charsetfunctions; + +/** + * Class to represent a close frame + */ +public class CloseFrame extends ControlFrame { + + /** + * indicates a normal closure, meaning whatever purpose the connection was established for has + * been fulfilled. + */ + public static final int NORMAL = 1000; + /** + * 1001 indicates that an endpoint is "going away", such as a server going down, or a browser + * having navigated away from a page. + */ + public static final int GOING_AWAY = 1001; + /** + * 1002 indicates that an endpoint is terminating the connection due to a protocol error. + */ + public static final int PROTOCOL_ERROR = 1002; + /** + * 1003 indicates that an endpoint is terminating the connection because it has received a type of + * data it cannot accept (e.g. an endpoint that understands only text data MAY send this if it + * receives a binary message). + */ + public static final int REFUSE = 1003; + /*1004: Reserved. The specific meaning might be defined in the future.*/ + /** + * 1005 is a reserved value and MUST NOT be set as a status code in a Close control frame by an + * endpoint. It is designated for use in applications expecting a status code to indicate that no + * status code was actually present. + */ + public static final int NOCODE = 1005; + /** + * 1006 is a reserved value and MUST NOT be set as a status code in a Close control frame by an + * endpoint. It is designated for use in applications expecting a status code to indicate that the + * connection was closed abnormally, e.g. without sending or receiving a Close control frame. + */ + public static final int ABNORMAL_CLOSE = 1006; + /** + * 1007 indicates that an endpoint is terminating the connection because it has received data + * within a message that was not consistent with the type of the message (e.g., non-UTF-8 + * [RFC3629] data within a text message). + */ + public static final int NO_UTF8 = 1007; + /** + * 1008 indicates that an endpoint is terminating the connection because it has received a message + * that violates its policy. This is a generic status code that can be returned when there is no + * other more suitable status code (e.g. 1003 or 1009), or if there is a need to hide specific + * details about the policy. + */ + public static final int POLICY_VALIDATION = 1008; + /** + * 1009 indicates that an endpoint is terminating the connection because it has received a message + * which is too big for it to process. + */ + public static final int TOOBIG = 1009; + /** + * 1010 indicates that an endpoint (client) is terminating the connection because it has expected + * the server to negotiate one or more extension, but the server didn't return them in the + * response message of the WebSocket handshake. The list of extensions which are needed SHOULD + * appear in the /reason/ part of the Close frame. Note that this status code is not used by the + * server, because it can fail the WebSocket handshake instead. + */ + public static final int EXTENSION = 1010; + /** + * 1011 indicates that a server is terminating the connection because it encountered an unexpected + * condition that prevented it from fulfilling the request. + **/ + public static final int UNEXPECTED_CONDITION = 1011; + /** + * 1012 indicates that the service is restarted. A client may reconnect, and if it choses to do, + * should reconnect using a randomized delay of 5 - 30s. See https://www.ietf.org/mail-archive/web/hybi/current/msg09670.html + * for more information. + * + * @since 1.3.8 + **/ + public static final int SERVICE_RESTART = 1012; + /** + * 1013 indicates that the service is experiencing overload. A client should only connect to a + * different IP (when there are multiple for the target) or reconnect to the same IP upon user + * action. See https://www.ietf.org/mail-archive/web/hybi/current/msg09670.html for more + * information. + * + * @since 1.3.8 + **/ + public static final int TRY_AGAIN_LATER = 1013; + /** + * 1014 indicates that the server was acting as a gateway or proxy and received an invalid + * response from the upstream server. This is similar to 502 HTTP Status Code See + * https://www.ietf.org/mail-archive/web/hybi/current/msg10748.html fore more information. + * + * @since 1.3.8 + **/ + public static final int BAD_GATEWAY = 1014; + /** + * 1015 is a reserved value and MUST NOT be set as a status code in a Close control frame by an + * endpoint. It is designated for use in applications expecting a status code to indicate that the + * connection was closed due to a failure to perform a TLS handshake (e.g., the server certificate + * can't be verified). + **/ + public static final int TLS_ERROR = 1015; + + /** + * The connection had never been established + */ + public static final int NEVER_CONNECTED = -1; + + /** + * The connection had a buggy close (this should not happen) + */ + public static final int BUGGYCLOSE = -2; + + /** + * The connection was flushed and closed + */ + public static final int FLASHPOLICY = -3; + + + /** + * The close code used in this close frame + */ + private int code; + + /** + * The close message used in this close frame + */ + private String reason; + + /** + * Constructor for a close frame + *

+ * Using opcode closing and fin = true + */ + public CloseFrame() { + super(Opcode.CLOSING); + setReason(""); + setCode(CloseFrame.NORMAL); + } + + /** + * Set the close code for this close frame + * + * @param code the close code + */ + public void setCode(int code) { + this.code = code; + // CloseFrame.TLS_ERROR is not allowed to be transferred over the wire + if (code == CloseFrame.TLS_ERROR) { + this.code = CloseFrame.NOCODE; + this.reason = ""; + } + updatePayload(); + } + + /** + * Set the close reason for this close frame + * + * @param reason the reason code + */ + public void setReason(String reason) { + if (reason == null) { + reason = ""; + } + this.reason = reason; + updatePayload(); + } + + /** + * Get the used close code + * + * @return the used close code + */ + public int getCloseCode() { + return code; + } + + /** + * Get the message that closeframe is containing + * + * @return the message in this frame + */ + public String getMessage() { + return reason; + } + + @Override + public String toString() { + return super.toString() + "code: " + code; + } + + @Override + public void isValid() throws InvalidDataException { + super.isValid(); + if (code == CloseFrame.NO_UTF8 && reason.isEmpty()) { + throw new InvalidDataException(CloseFrame.NO_UTF8, "Received text is no valid utf8 string!"); + } + if (code == CloseFrame.NOCODE && 0 < reason.length()) { + throw new InvalidDataException(PROTOCOL_ERROR, + "A close frame must have a closecode if it has a reason"); + } + //Intentional check for code != CloseFrame.TLS_ERROR just to make sure even if the code earlier changes + if ((code > CloseFrame.TLS_ERROR && code < 3000)) { + throw new InvalidDataException(PROTOCOL_ERROR, "Trying to send an illegal close code!"); + } + if (code == CloseFrame.ABNORMAL_CLOSE || code == CloseFrame.TLS_ERROR + || code == CloseFrame.NOCODE || code > 4999 || code < 1000 || code == 1004) { + throw new InvalidFrameException("closecode must not be sent over the wire: " + code); + } + } + + @Override + public void setPayload(ByteBuffer payload) { + code = CloseFrame.NOCODE; + reason = ""; + payload.mark(); + if (payload.remaining() == 0) { + code = CloseFrame.NORMAL; + } else if (payload.remaining() == 1) { + code = CloseFrame.PROTOCOL_ERROR; + } else { + if (payload.remaining() >= 2) { + ByteBuffer bb = ByteBuffer.allocate(4); + bb.position(2); + bb.putShort(payload.getShort()); + bb.position(0); + code = bb.getInt(); + } + payload.reset(); + try { + int mark = payload.position();// because stringUtf8 also creates a mark + validateUtf8(payload, mark); + } catch (InvalidDataException e) { + code = CloseFrame.NO_UTF8; + reason = null; + } + } + } + + /** + * Validate the payload to valid utf8 + * + * @param mark the current mark + * @param payload the current payload + * @throws InvalidDataException the current payload is not a valid utf8 + */ + private void validateUtf8(ByteBuffer payload, int mark) throws InvalidDataException { + try { + payload.position(payload.position() + 2); + reason = Charsetfunctions.stringUtf8(payload); + } catch (IllegalArgumentException e) { + throw new InvalidDataException(CloseFrame.NO_UTF8); + } finally { + payload.position(mark); + } + } + + /** + * Update the payload to represent the close code and the reason + */ + private void updatePayload() { + byte[] by = Charsetfunctions.utf8Bytes(reason); + ByteBuffer buf = ByteBuffer.allocate(4); + buf.putInt(code); + buf.position(2); + ByteBuffer pay = ByteBuffer.allocate(2 + by.length); + pay.put(buf); + pay.put(by); + pay.rewind(); + super.setPayload(pay); + } + + @Override + public ByteBuffer getPayloadData() { + if (code == NOCODE) { + return ByteBufferUtils.getEmptyByteBuffer(); + } + return super.getPayloadData(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + + CloseFrame that = (CloseFrame) o; + + if (code != that.code) { + return false; + } + return reason != null ? reason.equals(that.reason) : that.reason == null; + } -public interface CloseFrame extends Framedata { - /** - * indicates a normal closure, meaning whatever purpose the - * connection was established for has been fulfilled. - */ - public static final int NORMAL = 1000; - /** - * 1001 indicates that an endpoint is "going away", such as a server - * going down, or a browser having navigated away from a page. - */ - public static final int GOING_AWAY = 1001; - /** - * 1002 indicates that an endpoint is terminating the connection due - * to a protocol error. - */ - public static final int PROTOCOL_ERROR = 1002; - /** - * 1003 indicates that an endpoint is terminating the connection - * because it has received a type of data it cannot accept (e.g. an - * endpoint that understands only text data MAY send this if it - * receives a binary message). - */ - public static final int REFUSE = 1003; - /*1004: Reserved. The specific meaning might be defined in the future.*/ - /** - * 1005 is a reserved value and MUST NOT be set as a status code in a - * Close control frame by an endpoint. It is designated for use in - * applications expecting a status code to indicate that no status - * code was actually present. - */ - public static final int NOCODE = 1005; - /** - * 1006 is a reserved value and MUST NOT be set as a status code in a - * Close control frame by an endpoint. It is designated for use in - * applications expecting a status code to indicate that the - * connection was closed abnormally, e.g. without sending or - * receiving a Close control frame. - */ - public static final int ABNORMAL_CLOSE = 1006; - /** - * 1007 indicates that an endpoint is terminating the connection - * because it has received data within a message that was not - * consistent with the type of the message (e.g., non-UTF-8 [RFC3629] - * data within a text message). - */ - public static final int NO_UTF8 = 1007; - /** - * 1008 indicates that an endpoint is terminating the connection - * because it has received a message that violates its policy. This - * is a generic status code that can be returned when there is no - * other more suitable status code (e.g. 1003 or 1009), or if there - * is a need to hide specific details about the policy. - */ - public static final int POLICY_VALIDATION = 1008; - /** - * 1009 indicates that an endpoint is terminating the connection - * because it has received a message which is too big for it to - * process. - */ - public static final int TOOBIG = 1009; - /** - * 1010 indicates that an endpoint (client) is terminating the - * connection because it has expected the server to negotiate one or - * more extension, but the server didn't return them in the response - * message of the WebSocket handshake. The list of extensions which - * are needed SHOULD appear in the /reason/ part of the Close frame. - * Note that this status code is not used by the server, because it - * can fail the WebSocket handshake instead. - */ - public static final int EXTENSION = 1010; - /** - * 1011 indicates that a server is terminating the connection because - * it encountered an unexpected condition that prevented it from - * fulfilling the request. - **/ - public static final int UNEXPECTED_CONDITION = 1011; - /** - * 1015 is a reserved value and MUST NOT be set as a status code in a - * Close control frame by an endpoint. It is designated for use in - * applications expecting a status code to indicate that the - * connection was closed due to a failure to perform a TLS handshake - * (e.g., the server certificate can't be verified). - **/ - public static final int TLS_ERROR = 1015; - - /** The connection had never been established */ - public static final int NEVER_CONNECTED = -1; - public static final int BUGGYCLOSE = -2; - public static final int FLASHPOLICY = -3; - - public int getCloseCode() throws InvalidFrameException; - public String getMessage() throws InvalidDataException; + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + code; + result = 31 * result + (reason != null ? reason.hashCode() : 0); + return result; + } } diff --git a/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java b/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java deleted file mode 100644 index fee1b540d..000000000 --- a/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.java_websocket.framing; - -import java.nio.ByteBuffer; - -import org.java_websocket.exceptions.InvalidDataException; -import org.java_websocket.exceptions.InvalidFrameException; -import org.java_websocket.util.Charsetfunctions; - -public class CloseFrameBuilder extends FramedataImpl1 implements CloseFrame { - - static final ByteBuffer emptybytebuffer = ByteBuffer.allocate( 0 ); - - private int code; - private String reason; - - public CloseFrameBuilder() { - super( Opcode.CLOSING ); - setFin( true ); - } - - public CloseFrameBuilder( int code ) throws InvalidDataException { - super( Opcode.CLOSING ); - setFin( true ); - setCodeAndMessage( code, "" ); - } - - public CloseFrameBuilder( int code , String m ) throws InvalidDataException { - super( Opcode.CLOSING ); - setFin( true ); - setCodeAndMessage( code, m ); - } - - private void setCodeAndMessage( int code, String m ) throws InvalidDataException { - if( m == null ) { - m = ""; - } - // CloseFrame.TLS_ERROR is not allowed to be transfered over the wire - if( code == CloseFrame.TLS_ERROR ) { - code = CloseFrame.NOCODE; - m = ""; - } - if( code == CloseFrame.NOCODE ) { - if( 0 < m.length() ) { - throw new InvalidDataException( PROTOCOL_ERROR, "A close frame must have a closecode if it has a reason" ); - } - return;// empty payload - } - - byte[] by = Charsetfunctions.utf8Bytes( m ); - ByteBuffer buf = ByteBuffer.allocate( 4 ); - buf.putInt( code ); - buf.position( 2 ); - ByteBuffer pay = ByteBuffer.allocate( 2 + by.length ); - pay.put( buf ); - pay.put( by ); - pay.rewind(); - setPayload( pay ); - } - - private void initCloseCode() throws InvalidFrameException { - code = CloseFrame.NOCODE; - ByteBuffer payload = super.getPayloadData(); - payload.mark(); - if( payload.remaining() >= 2 ) { - ByteBuffer bb = ByteBuffer.allocate( 4 ); - bb.position( 2 ); - bb.putShort( payload.getShort() ); - bb.position( 0 ); - code = bb.getInt(); - - if( code == CloseFrame.ABNORMAL_CLOSE || code == CloseFrame.TLS_ERROR || code == CloseFrame.NOCODE || code > 4999 || code < 1000 || code == 1004 ) { - throw new InvalidFrameException( "closecode must not be sent over the wire: " + code ); - } - } - payload.reset(); - } - - @Override - public int getCloseCode() { - return code; - } - - private void initMessage() throws InvalidDataException { - if( code == CloseFrame.NOCODE ) { - reason = Charsetfunctions.stringUtf8( super.getPayloadData() ); - } else { - ByteBuffer b = super.getPayloadData(); - int mark = b.position();// because stringUtf8 also creates a mark - try { - b.position( b.position() + 2 ); - reason = Charsetfunctions.stringUtf8( b ); - } catch ( IllegalArgumentException e ) { - throw new InvalidFrameException( e ); - } finally { - b.position( mark ); - } - } - } - - @Override - public String getMessage() { - return reason; - } - - @Override - public String toString() { - return super.toString() + "code: " + code; - } - - @Override - public void setPayload( ByteBuffer payload ) throws InvalidDataException { - super.setPayload( payload ); - initCloseCode(); - initMessage(); - } - @Override - public ByteBuffer getPayloadData() { - if( code == NOCODE ) - return emptybytebuffer; - return super.getPayloadData(); - } - -} diff --git a/src/main/java/org/java_websocket/framing/ContinuousFrame.java b/src/main/java/org/java_websocket/framing/ContinuousFrame.java new file mode 100644 index 000000000..c518cc3bd --- /dev/null +++ b/src/main/java/org/java_websocket/framing/ContinuousFrame.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.framing; + +import org.java_websocket.enums.Opcode; + +/** + * Class to represent a continuous frame + */ +public class ContinuousFrame extends DataFrame { + + /** + * constructor which sets the opcode of this frame to continuous + */ + public ContinuousFrame() { + super(Opcode.CONTINUOUS); + } +} diff --git a/src/main/java/org/java_websocket/framing/ControlFrame.java b/src/main/java/org/java_websocket/framing/ControlFrame.java new file mode 100644 index 000000000..1469dc5e6 --- /dev/null +++ b/src/main/java/org/java_websocket/framing/ControlFrame.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.framing; + +import org.java_websocket.enums.Opcode; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; + +/** + * Abstract class to represent control frames + */ +public abstract class ControlFrame extends FramedataImpl1 { + + /** + * Class to represent a control frame + * + * @param opcode the opcode to use + */ + public ControlFrame(Opcode opcode) { + super(opcode); + } + + @Override + public void isValid() throws InvalidDataException { + if (!isFin()) { + throw new InvalidFrameException("Control frame can't have fin==false set"); + } + if (isRSV1()) { + throw new InvalidFrameException("Control frame can't have rsv1==true set"); + } + if (isRSV2()) { + throw new InvalidFrameException("Control frame can't have rsv2==true set"); + } + if (isRSV3()) { + throw new InvalidFrameException("Control frame can't have rsv3==true set"); + } + } +} diff --git a/src/main/java/org/java_websocket/framing/DataFrame.java b/src/main/java/org/java_websocket/framing/DataFrame.java new file mode 100644 index 000000000..c845c2ced --- /dev/null +++ b/src/main/java/org/java_websocket/framing/DataFrame.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.framing; + +import org.java_websocket.enums.Opcode; +import org.java_websocket.exceptions.InvalidDataException; + +/** + * Abstract class to represent data frames + */ +public abstract class DataFrame extends FramedataImpl1 { + + /** + * Class to represent a data frame + * + * @param opcode the opcode to use + */ + public DataFrame(Opcode opcode) { + super(opcode); + } + + @Override + public void isValid() throws InvalidDataException { + //Nothing specific to check + } +} diff --git a/src/main/java/org/java_websocket/framing/FrameBuilder.java b/src/main/java/org/java_websocket/framing/FrameBuilder.java deleted file mode 100644 index 25a20de43..000000000 --- a/src/main/java/org/java_websocket/framing/FrameBuilder.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.java_websocket.framing; - -import java.nio.ByteBuffer; - -import org.java_websocket.exceptions.InvalidDataException; - -public interface FrameBuilder extends Framedata { - - public abstract void setFin( boolean fin ); - - public abstract void setOptcode( Opcode optcode ); - - public abstract void setPayload( ByteBuffer payload ) throws InvalidDataException; - - public abstract void setTransferemasked( boolean transferemasked ); - -} \ No newline at end of file diff --git a/src/main/java/org/java_websocket/framing/Framedata.java b/src/main/java/org/java_websocket/framing/Framedata.java index 3dfa8c086..d3e8f3cbe 100644 --- a/src/main/java/org/java_websocket/framing/Framedata.java +++ b/src/main/java/org/java_websocket/framing/Framedata.java @@ -1,17 +1,94 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.framing; import java.nio.ByteBuffer; +import org.java_websocket.enums.Opcode; -import org.java_websocket.exceptions.InvalidFrameException; - +/** + * The interface for the frame + */ public interface Framedata { - public enum Opcode { - CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING - // more to come - } - public boolean isFin(); - public boolean getTransfereMasked(); - public Opcode getOpcode(); - public ByteBuffer getPayloadData();// TODO the separation of the application data and the extension data is yet to be done - public abstract void append( Framedata nextframe ) throws InvalidFrameException; + + /** + * Indicates that this is the final fragment in a message. The first fragment MAY also be the + * final fragment. + * + * @return true, if this frame is the final fragment + */ + boolean isFin(); + + /** + * Indicates that this frame has the rsv1 bit set. + * + * @return true, if this frame has the rsv1 bit set + */ + boolean isRSV1(); + + /** + * Indicates that this frame has the rsv2 bit set. + * + * @return true, if this frame has the rsv2 bit set + */ + boolean isRSV2(); + + /** + * Indicates that this frame has the rsv3 bit set. + * + * @return true, if this frame has the rsv3 bit set + */ + boolean isRSV3(); + + /** + * Defines whether the "Payload data" is masked. + * + * @return true, "Payload data" is masked + */ + boolean getTransfereMasked(); + + /** + * Defines the interpretation of the "Payload data". + * + * @return the interpretation as a Opcode + */ + Opcode getOpcode(); + + /** + * The "Payload data" which was sent in this frame + * + * @return the "Payload data" as ByteBuffer + */ + ByteBuffer getPayloadData();// TODO the separation of the application data and the extension data is yet to be done + + /** + * Appends an additional frame to the current frame + *

+ * This methods does not override the opcode, but does override the fin + * + * @param nextframe the additional frame + */ + void append(Framedata nextframe); } diff --git a/src/main/java/org/java_websocket/framing/FramedataImpl1.java b/src/main/java/org/java_websocket/framing/FramedataImpl1.java index 5fba075b4..fc74f7aa2 100644 --- a/src/main/java/org/java_websocket/framing/FramedataImpl1.java +++ b/src/main/java/org/java_websocket/framing/FramedataImpl1.java @@ -1,110 +1,294 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.framing; import java.nio.ByteBuffer; -import java.util.Arrays; - +import org.java_websocket.enums.Opcode; import org.java_websocket.exceptions.InvalidDataException; -import org.java_websocket.exceptions.InvalidFrameException; -import org.java_websocket.util.Charsetfunctions; - -public class FramedataImpl1 implements FrameBuilder { - protected static byte[] emptyarray = {}; - protected boolean fin; - protected Opcode optcode; - private ByteBuffer unmaskedpayload; - protected boolean transferemasked; - - public FramedataImpl1() { - } - - public FramedataImpl1( Opcode op ) { - this.optcode = op; - unmaskedpayload = ByteBuffer.wrap( emptyarray ); - } - - /** - * Helper constructor which helps to create "echo" frames. - * The new object will use the same underlying payload data. - **/ - public FramedataImpl1( Framedata f ) { - fin = f.isFin(); - optcode = f.getOpcode(); - unmaskedpayload = f.getPayloadData(); - transferemasked = f.getTransfereMasked(); - } - - @Override - public boolean isFin() { - return fin; - } - - @Override - public Opcode getOpcode() { - return optcode; - } - - @Override - public boolean getTransfereMasked() { - return transferemasked; - } - - @Override - public ByteBuffer getPayloadData() { - return unmaskedpayload; - } - - @Override - public void setFin( boolean fin ) { - this.fin = fin; - } - - @Override - public void setOptcode( Opcode optcode ) { - this.optcode = optcode; - } - - @Override - public void setPayload( ByteBuffer payload ) throws InvalidDataException { - unmaskedpayload = payload; - } - - @Override - public void setTransferemasked( boolean transferemasked ) { - this.transferemasked = transferemasked; - } - - @Override - public void append( Framedata nextframe ) throws InvalidFrameException { - ByteBuffer b = nextframe.getPayloadData(); - if( unmaskedpayload == null ) { - unmaskedpayload = ByteBuffer.allocate( b.remaining() ); - b.mark(); - unmaskedpayload.put( b ); - b.reset(); - } else { - b.mark(); - unmaskedpayload.position( unmaskedpayload.limit() ); - unmaskedpayload.limit( unmaskedpayload.capacity() ); - - if( b.remaining() > unmaskedpayload.remaining() ) { - ByteBuffer tmp = ByteBuffer.allocate( b.remaining() + unmaskedpayload.capacity() ); - unmaskedpayload.flip(); - tmp.put( unmaskedpayload ); - tmp.put( b ); - unmaskedpayload = tmp; - - } else { - unmaskedpayload.put( b ); - } - unmaskedpayload.rewind(); - b.reset(); - } - fin = nextframe.isFin(); - } - - @Override - public String toString() { - return "Framedata{ optcode:" + getOpcode() + ", fin:" + isFin() + ", payloadlength:[pos:" + unmaskedpayload.position() + ", len:" + unmaskedpayload.remaining() + "], payload:" + Arrays.toString( Charsetfunctions.utf8Bytes( new String( unmaskedpayload.array() ) ) ) + "}"; - } +import org.java_websocket.util.ByteBufferUtils; + +/** + * Abstract implementation of a frame + */ +public abstract class FramedataImpl1 implements Framedata { + + /** + * Indicates that this is the final fragment in a message. + */ + private boolean fin; + /** + * Defines the interpretation of the "Payload data". + */ + private Opcode optcode; + + /** + * The unmasked "Payload data" which was sent in this frame + */ + private ByteBuffer unmaskedpayload; + + /** + * Defines whether the "Payload data" is masked. + */ + private boolean transferemasked; + + /** + * Indicates that the rsv1 bit is set or not + */ + private boolean rsv1; + + /** + * Indicates that the rsv2 bit is set or not + */ + private boolean rsv2; + + /** + * Indicates that the rsv3 bit is set or not + */ + private boolean rsv3; + + /** + * Check if the frame is valid due to specification + * + * @throws InvalidDataException thrown if the frame is not a valid frame + */ + public abstract void isValid() throws InvalidDataException; + + /** + * Constructor for a FramedataImpl without any attributes set apart from the opcode + * + * @param op the opcode to use + */ + public FramedataImpl1(Opcode op) { + optcode = op; + unmaskedpayload = ByteBufferUtils.getEmptyByteBuffer(); + fin = true; + transferemasked = false; + rsv1 = false; + rsv2 = false; + rsv3 = false; + } + + @Override + public boolean isRSV1() { + return rsv1; + } + + @Override + public boolean isRSV2() { + return rsv2; + } + + @Override + public boolean isRSV3() { + return rsv3; + } + + @Override + public boolean isFin() { + return fin; + } + + @Override + public Opcode getOpcode() { + return optcode; + } + + @Override + public boolean getTransfereMasked() { + return transferemasked; + } + + @Override + public ByteBuffer getPayloadData() { + return unmaskedpayload; + } + + @Override + public void append(Framedata nextframe) { + ByteBuffer b = nextframe.getPayloadData(); + if (unmaskedpayload == null) { + unmaskedpayload = ByteBuffer.allocate(b.remaining()); + b.mark(); + unmaskedpayload.put(b); + b.reset(); + } else { + b.mark(); + unmaskedpayload.position(unmaskedpayload.limit()); + unmaskedpayload.limit(unmaskedpayload.capacity()); + + if (b.remaining() > unmaskedpayload.remaining()) { + ByteBuffer tmp = ByteBuffer.allocate(b.remaining() + unmaskedpayload.capacity()); + unmaskedpayload.flip(); + tmp.put(unmaskedpayload); + tmp.put(b); + unmaskedpayload = tmp; + + } else { + unmaskedpayload.put(b); + } + unmaskedpayload.rewind(); + b.reset(); + } + fin = nextframe.isFin(); + + } + + @Override + public String toString() { + return "Framedata{ opcode:" + getOpcode() + ", fin:" + isFin() + ", rsv1:" + isRSV1() + + ", rsv2:" + isRSV2() + ", rsv3:" + isRSV3() + ", payload length:[pos:" + unmaskedpayload + .position() + ", len:" + unmaskedpayload.remaining() + "], payload:" + ( + unmaskedpayload.remaining() > 1000 ? "(too big to display)" + : new String(unmaskedpayload.array())) + '}'; + } + + /** + * Set the payload of this frame to the provided payload + * + * @param payload the payload which is to set + */ + public void setPayload(ByteBuffer payload) { + this.unmaskedpayload = payload; + } + + /** + * Set the fin of this frame to the provided boolean + * + * @param fin true if fin has to be set + */ + public void setFin(boolean fin) { + this.fin = fin; + } + + /** + * Set the rsv1 of this frame to the provided boolean + * + * @param rsv1 true if rsv1 has to be set + */ + public void setRSV1(boolean rsv1) { + this.rsv1 = rsv1; + } + + /** + * Set the rsv2 of this frame to the provided boolean + * + * @param rsv2 true if rsv2 has to be set + */ + public void setRSV2(boolean rsv2) { + this.rsv2 = rsv2; + } + + /** + * Set the rsv3 of this frame to the provided boolean + * + * @param rsv3 true if rsv3 has to be set + */ + public void setRSV3(boolean rsv3) { + this.rsv3 = rsv3; + } + + /** + * Set the tranferemask of this frame to the provided boolean + * + * @param transferemasked true if transferemasked has to be set + */ + public void setTransferemasked(boolean transferemasked) { + this.transferemasked = transferemasked; + } + + /** + * Get a frame with a specific opcode + * + * @param opcode the opcode representing the frame + * @return the frame with a specific opcode + */ + public static FramedataImpl1 get(Opcode opcode) { + if (opcode == null) { + throw new IllegalArgumentException("Supplied opcode cannot be null"); + } + switch (opcode) { + case PING: + return new PingFrame(); + case PONG: + return new PongFrame(); + case TEXT: + return new TextFrame(); + case BINARY: + return new BinaryFrame(); + case CLOSING: + return new CloseFrame(); + case CONTINUOUS: + return new ContinuousFrame(); + default: + throw new IllegalArgumentException("Supplied opcode is invalid"); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + FramedataImpl1 that = (FramedataImpl1) o; + + if (fin != that.fin) { + return false; + } + if (transferemasked != that.transferemasked) { + return false; + } + if (rsv1 != that.rsv1) { + return false; + } + if (rsv2 != that.rsv2) { + return false; + } + if (rsv3 != that.rsv3) { + return false; + } + if (optcode != that.optcode) { + return false; + } + return unmaskedpayload != null ? unmaskedpayload.equals(that.unmaskedpayload) + : that.unmaskedpayload == null; + } + @Override + public int hashCode() { + int result = (fin ? 1 : 0); + result = 31 * result + optcode.hashCode(); + result = 31 * result + (unmaskedpayload != null ? unmaskedpayload.hashCode() : 0); + result = 31 * result + (transferemasked ? 1 : 0); + result = 31 * result + (rsv1 ? 1 : 0); + result = 31 * result + (rsv2 ? 1 : 0); + result = 31 * result + (rsv3 ? 1 : 0); + return result; + } } diff --git a/src/main/java/org/java_websocket/framing/PingFrame.java b/src/main/java/org/java_websocket/framing/PingFrame.java new file mode 100644 index 000000000..ae2b29119 --- /dev/null +++ b/src/main/java/org/java_websocket/framing/PingFrame.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.framing; + +import org.java_websocket.enums.Opcode; + +/** + * Class to represent a ping frame + */ +public class PingFrame extends ControlFrame { + + /** + * constructor which sets the opcode of this frame to ping + */ + public PingFrame() { + super(Opcode.PING); + } +} diff --git a/src/main/java/org/java_websocket/framing/PongFrame.java b/src/main/java/org/java_websocket/framing/PongFrame.java new file mode 100644 index 000000000..4b58139ea --- /dev/null +++ b/src/main/java/org/java_websocket/framing/PongFrame.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.framing; + +import org.java_websocket.enums.Opcode; + +/** + * Class to represent a pong frame + */ +public class PongFrame extends ControlFrame { + + /** + * constructor which sets the opcode of this frame to pong + */ + public PongFrame() { + super(Opcode.PONG); + } + + /** + * constructor which sets the opcode of this frame to ping copying over the payload of the ping + * + * @param pingFrame the PingFrame which payload is to copy + */ + public PongFrame(PingFrame pingFrame) { + super(Opcode.PONG); + setPayload(pingFrame.getPayloadData()); + } +} diff --git a/src/main/java/org/java_websocket/framing/TextFrame.java b/src/main/java/org/java_websocket/framing/TextFrame.java new file mode 100644 index 000000000..52154b47e --- /dev/null +++ b/src/main/java/org/java_websocket/framing/TextFrame.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.framing; + +import org.java_websocket.enums.Opcode; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.util.Charsetfunctions; + +/** + * Class to represent a text frames + */ +public class TextFrame extends DataFrame { + + /** + * constructor which sets the opcode of this frame to text + */ + public TextFrame() { + super(Opcode.TEXT); + } + + @Override + public void isValid() throws InvalidDataException { + super.isValid(); + if (!Charsetfunctions.isValidUTF8(getPayloadData())) { + throw new InvalidDataException(CloseFrame.NO_UTF8, "Received text is no valid utf8 string!"); + } + } +} diff --git a/src/main/java/org/java_websocket/framing/package-info.java b/src/main/java/org/java_websocket/framing/package-info.java new file mode 100644 index 000000000..12e1510ec --- /dev/null +++ b/src/main/java/org/java_websocket/framing/package-info.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +/** + * This package encapsulates all interfaces and implementations in relation with the WebSocket + * frames. + */ +package org.java_websocket.framing; \ No newline at end of file diff --git a/src/main/java/org/java_websocket/handshake/ClientHandshake.java b/src/main/java/org/java_websocket/handshake/ClientHandshake.java index 918d22184..f0cbc3ab9 100644 --- a/src/main/java/org/java_websocket/handshake/ClientHandshake.java +++ b/src/main/java/org/java_websocket/handshake/ClientHandshake.java @@ -1,6 +1,39 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.handshake; +/** + * The interface for a client handshake + */ public interface ClientHandshake extends Handshakedata { - /**returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2*/ - public String getResourceDescriptor(); + + /** + * returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2 + * + * @return the HTTP Request-URI + */ + String getResourceDescriptor(); } diff --git a/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java b/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java index 88ac4f27b..81875153d 100644 --- a/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java +++ b/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java @@ -1,5 +1,39 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.handshake; +/** + * The interface for building a handshake for the client + */ public interface ClientHandshakeBuilder extends HandshakeBuilder, ClientHandshake { - public void setResourceDescriptor( String resourceDescriptor ); + + /** + * Set a specific resource descriptor + * + * @param resourceDescriptor the resource descriptior to set + */ + void setResourceDescriptor(String resourceDescriptor); } diff --git a/src/main/java/org/java_websocket/handshake/HandshakeBuilder.java b/src/main/java/org/java_websocket/handshake/HandshakeBuilder.java index 8a6236ca4..1f4de2067 100644 --- a/src/main/java/org/java_websocket/handshake/HandshakeBuilder.java +++ b/src/main/java/org/java_websocket/handshake/HandshakeBuilder.java @@ -1,6 +1,47 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.handshake; +/** + * The interface for building a handshake + */ public interface HandshakeBuilder extends Handshakedata { - public abstract void setContent( byte[] content ); - public abstract void put( String name, String value ); + + /** + * Setter for the content of the handshake + * + * @param content the content to set + */ + void setContent(byte[] content); + + /** + * Adding a specific field with a specific value + * + * @param name the http field + * @param value the value for this field + */ + void put(String name, String value); } diff --git a/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java b/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java index 15715e37e..11ffa43dd 100644 --- a/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java +++ b/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java @@ -1,18 +1,50 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.handshake; +/** + * Implementation for a client handshake + */ public class HandshakeImpl1Client extends HandshakedataImpl1 implements ClientHandshakeBuilder { - private String resourceDescriptor = "*"; - public HandshakeImpl1Client() { - } + /** + * Attribute for the resource descriptor + */ + private String resourceDescriptor = "*"; - public void setResourceDescriptor( String resourceDescriptor ) throws IllegalArgumentException { - if(resourceDescriptor==null) - throw new IllegalArgumentException( "http resource descriptor must not be null" ); - this.resourceDescriptor = resourceDescriptor; - } + @Override + public void setResourceDescriptor(String resourceDescriptor) { + if (resourceDescriptor == null) { + throw new IllegalArgumentException("http resource descriptor must not be null"); + } + this.resourceDescriptor = resourceDescriptor; + } - public String getResourceDescriptor() { - return resourceDescriptor; - } + @Override + public String getResourceDescriptor() { + return resourceDescriptor; + } } diff --git a/src/main/java/org/java_websocket/handshake/HandshakeImpl1Server.java b/src/main/java/org/java_websocket/handshake/HandshakeImpl1Server.java index 7063b892d..87540141d 100644 --- a/src/main/java/org/java_websocket/handshake/HandshakeImpl1Server.java +++ b/src/main/java/org/java_websocket/handshake/HandshakeImpl1Server.java @@ -1,29 +1,62 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.handshake; +/** + * Implementation for a server handshake + */ public class HandshakeImpl1Server extends HandshakedataImpl1 implements ServerHandshakeBuilder { - private short httpstatus; - private String httpstatusmessage; - - public HandshakeImpl1Server() { - } - - @Override - public String getHttpStatusMessage() { - return httpstatusmessage; - } - - @Override - public short getHttpStatus() { - return httpstatus; - } - - public void setHttpStatusMessage( String message ) { - this.httpstatusmessage = message; - } - - public void setHttpStatus( short status ) { - httpstatus = status; - } - + /** + * Attribute for the http status + */ + private short httpstatus; + + /** + * Attribute for the http status message + */ + private String httpstatusmessage; + + @Override + public String getHttpStatusMessage() { + return httpstatusmessage; + } + + @Override + public short getHttpStatus() { + return httpstatus; + } + + @Override + public void setHttpStatusMessage(String message) { + this.httpstatusmessage = message; + } + + @Override + public void setHttpStatus(short status) { + httpstatus = status; + } } diff --git a/src/main/java/org/java_websocket/handshake/Handshakedata.java b/src/main/java/org/java_websocket/handshake/Handshakedata.java index 577d6ce11..fd270ecb6 100644 --- a/src/main/java/org/java_websocket/handshake/Handshakedata.java +++ b/src/main/java/org/java_websocket/handshake/Handshakedata.java @@ -1,10 +1,64 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.handshake; import java.util.Iterator; +/** + * The interface for the data of a handshake + */ public interface Handshakedata { - public Iterator iterateHttpFields(); - public String getFieldValue( String name ); - public boolean hasFieldValue( String name ); - public byte[] getContent(); + + /** + * Iterator for the http fields + * + * @return the http fields + */ + Iterator iterateHttpFields(); + + /** + * Gets the value of the field + * + * @param name The name of the field + * @return the value of the field or an empty String if not in the handshake + */ + String getFieldValue(String name); + + /** + * Checks if this handshake contains a specific field + * + * @param name The name of the field + * @return true, if it contains the field + */ + boolean hasFieldValue(String name); + + /** + * Get the content of the handshake + * + * @return the content as byte-array + */ + byte[] getContent(); } diff --git a/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java b/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java index d4d9555c6..62824cdd2 100644 --- a/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java +++ b/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java @@ -1,60 +1,87 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.handshake; import java.util.Collections; import java.util.Iterator; import java.util.TreeMap; +/** + * Implementation of a handshake builder + */ public class HandshakedataImpl1 implements HandshakeBuilder { - private byte[] content; - private TreeMap map; - - public HandshakedataImpl1() { - map = new TreeMap( String.CASE_INSENSITIVE_ORDER ); - } - - /*public HandshakedataImpl1( Handshakedata h ) { - httpstatusmessage = h.getHttpStatusMessage(); - resourcedescriptor = h.getResourceDescriptor(); - content = h.getContent(); - map = new LinkedHashMap(); - Iterator it = h.iterateHttpFields(); - while ( it.hasNext() ) { - String key = (String) it.next(); - map.put( key, h.getFieldValue( key ) ); - } - }*/ - - @Override - public Iterator iterateHttpFields() { - return Collections.unmodifiableSet( map.keySet() ).iterator();// Safety first - } - - @Override - public String getFieldValue( String name ) { - String s = map.get( name ); - if ( s == null ) { - return ""; - } - return s; - } - - @Override - public byte[] getContent() { - return content; - } - - @Override - public void setContent( byte[] content ) { - this.content = content; - } - - @Override - public void put( String name, String value ) { - map.put( name, value ); - } - - @Override - public boolean hasFieldValue( String name ) { - return map.containsKey( name ); - } + + /** + * Attribute for the content of the handshake + */ + private byte[] content; + + /** + * Attribute for the http fields and values + */ + private TreeMap map; + + /** + * Constructor for handshake implementation + */ + public HandshakedataImpl1() { + map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + } + + @Override + public Iterator iterateHttpFields() { + return Collections.unmodifiableSet(map.keySet()).iterator();// Safety first + } + + @Override + public String getFieldValue(String name) { + String s = map.get(name); + if (s == null) { + return ""; + } + return s; + } + + @Override + public byte[] getContent() { + return content; + } + + @Override + public void setContent(byte[] content) { + this.content = content; + } + + @Override + public void put(String name, String value) { + map.put(name, value); + } + + @Override + public boolean hasFieldValue(String name) { + return map.containsKey(name); + } } diff --git a/src/main/java/org/java_websocket/handshake/ServerHandshake.java b/src/main/java/org/java_websocket/handshake/ServerHandshake.java index 880e9b2d5..1b5a5a9b8 100644 --- a/src/main/java/org/java_websocket/handshake/ServerHandshake.java +++ b/src/main/java/org/java_websocket/handshake/ServerHandshake.java @@ -1,6 +1,46 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.handshake; +/** + * Interface for the server handshake + */ public interface ServerHandshake extends Handshakedata { - public short getHttpStatus(); - public String getHttpStatusMessage(); + + /** + * Get the http status code + * + * @return the http status code + */ + short getHttpStatus(); + + /** + * Get the http status message + * + * @return the http status message + */ + String getHttpStatusMessage(); } diff --git a/src/main/java/org/java_websocket/handshake/ServerHandshakeBuilder.java b/src/main/java/org/java_websocket/handshake/ServerHandshakeBuilder.java index d518dfb12..51212f048 100644 --- a/src/main/java/org/java_websocket/handshake/ServerHandshakeBuilder.java +++ b/src/main/java/org/java_websocket/handshake/ServerHandshakeBuilder.java @@ -1,6 +1,46 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.handshake; +/** + * The interface for building a handshake for the server + */ public interface ServerHandshakeBuilder extends HandshakeBuilder, ServerHandshake { - public void setHttpStatus( short status ); - public void setHttpStatusMessage( String message ); + + /** + * Setter for the http status code + * + * @param status the http status code + */ + void setHttpStatus(short status); + + /** + * Setter for the http status message + * + * @param message the http status message + */ + void setHttpStatusMessage(String message); } diff --git a/src/main/java/org/java_websocket/handshake/package-info.java b/src/main/java/org/java_websocket/handshake/package-info.java new file mode 100644 index 000000000..7af26d497 --- /dev/null +++ b/src/main/java/org/java_websocket/handshake/package-info.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +/** + * This package encapsulates all interfaces and implementations in relation with the WebSocket + * handshake. + */ +package org.java_websocket.handshake; + diff --git a/src/main/java/org/java_websocket/interfaces/ISSLChannel.java b/src/main/java/org/java_websocket/interfaces/ISSLChannel.java new file mode 100644 index 000000000..c783c5844 --- /dev/null +++ b/src/main/java/org/java_websocket/interfaces/ISSLChannel.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + * + */ + +package org.java_websocket.interfaces; + +import javax.net.ssl.SSLEngine; + +/** + * Interface which specifies all required methods a SSLSocketChannel has to make public. + * + * @since 1.4.1 + */ +public interface ISSLChannel { + + /** + * Get the ssl engine used for the de- and encryption of the communication. + * + * @return the ssl engine of this channel + */ + SSLEngine getSSLEngine(); +} diff --git a/src/main/java/org/java_websocket/interfaces/package-info.java b/src/main/java/org/java_websocket/interfaces/package-info.java new file mode 100644 index 000000000..b70dbc174 --- /dev/null +++ b/src/main/java/org/java_websocket/interfaces/package-info.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + * + */ + +/** + * This package encapsulates all new interfaces. + */ +package org.java_websocket.interfaces; \ No newline at end of file diff --git a/src/main/java/org/java_websocket/protocols/IProtocol.java b/src/main/java/org/java_websocket/protocols/IProtocol.java new file mode 100644 index 000000000..5300aed2a --- /dev/null +++ b/src/main/java/org/java_websocket/protocols/IProtocol.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.protocols; + +/** + * Interface which specifies all required methods for a Sec-WebSocket-Protocol + * + * @since 1.3.7 + */ +public interface IProtocol { + + /** + * Check if the received Sec-WebSocket-Protocol header field contains a offer for the specific + * protocol + * + * @param inputProtocolHeader the received Sec-WebSocket-Protocol header field offered by the + * other endpoint + * @return true, if the offer does fit to this specific protocol + * @since 1.3.7 + */ + boolean acceptProvidedProtocol(String inputProtocolHeader); + + /** + * Return the specific Sec-WebSocket-protocol header offer for this protocol if the endpoint. If + * the extension returns an empty string (""), the offer will not be included in the handshake. + * + * @return the specific Sec-WebSocket-Protocol header for this protocol + * @since 1.3.7 + */ + String getProvidedProtocol(); + + /** + * To prevent protocols to be used more than once the Websocket implementation should call this + * method in order to create a new usable version of a given protocol instance. + * + * @return a copy of the protocol + * @since 1.3.7 + */ + IProtocol copyInstance(); + + /** + * Return a string which should contain the protocol name as well as additional information about + * the current configurations for this protocol (DEBUG purposes) + * + * @return a string containing the protocol name as well as additional information + * @since 1.3.7 + */ + String toString(); +} diff --git a/src/main/java/org/java_websocket/protocols/Protocol.java b/src/main/java/org/java_websocket/protocols/Protocol.java new file mode 100644 index 000000000..f7fbfb58b --- /dev/null +++ b/src/main/java/org/java_websocket/protocols/Protocol.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.protocols; + +import java.util.regex.Pattern; + +/** + * Class which represents the protocol used as Sec-WebSocket-Protocol + * + * @since 1.3.7 + */ +public class Protocol implements IProtocol { + + private static final Pattern patternSpace = Pattern.compile(" "); + private static final Pattern patternComma = Pattern.compile(","); + + /** + * Attribute for the provided protocol + */ + private final String providedProtocol; + + /** + * Constructor for a Sec-Websocket-Protocol + * + * @param providedProtocol the protocol string + */ + public Protocol(String providedProtocol) { + if (providedProtocol == null) { + throw new IllegalArgumentException(); + } + this.providedProtocol = providedProtocol; + } + + @Override + public boolean acceptProvidedProtocol(String inputProtocolHeader) { + if ("".equals(providedProtocol)) { + return true; + } + String protocolHeader = patternSpace.matcher(inputProtocolHeader).replaceAll(""); + String[] headers = patternComma.split(protocolHeader); + for (String header : headers) { + if (providedProtocol.equals(header)) { + return true; + } + } + return false; + } + + @Override + public String getProvidedProtocol() { + return this.providedProtocol; + } + + @Override + public IProtocol copyInstance() { + return new Protocol(getProvidedProtocol()); + } + + @Override + public String toString() { + return getProvidedProtocol(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Protocol protocol = (Protocol) o; + + return providedProtocol.equals(protocol.providedProtocol); + } + + @Override + public int hashCode() { + return providedProtocol.hashCode(); + } +} diff --git a/src/main/java/org/java_websocket/protocols/package-info.java b/src/main/java/org/java_websocket/protocols/package-info.java new file mode 100644 index 000000000..6f8132b55 --- /dev/null +++ b/src/main/java/org/java_websocket/protocols/package-info.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +/** + * This package encapsulates all interfaces and implementations in relation with the WebSocket + * Sec-WebSocket-Protocol. + */ +package org.java_websocket.protocols; \ No newline at end of file diff --git a/src/main/java/org/java_websocket/server/CustomSSLWebSocketServerFactory.java b/src/main/java/org/java_websocket/server/CustomSSLWebSocketServerFactory.java new file mode 100644 index 000000000..1246b22d9 --- /dev/null +++ b/src/main/java/org/java_websocket/server/CustomSSLWebSocketServerFactory.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.server; + +import java.io.IOException; +import java.nio.channels.ByteChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import org.java_websocket.SSLSocketChannel2; + +/** + * WebSocketFactory that can be configured to only support specific protocols and cipher suites. + */ +public class CustomSSLWebSocketServerFactory extends DefaultSSLWebSocketServerFactory { + + /** + * The enabled protocols saved as a String array + */ + private final String[] enabledProtocols; + + /** + * The enabled ciphersuites saved as a String array + */ + private final String[] enabledCiphersuites; + + /** + * New CustomSSLWebSocketServerFactory configured to only support given protocols and given cipher + * suites. + * + * @param sslContext - can not be null + * @param enabledProtocols - only these protocols are enabled, when null default + * settings will be used. + * @param enabledCiphersuites - only these cipher suites are enabled, when null + * default settings will be used. + */ + public CustomSSLWebSocketServerFactory(SSLContext sslContext, String[] enabledProtocols, + String[] enabledCiphersuites) { + this(sslContext, Executors.newSingleThreadScheduledExecutor(), enabledProtocols, + enabledCiphersuites); + } + + /** + * New CustomSSLWebSocketServerFactory configured to only support given protocols and given cipher + * suites. + * + * @param sslContext - can not be null + * @param executerService - can not be null + * @param enabledProtocols - only these protocols are enabled, when null default + * settings will be used. + * @param enabledCiphersuites - only these cipher suites are enabled, when null + * default settings will be used. + */ + public CustomSSLWebSocketServerFactory(SSLContext sslContext, ExecutorService executerService, + String[] enabledProtocols, String[] enabledCiphersuites) { + super(sslContext, executerService); + this.enabledProtocols = enabledProtocols; + this.enabledCiphersuites = enabledCiphersuites; + } + + @Override + public ByteChannel wrapChannel(SocketChannel channel, SelectionKey key) throws IOException { + SSLEngine e = sslcontext.createSSLEngine(); + if (enabledProtocols != null) { + e.setEnabledProtocols(enabledProtocols); + } + if (enabledCiphersuites != null) { + e.setEnabledCipherSuites(enabledCiphersuites); + } + e.setUseClientMode(false); + return new SSLSocketChannel2(channel, e, exec, key); + } + +} \ No newline at end of file diff --git a/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java b/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java index b871260f8..f2732030c 100644 --- a/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java +++ b/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java @@ -1,51 +1,92 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.server; + import java.io.IOException; -import java.net.Socket; import java.nio.channels.ByteChannel; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; - import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; - import org.java_websocket.SSLSocketChannel2; import org.java_websocket.WebSocketAdapter; import org.java_websocket.WebSocketImpl; +import org.java_websocket.WebSocketServerFactory; import org.java_websocket.drafts.Draft; +public class DefaultSSLWebSocketServerFactory implements WebSocketServerFactory { + + protected SSLContext sslcontext; + protected ExecutorService exec; + + public DefaultSSLWebSocketServerFactory(SSLContext sslContext) { + this(sslContext, Executors.newSingleThreadScheduledExecutor()); + } + + public DefaultSSLWebSocketServerFactory(SSLContext sslContext, ExecutorService exec) { + if (sslContext == null || exec == null) { + throw new IllegalArgumentException(); + } + this.sslcontext = sslContext; + this.exec = exec; + } + + @Override + public ByteChannel wrapChannel(SocketChannel channel, SelectionKey key) throws IOException { + SSLEngine e = sslcontext.createSSLEngine(); + /* + * See https://github.com/TooTallNate/Java-WebSocket/issues/466 + * + * We remove TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 from the enabled ciphers since it is just available when you patch your java installation directly. + * E.g. firefox requests this cipher and this causes some dcs/instable connections + */ + List ciphers = new ArrayList<>(Arrays.asList(e.getEnabledCipherSuites())); + ciphers.remove("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); + e.setEnabledCipherSuites(ciphers.toArray(new String[ciphers.size()])); + e.setUseClientMode(false); + return new SSLSocketChannel2(channel, e, exec, key); + } + + @Override + public WebSocketImpl createWebSocket(WebSocketAdapter a, Draft d) { + return new WebSocketImpl(a, d); + } + + @Override + public WebSocketImpl createWebSocket(WebSocketAdapter a, List d) { + return new WebSocketImpl(a, d); + } -public class DefaultSSLWebSocketServerFactory implements WebSocketServer.WebSocketServerFactory { - protected SSLContext sslcontext; - protected ExecutorService exec; - - public DefaultSSLWebSocketServerFactory( SSLContext sslContext ) { - this( sslContext, Executors.newSingleThreadScheduledExecutor() ); - } - - public DefaultSSLWebSocketServerFactory( SSLContext sslContext , ExecutorService exec ) { - if( sslContext == null || exec == null ) - throw new IllegalArgumentException(); - this.sslcontext = sslContext; - this.exec = exec; - } - - @Override - public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key ) throws IOException { - SSLEngine e = sslcontext.createSSLEngine(); - e.setUseClientMode( false ); - return new SSLSocketChannel2( channel, e, exec, key ); - } - - @Override - public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket c ) { - return new WebSocketImpl( a, d ); - } - - @Override - public WebSocketImpl createWebSocket( WebSocketAdapter a, List d, Socket s ) { - return new WebSocketImpl( a, d ); - } + @Override + public void close() { + exec.shutdown(); + } } \ No newline at end of file diff --git a/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java b/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java index 3b89cdc2f..80527e8e7 100644 --- a/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java +++ b/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java @@ -1,26 +1,57 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.server; -import java.net.Socket; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.util.List; - import org.java_websocket.WebSocketAdapter; import org.java_websocket.WebSocketImpl; +import org.java_websocket.WebSocketServerFactory; import org.java_websocket.drafts.Draft; -import org.java_websocket.server.WebSocketServer.WebSocketServerFactory; public class DefaultWebSocketServerFactory implements WebSocketServerFactory { - @Override - public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket s ) { - return new WebSocketImpl( a, d ); - } - @Override - public WebSocketImpl createWebSocket( WebSocketAdapter a, List d, Socket s ) { - return new WebSocketImpl( a, d ); - } - @Override - public SocketChannel wrapChannel( SocketChannel channel, SelectionKey key ) { - return (SocketChannel) channel; - } + + @Override + public WebSocketImpl createWebSocket(WebSocketAdapter a, Draft d) { + return new WebSocketImpl(a, d); + } + + @Override + public WebSocketImpl createWebSocket(WebSocketAdapter a, List d) { + return new WebSocketImpl(a, d); + } + + @Override + public SocketChannel wrapChannel(SocketChannel channel, SelectionKey key) { + return channel; + } + + @Override + public void close() { + //Nothing to do for a normal ws factory + } } \ No newline at end of file diff --git a/src/main/java/org/java_websocket/server/SSLParametersWebSocketServerFactory.java b/src/main/java/org/java_websocket/server/SSLParametersWebSocketServerFactory.java new file mode 100644 index 000000000..d7685bfd8 --- /dev/null +++ b/src/main/java/org/java_websocket/server/SSLParametersWebSocketServerFactory.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.server; + +import java.io.IOException; +import java.nio.channels.ByteChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; +import org.java_websocket.SSLSocketChannel2; + +/** + * WebSocketFactory that can be configured to only support specific protocols and cipher suites. + */ +public class SSLParametersWebSocketServerFactory extends DefaultSSLWebSocketServerFactory { + + private final SSLParameters sslParameters; + + /** + * New CustomSSLWebSocketServerFactory configured to only support given protocols and given cipher + * suites. + * + * @param sslContext - can not be null + * @param sslParameters - can not be null + */ + public SSLParametersWebSocketServerFactory(SSLContext sslContext, SSLParameters sslParameters) { + this(sslContext, Executors.newSingleThreadScheduledExecutor(), sslParameters); + } + + /** + * New CustomSSLWebSocketServerFactory configured to only support given protocols and given cipher + * suites. + * + * @param sslContext - can not be null + * @param executerService - can not be null + * @param sslParameters - can not be null + */ + public SSLParametersWebSocketServerFactory(SSLContext sslContext, ExecutorService executerService, + SSLParameters sslParameters) { + super(sslContext, executerService); + if (sslParameters == null) { + throw new IllegalArgumentException(); + } + this.sslParameters = sslParameters; + } + + @Override + public ByteChannel wrapChannel(SocketChannel channel, SelectionKey key) throws IOException { + SSLEngine e = sslcontext.createSSLEngine(); + e.setUseClientMode(false); + e.setSSLParameters(sslParameters); + return new SSLSocketChannel2(channel, e, exec, key); + } +} \ No newline at end of file diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index 12cea2f1d..8d11bcf48 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -1,12 +1,36 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.server; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; -import java.net.UnknownHostException; +import java.net.SocketAddress; import java.nio.ByteBuffer; -import java.nio.channels.ByteChannel; import java.nio.channels.CancelledKeyException; import java.nio.channels.ClosedByInterruptException; import java.nio.channels.SelectableChannel; @@ -17,717 +41,1133 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; - +import org.java_websocket.AbstractWebSocket; import org.java_websocket.SocketChannelIOHelper; import org.java_websocket.WebSocket; -import org.java_websocket.WebSocketAdapter; import org.java_websocket.WebSocketFactory; import org.java_websocket.WebSocketImpl; +import org.java_websocket.WebSocketServerFactory; import org.java_websocket.WrappedByteChannel; import org.java_websocket.drafts.Draft; -import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.WebsocketNotConnectedException; +import org.java_websocket.exceptions.WrappedIOException; import org.java_websocket.framing.CloseFrame; import org.java_websocket.framing.Framedata; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.handshake.Handshakedata; -import org.java_websocket.handshake.ServerHandshakeBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * WebSocketServer is an abstract class that only takes care of the - * HTTP handshake portion of WebSockets. It's up to a subclass to add - * functionality/purpose to the server. - * + * WebSocketServer is an abstract class that only takes care of the + * HTTP handshake portion of WebSockets. It's up to a subclass to add functionality/purpose to the + * server. */ -public abstract class WebSocketServer extends WebSocketAdapter implements Runnable { - - public static int DECODERS = Runtime.getRuntime().availableProcessors(); - - /** - * Holds the list of active WebSocket connections. "Active" means WebSocket - * handshake is complete and socket can be written to, or read from. - */ - private final Collection connections; - /** - * The port number that this WebSocket server should listen on. Default is - * WebSocket.DEFAULT_PORT. - */ - private final InetSocketAddress address; - /** - * The socket channel for this WebSocket server. - */ - private ServerSocketChannel server; - /** - * The 'Selector' used to get event keys from the underlying socket. - */ - private Selector selector; - /** - * The Draft of the WebSocket protocol the Server is adhering to. - */ - private List drafts; - - private Thread selectorthread; - - private final AtomicBoolean isclosed = new AtomicBoolean( false ); - - private List decoders; - - private List iqueue; - private BlockingQueue buffers; - private int queueinvokes = 0; - private final AtomicInteger queuesize = new AtomicInteger( 0 ); - - private WebSocketServerFactory wsf = new DefaultWebSocketServerFactory(); - - /** - * Creates a WebSocketServer that will attempt to - * listen on port WebSocket.DEFAULT_PORT. - * - * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here - */ - public WebSocketServer() throws UnknownHostException { - this( new InetSocketAddress( WebSocket.DEFAULT_PORT ), DECODERS, null ); - } - - /** - * Creates a WebSocketServer that will attempt to bind/listen on the given address. - * - * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here - */ - public WebSocketServer( InetSocketAddress address ) { - this( address, DECODERS, null ); - } - - /** - * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here - */ - public WebSocketServer( InetSocketAddress address , int decoders ) { - this( address, decoders, null ); - } - - /** - * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here - */ - public WebSocketServer( InetSocketAddress address , List drafts ) { - this( address, DECODERS, drafts ); - } - - /** - * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here - */ - public WebSocketServer( InetSocketAddress address , int decodercount , List drafts ) { - this( address, decodercount, drafts, new HashSet() ); - } - - /** - * Creates a WebSocketServer that will attempt to bind/listen on the given address, - * and comply with Draft version draft. - * - * @param address - * The address (host:port) this server should listen on. - * @param decodercount - * The number of {@link WebSocketWorker}s that will be used to process the incoming network data. By default this will be Runtime.getRuntime().availableProcessors() - * @param drafts - * The versions of the WebSocket protocol that this server - * instance should comply to. Clients that use an other protocol version will be rejected. - * - * @param connectionscontainer - * Allows to specify a collection that will be used to store the websockets in.
- * If you plan to often iterate through the currently connected websockets you may want to use a collection that does not require synchronization like a {@link CopyOnWriteArraySet}. In that case make sure that you overload {@link #removeConnection(WebSocket)} and {@link #addConnection(WebSocket)}.
- * By default a {@link HashSet} will be used. - * - * @see #removeConnection(WebSocket) for more control over syncronized operation - * @see more about drafts - */ - public WebSocketServer( InetSocketAddress address , int decodercount , List drafts , Collection connectionscontainer ) { - if( address == null || decodercount < 1 || connectionscontainer == null ) { - throw new IllegalArgumentException( "address and connectionscontainer must not be null and you need at least 1 decoder" ); - } - - if( drafts == null ) - this.drafts = Collections.emptyList(); - else - this.drafts = drafts; - - this.address = address; - this.connections = connectionscontainer; - - iqueue = new LinkedList(); - - decoders = new ArrayList( decodercount ); - buffers = new LinkedBlockingQueue(); - for( int i = 0 ; i < decodercount ; i++ ) { - WebSocketWorker ex = new WebSocketWorker(); - decoders.add( ex ); - ex.start(); - } - } - - /** - * Starts the server selectorthread that binds to the currently set port number and - * listeners for WebSocket connection requests. Creates a fixed thread pool with the size {@link WebSocketServer#DECODERS}
- * May only be called once. - * - * Alternatively you can call {@link WebSocketServer#run()} directly. - * - * @throws IllegalStateException - */ - public void start() { - if( selectorthread != null ) - throw new IllegalStateException( getClass().getName() + " can only be started once." ); - new Thread( this ).start(); - } - - /** - * Closes all connected clients sockets, then closes the underlying - * ServerSocketChannel, effectively killing the server socket selectorthread, - * freeing the port the server was bound to and stops all internal workerthreads. - * - * If this method is called before the server is started it will never start. - * - * @param timeout - * Specifies how many milliseconds the overall close handshaking may take altogether before the connections are closed without proper close handshaking.
- * - * @throws InterruptedException - */ - public void stop( int timeout ) throws InterruptedException { - if( !isclosed.compareAndSet( false, true ) ) { // this also makes sure that no further connections will be added to this.connections - return; - } - - List socketsToClose = null; - - // copy the connections in a list (prevent callback deadlocks) - synchronized ( connections ) { - socketsToClose = new ArrayList( connections ); - } - - for( WebSocket ws : socketsToClose ) { - ws.close( CloseFrame.GOING_AWAY ); - } - - synchronized ( this ) { - if( selectorthread != null && selectorthread != Thread.currentThread() ) { - selector.wakeup(); - selectorthread.interrupt(); - selectorthread.join( timeout ); - } - } - } - public void stop() throws IOException , InterruptedException { - stop( 0 ); - } - - /** - * Returns a WebSocket[] of currently connected clients. - * Its iterators will be failfast and its not judicious - * to modify it. - * - * @return The currently connected clients. - */ - public Collection connections() { - return this.connections; - } - - public InetSocketAddress getAddress() { - return this.address; - } - - /** - * Gets the port number that this server listens on. - * - * @return The port number. - */ - public int getPort() { - int port = getAddress().getPort(); - if( port == 0 && server != null ) { - port = server.socket().getLocalPort(); - } - return port; - } - - public List getDraft() { - return Collections.unmodifiableList( drafts ); - } - - // Runnable IMPLEMENTATION ///////////////////////////////////////////////// - public void run() { - synchronized ( this ) { - if( selectorthread != null ) - throw new IllegalStateException( getClass().getName() + " can only be started once." ); - selectorthread = Thread.currentThread(); - if( isclosed.get() ) { - return; - } - } - selectorthread.setName( "WebsocketSelector" + selectorthread.getId() ); - try { - server = ServerSocketChannel.open(); - server.configureBlocking( false ); - ServerSocket socket = server.socket(); - socket.setReceiveBufferSize( WebSocketImpl.RCVBUF ); - socket.bind( address ); - selector = Selector.open(); - server.register( selector, server.validOps() ); - } catch ( IOException ex ) { - handleFatal( null, ex ); - return; - } - try { - while ( !selectorthread.isInterrupted() ) { - SelectionKey key = null; - WebSocketImpl conn = null; - try { - selector.select(); - Set keys = selector.selectedKeys(); - Iterator i = keys.iterator(); - - while ( i.hasNext() ) { - key = i.next(); - - if( !key.isValid() ) { - // Object o = key.attachment(); - continue; - } - - if( key.isAcceptable() ) { - if( !onConnect( key ) ) { - key.cancel(); - continue; - } - - SocketChannel channel = server.accept(); - channel.configureBlocking( false ); - WebSocketImpl w = wsf.createWebSocket( this, drafts, channel.socket() ); - w.key = channel.register( selector, SelectionKey.OP_READ, w ); - w.channel = wsf.wrapChannel( channel, w.key ); - i.remove(); - allocateBuffers( w ); - continue; - } - - if( key.isReadable() ) { - conn = (WebSocketImpl) key.attachment(); - ByteBuffer buf = takeBuffer(); - try { - if( SocketChannelIOHelper.read( buf, conn, conn.channel ) ) { - if( buf.hasRemaining() ) { - conn.inQueue.put( buf ); - queue( conn ); - i.remove(); - if( conn.channel instanceof WrappedByteChannel ) { - if( ( (WrappedByteChannel) conn.channel ).isNeedRead() ) { - iqueue.add( conn ); - } - } - } else - pushBuffer( buf ); - } else { - pushBuffer( buf ); - } - } catch ( IOException e ) { - pushBuffer( buf ); - throw e; - } - } - if( key.isWritable() ) { - conn = (WebSocketImpl) key.attachment(); - if( SocketChannelIOHelper.batch( conn, conn.channel ) ) { - if( key.isValid() ) - key.interestOps( SelectionKey.OP_READ ); - } - } - } - while ( !iqueue.isEmpty() ) { - conn = iqueue.remove( 0 ); - WrappedByteChannel c = ( (WrappedByteChannel) conn.channel ); - ByteBuffer buf = takeBuffer(); - try { - if( SocketChannelIOHelper.readMore( buf, conn, c ) ) - iqueue.add( conn ); - if( buf.hasRemaining() ) { - conn.inQueue.put( buf ); - queue( conn ); - } else { - pushBuffer( buf ); - } - } catch ( IOException e ) { - pushBuffer( buf ); - throw e; - } - - } - } catch ( CancelledKeyException e ) { - // an other thread may cancel the key - } catch ( ClosedByInterruptException e ) { - return; // do the same stuff as when InterruptedException is thrown - } catch ( IOException ex ) { - if( key != null ) - key.cancel(); - handleIOException( key, conn, ex ); - } catch ( InterruptedException e ) { - return;// FIXME controlled shutdown (e.g. take care of buffermanagement) - } - } - - } catch ( RuntimeException e ) { - // should hopefully never occur - handleFatal( null, e ); - } finally { - if( decoders != null ) { - for( WebSocketWorker w : decoders ) { - w.interrupt(); - } - } - if( server != null ) { - try { - server.close(); - } catch ( IOException e ) { - onError( null, e ); - } - } - } - } - protected void allocateBuffers( WebSocket c ) throws InterruptedException { - if( queuesize.get() >= 2 * decoders.size() + 1 ) { - return; - } - queuesize.incrementAndGet(); - buffers.put( createBuffer() ); - } - - protected void releaseBuffers( WebSocket c ) throws InterruptedException { - // queuesize.decrementAndGet(); - // takeBuffer(); - } - - public ByteBuffer createBuffer() { - return ByteBuffer.allocate( WebSocketImpl.RCVBUF ); - } - - private void queue( WebSocketImpl ws ) throws InterruptedException { - if( ws.workerThread == null ) { - ws.workerThread = decoders.get( queueinvokes % decoders.size() ); - queueinvokes++; - } - ws.workerThread.put( ws ); - } - - private ByteBuffer takeBuffer() throws InterruptedException { - return buffers.take(); - } - - private void pushBuffer( ByteBuffer buf ) throws InterruptedException { - if( buffers.size() > queuesize.intValue() ) - return; - buffers.put( buf ); - } - - private void handleIOException( SelectionKey key, WebSocket conn, IOException ex ) { - // onWebsocketError( conn, ex );// conn may be null here - if( conn != null ) { - conn.closeConnection( CloseFrame.ABNORMAL_CLOSE, ex.getMessage() ); - } else if( key != null ) { - SelectableChannel channel = key.channel(); - if( channel != null && channel.isOpen() ) { // this could be the case if the IOException ex is a SSLException - try { - channel.close(); - } catch ( IOException e ) { - // there is nothing that must be done here - } - if( WebSocketImpl.DEBUG ) - System.out.println( "Connection closed because of" + ex ); - } - } - } - - private void handleFatal( WebSocket conn, Exception e ) { - onError( conn, e ); - try { - stop(); - } catch ( IOException e1 ) { - onError( null, e1 ); - } catch ( InterruptedException e1 ) { - Thread.currentThread().interrupt(); - onError( null, e1 ); - } - } - - /** - * Gets the XML string that should be returned if a client requests a Flash - * security policy. - * - * The default implementation allows access from all remote domains, but - * only on the port that this WebSocketServer is listening on. - * - * This is specifically implemented for gitime's WebSocket client for Flash: - * http://github.com/gimite/web-socket-js - * - * @return An XML String that comforms to Flash's security policy. You MUST - * not include the null char at the end, it is appended automatically. - */ - protected String getFlashSecurityPolicy() { - return ""; - } - - @Override - public final void onWebsocketMessage( WebSocket conn, String message ) { - onMessage( conn, message ); - } - - @Override - @Deprecated - public/*final*/void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) {// onFragment should be overloaded instead - onFragment( conn, frame ); - } - - @Override - public final void onWebsocketMessage( WebSocket conn, ByteBuffer blob ) { - onMessage( conn, blob ); - } - - @Override - public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) { - if( addConnection( conn ) ) { - onOpen( conn, (ClientHandshake) handshake ); - } - } - - @Override - public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) { - selector.wakeup(); - try { - if( removeConnection( conn ) ) { - onClose( conn, code, reason, remote ); - } - } finally { - try { - releaseBuffers( conn ); - } catch ( InterruptedException e ) { - Thread.currentThread().interrupt(); - } - } - - } - - /** - * This method performs remove operations on the connection and therefore also gives control over whether the operation shall be synchronized - *

- * {@link #WebSocketServer(InetSocketAddress, int, List, Collection)} allows to specify a collection which will be used to store current connections in.
- * Depending on the type on the connection, modifications of that collection may have to be synchronized. - **/ - protected boolean removeConnection( WebSocket ws ) { - boolean removed; - synchronized ( connections ) { - removed = this.connections.remove( ws ); - assert ( removed ); - } - if( isclosed.get() && connections.size() == 0 ) { - selectorthread.interrupt(); - } - return removed; - } - @Override - public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException { - return super.onWebsocketHandshakeReceivedAsServer( conn, draft, request ); - } - - /** @see #removeConnection(WebSocket) */ - protected boolean addConnection( WebSocket ws ) { - if( !isclosed.get() ) { - synchronized ( connections ) { - boolean succ = this.connections.add( ws ); - assert ( succ ); - return succ; - } - } else { - // This case will happen when a new connection gets ready while the server is already stopping. - ws.close( CloseFrame.GOING_AWAY ); - return true;// for consistency sake we will make sure that both onOpen will be called - } - } - /** - * @param conn - * may be null if the error does not belong to a single connection - */ - @Override - public final void onWebsocketError( WebSocket conn, Exception ex ) { - onError( conn, ex ); - } - - @Override - public final void onWriteDemand( WebSocket w ) { - WebSocketImpl conn = (WebSocketImpl) w; - try { - conn.key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE ); - } catch ( CancelledKeyException e ) { - // the thread which cancels key is responsible for possible cleanup - conn.outQueue.clear(); - } - selector.wakeup(); - } - - @Override - public void onWebsocketCloseInitiated( WebSocket conn, int code, String reason ) { - onCloseInitiated( conn, code, reason ); - } - - @Override - public void onWebsocketClosing( WebSocket conn, int code, String reason, boolean remote ) { - onClosing( conn, code, reason, remote ); - - } - - public void onCloseInitiated( WebSocket conn, int code, String reason ) { - } - - public void onClosing( WebSocket conn, int code, String reason, boolean remote ) { - - } - - public final void setWebSocketFactory( WebSocketServerFactory wsf ) { - this.wsf = wsf; - } - - public final WebSocketFactory getWebSocketFactory() { - return wsf; - } - - /** - * Returns whether a new connection shall be accepted or not.
- * Therefore method is well suited to implement some kind of connection limitation.
- * - * @see #onOpen(WebSocket, ClientHandshake) - * @see #onWebsocketHandshakeReceivedAsServer(WebSocket, Draft, ClientHandshake) - **/ - protected boolean onConnect( SelectionKey key ) { - return true; - } - - private Socket getSocket( WebSocket conn ) { - WebSocketImpl impl = (WebSocketImpl) conn; - return ( (SocketChannel) impl.key.channel() ).socket(); - } - - @Override - public InetSocketAddress getLocalSocketAddress( WebSocket conn ) { - return (InetSocketAddress) getSocket( conn ).getLocalSocketAddress(); - } - - @Override - public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) { - return (InetSocketAddress) getSocket( conn ).getRemoteSocketAddress(); - } - - /** Called after an opening handshake has been performed and the given websocket is ready to be written on. */ - public abstract void onOpen( WebSocket conn, ClientHandshake handshake ); - /** - * Called after the websocket connection has been closed. - * - * @param code - * The codes can be looked up here: {@link CloseFrame} - * @param reason - * Additional information string - * @param remote - * Returns whether or not the closing of the connection was initiated by the remote host. - **/ - public abstract void onClose( WebSocket conn, int code, String reason, boolean remote ); - /** - * Callback for string messages received from the remote host - * - * @see #onMessage(WebSocket, ByteBuffer) - **/ - public abstract void onMessage( WebSocket conn, String message ); - /** - * Called when errors occurs. If an error causes the websocket connection to fail {@link #onClose(WebSocket, int, String, boolean)} will be called additionally.
- * This method will be called primarily because of IO or protocol errors.
- * If the given exception is an RuntimeException that probably means that you encountered a bug.
- * - * @param conn - * Can be null if there error does not belong to one specific websocket. For example if the servers port could not be bound. - **/ - public abstract void onError( WebSocket conn, Exception ex ); - /** - * Callback for binary messages received from the remote host - * - * @see #onMessage(WebSocket, String) - **/ - public void onMessage( WebSocket conn, ByteBuffer message ) { - } - - /** - * @see WebSocket#sendFragmentedFrame(org.java_websocket.framing.Framedata.Opcode, ByteBuffer, boolean) - */ - public void onFragment( WebSocket conn, Framedata fragment ) { - } - - public class WebSocketWorker extends Thread { - - private BlockingQueue iqueue; - - public WebSocketWorker() { - iqueue = new LinkedBlockingQueue(); - setName( "WebSocketWorker-" + getId() ); - setUncaughtExceptionHandler( new UncaughtExceptionHandler() { - @Override - public void uncaughtException( Thread t, Throwable e ) { - getDefaultUncaughtExceptionHandler().uncaughtException( t, e ); - } - } ); - } - - public void put( WebSocketImpl ws ) throws InterruptedException { - iqueue.put( ws ); - } - - @Override - public void run() { - WebSocketImpl ws = null; - try { - while ( true ) { - ByteBuffer buf = null; - ws = iqueue.take(); - buf = ws.inQueue.poll(); - assert ( buf != null ); - try { - ws.decode( buf ); - } catch(Exception e){ - System.err.println("Error while reading from remote connection: " + e); - } - - finally { - pushBuffer( buf ); - } - } - } catch ( InterruptedException e ) { - } catch ( RuntimeException e ) { - handleFatal( ws, e ); - } - } - } - - public interface WebSocketServerFactory extends WebSocketFactory { - @Override - public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket s ); - - public WebSocketImpl createWebSocket( WebSocketAdapter a, List drafts, Socket s ); - - /** - * Allows to wrap the Socketchannel( key.channel() ) to insert a protocol layer( like ssl or proxy authentication) beyond the ws layer. - * - * @param key - * a SelectionKey of an open SocketChannel. - * @return The channel on which the read and write operations will be performed.
- */ - public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key ) throws IOException; - } +public abstract class WebSocketServer extends AbstractWebSocket implements Runnable { + + private static final int AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors(); + + /** + * Logger instance + * + * @since 1.4.0 + */ + private final Logger log = LoggerFactory.getLogger(WebSocketServer.class); + + /** + * Holds the list of active WebSocket connections. "Active" means WebSocket handshake is complete + * and socket can be written to, or read from. + */ + private final Collection connections; + /** + * The port number that this WebSocket server should listen on. Default is + * WebSocketImpl.DEFAULT_PORT. + */ + private final InetSocketAddress address; + /** + * The socket channel for this WebSocket server. + */ + private ServerSocketChannel server; + /** + * The 'Selector' used to get event keys from the underlying socket. + */ + private Selector selector; + /** + * The Draft of the WebSocket protocol the Server is adhering to. + */ + private List drafts; + + private Thread selectorthread; + + private final AtomicBoolean isclosed = new AtomicBoolean(false); + + protected List decoders; + + private List iqueue; + private BlockingQueue buffers; + private int queueinvokes = 0; + private final AtomicInteger queuesize = new AtomicInteger(0); + + private WebSocketServerFactory wsf = new DefaultWebSocketServerFactory(); + + /** + * Attribute which allows you to configure the socket "backlog" parameter which determines how + * many client connections can be queued. + * + * @since 1.5.0 + */ + private int maxPendingConnections = -1; + + /** + * Creates a WebSocketServer that will attempt to listen on port WebSocketImpl.DEFAULT_PORT. + * + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer() { + this(new InetSocketAddress(WebSocketImpl.DEFAULT_PORT), AVAILABLE_PROCESSORS, null); + } + + /** + * Creates a WebSocketServer that will attempt to bind/listen on the given address. + * + * @param address The address to listen to + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer(InetSocketAddress address) { + this(address, AVAILABLE_PROCESSORS, null); + } + + /** + * @param address The address (host:port) this server should listen on. + * @param decodercount The number of {@link WebSocketWorker}s that will be used to process the + * incoming network data. By default this will be Runtime.getRuntime().availableProcessors() + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer(InetSocketAddress address, int decodercount) { + this(address, decodercount, null); + } + + /** + * @param address The address (host:port) this server should listen on. + * @param drafts The versions of the WebSocket protocol that this server instance should comply + * to. Clients that use an other protocol version will be rejected. + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer(InetSocketAddress address, List drafts) { + this(address, AVAILABLE_PROCESSORS, drafts); + } + + /** + * @param address The address (host:port) this server should listen on. + * @param decodercount The number of {@link WebSocketWorker}s that will be used to process the + * incoming network data. By default this will be Runtime.getRuntime().availableProcessors() + * @param drafts The versions of the WebSocket protocol that this server instance should + * comply to. Clients that use an other protocol version will be rejected. + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer(InetSocketAddress address, int decodercount, List drafts) { + this(address, decodercount, drafts, new HashSet()); + } + + // Small internal helper function to get around limitations of Java constructors. + private static InetSocketAddress checkAddressOfExistingChannel(ServerSocketChannel existingChannel) { + assert existingChannel.isOpen(); + SocketAddress addr; + try { + addr = existingChannel.getLocalAddress(); + } catch (IOException e) { + throw new IllegalArgumentException("Could not get address of channel passed to WebSocketServer, make sure it is bound", e); + } + if (addr == null) { + throw new IllegalArgumentException("Could not get address of channel passed to WebSocketServer, make sure it is bound"); + } + return (InetSocketAddress)addr; + } + + /** + * @param existingChannel An already open and bound server socket channel, which this server will use. + * For example, it can be System.inheritedChannel() to implement socket activation. + */ + public WebSocketServer(ServerSocketChannel existingChannel) { + this(checkAddressOfExistingChannel(existingChannel)); + this.server = existingChannel; + } + + /** + * Creates a WebSocketServer that will attempt to bind/listen on the given address, and + * comply with Draft version draft. + * + * @param address The address (host:port) this server should listen on. + * @param decodercount The number of {@link WebSocketWorker}s that will be used to process + * the incoming network data. By default this will be + * Runtime.getRuntime().availableProcessors() + * @param drafts The versions of the WebSocket protocol that this server instance + * should comply to. Clients that use an other protocol version will + * be rejected. + * @param connectionscontainer Allows to specify a collection that will be used to store the + * websockets in.
If you plan to often iterate through the + * currently connected websockets you may want to use a collection + * that does not require synchronization like a {@link + * CopyOnWriteArraySet}. In that case make sure that you overload + * {@link #removeConnection(WebSocket)} and {@link + * #addConnection(WebSocket)}.
By default a {@link HashSet} will + * be used. + * @see #removeConnection(WebSocket) for more control over syncronized operation + * @see more about + * drafts + */ + public WebSocketServer(InetSocketAddress address, int decodercount, List drafts, + Collection connectionscontainer) { + if (address == null || decodercount < 1 || connectionscontainer == null) { + throw new IllegalArgumentException( + "address and connectionscontainer must not be null and you need at least 1 decoder"); + } + + if (drafts == null) { + this.drafts = Collections.emptyList(); + } else { + this.drafts = drafts; + } + + this.address = address; + this.connections = connectionscontainer; + setTcpNoDelay(false); + setReuseAddr(false); + iqueue = new LinkedList<>(); + + decoders = new ArrayList<>(decodercount); + buffers = new LinkedBlockingQueue<>(); + for (int i = 0; i < decodercount; i++) { + WebSocketWorker ex = new WebSocketWorker(); + decoders.add(ex); + } + } + + + /** + * Starts the server selectorthread that binds to the currently set port number and listeners for + * WebSocket connection requests. Creates a fixed thread pool with the size {@link + * WebSocketServer#AVAILABLE_PROCESSORS}
May only be called once. + *

+ * Alternatively you can call {@link WebSocketServer#run()} directly. + * + * @throws IllegalStateException Starting an instance again + */ + public void start() { + if (selectorthread != null) { + throw new IllegalStateException(getClass().getName() + " can only be started once."); + } + Thread t = new Thread(this); + t.setDaemon(isDaemon()); + t.start(); + } + + public void stop(int timeout) throws InterruptedException { + stop(timeout, ""); + } + + /** + * Closes all connected clients sockets, then closes the underlying ServerSocketChannel, + * effectively killing the server socket selectorthread, freeing the port the server was bound to + * and stops all internal workerthreads. + *

+ * If this method is called before the server is started it will never start. + * + * @param timeout Specifies how many milliseconds the overall close handshaking may take + * altogether before the connections are closed without proper close + * handshaking. + * @param closeMessage Specifies message for remote client
+ * @throws InterruptedException Interrupt + */ + public void stop(int timeout, String closeMessage) throws InterruptedException { + if (!isclosed.compareAndSet(false, + true)) { // this also makes sure that no further connections will be added to this.connections + return; + } + + List socketsToClose; + + // copy the connections in a list (prevent callback deadlocks) + synchronized (connections) { + socketsToClose = new ArrayList<>(connections); + } + + for (WebSocket ws : socketsToClose) { + ws.close(CloseFrame.GOING_AWAY, closeMessage); + } + + wsf.close(); + + synchronized (this) { + if (selectorthread != null && selector != null) { + selector.wakeup(); + selectorthread.join(timeout); + } + } + } + + public void stop() throws InterruptedException { + stop(0); + } + + /** + * Returns all currently connected clients. This collection does not allow any modification e.g. + * removing a client. + * + * @return A unmodifiable collection of all currently connected clients + * @since 1.3.8 + */ + public Collection getConnections() { + synchronized (connections) { + return Collections.unmodifiableCollection(new ArrayList<>(connections)); + } + } + + public InetSocketAddress getAddress() { + return this.address; + } + + /** + * Gets the port number that this server listens on. + * + * @return The port number. + */ + public int getPort() { + int port = getAddress().getPort(); + if (port == 0 && server != null) { + port = server.socket().getLocalPort(); + } + return port; + } + + @Override + public void setDaemon(boolean daemon) { + // pass it to the AbstractWebSocket too, to use it on the connectionLostChecker thread factory + super.setDaemon(daemon); + // we need to apply this to the decoders as well since they were created during the constructor + for (WebSocketWorker w : decoders) { + if (w.isAlive()) { + throw new IllegalStateException("Cannot call setDaemon after server is already started!"); + } else { + w.setDaemon(daemon); + } + } + } + + /** + * Get the list of active drafts + * + * @return the available drafts for this server + */ + public List getDraft() { + return Collections.unmodifiableList(drafts); + } + + /** + * Set the requested maximum number of pending connections on the socket. The exact semantics are + * implementation specific. The value provided should be greater than 0. If it is less than or + * equal to 0, then an implementation specific default will be used. This option will be passed as + * "backlog" parameter to {@link ServerSocket#bind(SocketAddress, int)} + * + * @since 1.5.0 + * @param numberOfConnections the new number of allowed pending connections + */ + public void setMaxPendingConnections(int numberOfConnections) { + maxPendingConnections = numberOfConnections; + } + + /** + * Returns the currently configured maximum number of pending connections. + * + * @see #setMaxPendingConnections(int) + * @since 1.5.0 + * @return the maximum number of pending connections + */ + public int getMaxPendingConnections() { + return maxPendingConnections; + } + + // Runnable IMPLEMENTATION ///////////////////////////////////////////////// + public void run() { + if (!doEnsureSingleThread()) { + return; + } + if (!doSetupSelectorAndServerThread()) { + return; + } + try { + int shutdownCount = 5; + int selectTimeout = 0; + while (!selectorthread.isInterrupted() && shutdownCount != 0) { + SelectionKey key = null; + try { + if (isclosed.get()) { + selectTimeout = 5; + } + int keyCount = selector.select(selectTimeout); + if (keyCount == 0 && isclosed.get()) { + shutdownCount--; + } + Set keys = selector.selectedKeys(); + Iterator i = keys.iterator(); + + while (i.hasNext()) { + key = i.next(); + + if (!key.isValid()) { + continue; + } + + if (key.isAcceptable()) { + doAccept(key, i); + continue; + } + + if (key.isReadable() && !doRead(key, i)) { + continue; + } + + if (key.isWritable()) { + doWrite(key); + } + } + doAdditionalRead(); + } catch (CancelledKeyException e) { + // an other thread may cancel the key + } catch (ClosedByInterruptException e) { + return; // do the same stuff as when InterruptedException is thrown + } catch (WrappedIOException ex) { + handleIOException(key, ex.getConnection(), ex.getIOException()); + } catch (IOException ex) { + handleIOException(key, null, ex); + } catch (InterruptedException e) { + // FIXME controlled shutdown (e.g. take care of buffermanagement) + Thread.currentThread().interrupt(); + } + } + } catch (RuntimeException e) { + // should hopefully never occur + handleFatal(null, e); + } finally { + doServerShutdown(); + } + } + + /** + * Do an additional read + * + * @throws InterruptedException thrown by taking a buffer + * @throws IOException if an error happened during read + */ + private void doAdditionalRead() throws InterruptedException, IOException { + WebSocketImpl conn; + while (!iqueue.isEmpty()) { + conn = iqueue.remove(0); + WrappedByteChannel c = ((WrappedByteChannel) conn.getChannel()); + ByteBuffer buf = takeBuffer(); + try { + if (SocketChannelIOHelper.readMore(buf, conn, c)) { + iqueue.add(conn); + } + if (buf.hasRemaining()) { + conn.inQueue.put(buf); + queue(conn); + } else { + pushBuffer(buf); + } + } catch (IOException e) { + pushBuffer(buf); + throw e; + } + } + } + + /** + * Execute a accept operation + * + * @param key the selectionkey to read off + * @param i the iterator for the selection keys + * @throws InterruptedException thrown by taking a buffer + * @throws IOException if an error happened during accept + */ + private void doAccept(SelectionKey key, Iterator i) + throws IOException, InterruptedException { + if (!onConnect(key)) { + key.cancel(); + return; + } + + SocketChannel channel = server.accept(); + if (channel == null) { + return; + } + channel.configureBlocking(false); + Socket socket = channel.socket(); + socket.setTcpNoDelay(isTcpNoDelay()); + socket.setKeepAlive(true); + WebSocketImpl w = wsf.createWebSocket(this, drafts); + w.setSelectionKey(channel.register(selector, SelectionKey.OP_READ, w)); + try { + w.setChannel(wsf.wrapChannel(channel, w.getSelectionKey())); + i.remove(); + allocateBuffers(w); + } catch (IOException ex) { + if (w.getSelectionKey() != null) { + w.getSelectionKey().cancel(); + } + + handleIOException(w.getSelectionKey(), null, ex); + } + } + + /** + * Execute a read operation + * + * @param key the selectionkey to read off + * @param i the iterator for the selection keys + * @return true, if the read was successful, or false if there was an error + * @throws InterruptedException thrown by taking a buffer + * @throws IOException if an error happened during read + */ + private boolean doRead(SelectionKey key, Iterator i) + throws InterruptedException, WrappedIOException { + WebSocketImpl conn = (WebSocketImpl) key.attachment(); + ByteBuffer buf = takeBuffer(); + if (conn.getChannel() == null) { + key.cancel(); + + handleIOException(key, conn, new IOException()); + return false; + } + try { + if (SocketChannelIOHelper.read(buf, conn, conn.getChannel())) { + if (buf.hasRemaining()) { + conn.inQueue.put(buf); + queue(conn); + i.remove(); + if (conn.getChannel() instanceof WrappedByteChannel && ((WrappedByteChannel) conn + .getChannel()).isNeedRead()) { + iqueue.add(conn); + } + } else { + pushBuffer(buf); + } + } else { + pushBuffer(buf); + } + } catch (IOException e) { + pushBuffer(buf); + throw new WrappedIOException(conn, e); + } + return true; + } + + /** + * Execute a write operation + * + * @param key the selectionkey to write on + * @throws IOException if an error happened during batch + */ + private void doWrite(SelectionKey key) throws WrappedIOException { + WebSocketImpl conn = (WebSocketImpl) key.attachment(); + try { + if (SocketChannelIOHelper.batch(conn, conn.getChannel()) && key.isValid()) { + key.interestOps(SelectionKey.OP_READ); + } + } catch (IOException e) { + throw new WrappedIOException(conn, e); + } + } + + /** + * Setup the selector thread as well as basic server settings + * + * @return true, if everything was successful, false if some error happened + */ + private boolean doSetupSelectorAndServerThread() { + selectorthread.setName("WebSocketSelector-" + selectorthread.getId()); + try { + if (server == null) { + server = ServerSocketChannel.open(); + // If 'server' is not null, that means WebSocketServer was created from existing channel. + } + server.configureBlocking(false); + ServerSocket socket = server.socket(); + int receiveBufferSize = getReceiveBufferSize(); + if (receiveBufferSize > 0) { + socket.setReceiveBufferSize(receiveBufferSize); + } + socket.setReuseAddress(isReuseAddr()); + // Socket may be already bound, if an existing channel was passed to constructor. + // In this case we cannot modify backlog size from pure Java code, so leave it as is. + if (!socket.isBound()) { + socket.bind(address, getMaxPendingConnections()); + } + selector = Selector.open(); + server.register(selector, server.validOps()); + startConnectionLostTimer(); + for (WebSocketWorker ex : decoders) { + ex.start(); + } + onStart(); + } catch (IOException ex) { + handleFatal(null, ex); + return false; + } + return true; + } + + /** + * The websocket server can only be started once + * + * @return true, if the server can be started, false if already a thread is running + */ + private boolean doEnsureSingleThread() { + synchronized (this) { + if (selectorthread != null) { + throw new IllegalStateException(getClass().getName() + " can only be started once."); + } + selectorthread = Thread.currentThread(); + if (isclosed.get()) { + return false; + } + } + return true; + } + + /** + * Clean up everything after a shutdown + */ + private void doServerShutdown() { + stopConnectionLostTimer(); + if (decoders != null) { + for (WebSocketWorker w : decoders) { + w.interrupt(); + } + } + if (selector != null) { + try { + selector.close(); + } catch (IOException e) { + log.error("IOException during selector.close", e); + onError(null, e); + } + } + if (server != null) { + try { + server.close(); + } catch (IOException e) { + log.error("IOException during server.close", e); + onError(null, e); + } + } + } + + protected void allocateBuffers(WebSocket c) throws InterruptedException { + if (queuesize.get() >= 2 * decoders.size() + 1) { + return; + } + queuesize.incrementAndGet(); + buffers.put(createBuffer()); + } + + protected void releaseBuffers(WebSocket c) throws InterruptedException { + // queuesize.decrementAndGet(); + // takeBuffer(); + } + + public ByteBuffer createBuffer() { + int receiveBufferSize = getReceiveBufferSize(); + return ByteBuffer.allocate(receiveBufferSize > 0 ? receiveBufferSize : DEFAULT_READ_BUFFER_SIZE); + } + + protected void queue(WebSocketImpl ws) throws InterruptedException { + if (ws.getWorkerThread() == null) { + ws.setWorkerThread(decoders.get(queueinvokes % decoders.size())); + queueinvokes++; + } + ws.getWorkerThread().put(ws); + } + + private ByteBuffer takeBuffer() throws InterruptedException { + return buffers.take(); + } + + private void pushBuffer(ByteBuffer buf) throws InterruptedException { + if (buffers.size() > queuesize.intValue()) { + return; + } + buffers.put(buf); + } + + private void handleIOException(SelectionKey key, WebSocket conn, IOException ex) { + // onWebsocketError( conn, ex );// conn may be null here + if (key != null) { + key.cancel(); + } + if (conn != null) { + conn.closeConnection(CloseFrame.ABNORMAL_CLOSE, ex.getMessage()); + } else if (key != null) { + SelectableChannel channel = key.channel(); + if (channel != null && channel + .isOpen()) { // this could be the case if the IOException ex is a SSLException + try { + channel.close(); + } catch (IOException e) { + // there is nothing that must be done here + } + log.trace("Connection closed because of exception", ex); + } + } + } + + private void handleFatal(WebSocket conn, Exception e) { + log.error("Shutdown due to fatal error", e); + onError(conn, e); + + String causeMessage = e.getCause() != null ? " caused by " + e.getCause().getClass().getName() : ""; + String errorMessage = "Got error on server side: " + e.getClass().getName() + causeMessage; + try { + stop(0, errorMessage); + } catch (InterruptedException e1) { + Thread.currentThread().interrupt(); + log.error("Interrupt during stop", e); + onError(null, e1); + } + + //Shutting down WebSocketWorkers, see #222 + if (decoders != null) { + for (WebSocketWorker w : decoders) { + w.interrupt(); + } + } + if (selectorthread != null) { + selectorthread.interrupt(); + } + } + + @Override + public final void onWebsocketMessage(WebSocket conn, String message) { + onMessage(conn, message); + } + + + @Override + public final void onWebsocketMessage(WebSocket conn, ByteBuffer blob) { + onMessage(conn, blob); + } + + @Override + public final void onWebsocketOpen(WebSocket conn, Handshakedata handshake) { + if (addConnection(conn)) { + onOpen(conn, (ClientHandshake) handshake); + } + } + + @Override + public final void onWebsocketClose(WebSocket conn, int code, String reason, boolean remote) { + selector.wakeup(); + try { + if (removeConnection(conn)) { + onClose(conn, code, reason, remote); + } + } finally { + try { + releaseBuffers(conn); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + } + + /** + * This method performs remove operations on the connection and therefore also gives control over + * whether the operation shall be synchronized + *

+ * {@link #WebSocketServer(InetSocketAddress, int, List, Collection)} allows to specify a + * collection which will be used to store current connections in.
Depending on the type on the + * connection, modifications of that collection may have to be synchronized. + * + * @param ws The Websocket connection which should be removed + * @return Removing connection successful + */ + protected boolean removeConnection(WebSocket ws) { + boolean removed = false; + synchronized (connections) { + if (this.connections.contains(ws)) { + removed = this.connections.remove(ws); + } else { + //Don't throw an assert error if the ws is not in the list. e.g. when the other endpoint did not send any handshake. see #512 + log.trace( + "Removing connection which is not in the connections collection! Possible no handshake received! {}", + ws); + } + } + if (isclosed.get() && connections.isEmpty()) { + selectorthread.interrupt(); + } + return removed; + } + + /** + * @param ws the Websocket connection which should be added + * @return Adding connection successful + * @see #removeConnection(WebSocket) + */ + protected boolean addConnection(WebSocket ws) { + if (!isclosed.get()) { + synchronized (connections) { + return this.connections.add(ws); + } + } else { + // This case will happen when a new connection gets ready while the server is already stopping. + ws.close(CloseFrame.GOING_AWAY); + return true;// for consistency sake we will make sure that both onOpen will be called + } + } + + @Override + public final void onWebsocketError(WebSocket conn, Exception ex) { + onError(conn, ex); + } + + @Override + public final void onWriteDemand(WebSocket w) { + WebSocketImpl conn = (WebSocketImpl) w; + try { + conn.getSelectionKey().interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); + } catch (CancelledKeyException e) { + // the thread which cancels key is responsible for possible cleanup + conn.outQueue.clear(); + } + selector.wakeup(); + } + + @Override + public void onWebsocketCloseInitiated(WebSocket conn, int code, String reason) { + onCloseInitiated(conn, code, reason); + } + + @Override + public void onWebsocketClosing(WebSocket conn, int code, String reason, boolean remote) { + onClosing(conn, code, reason, remote); + + } + + public void onCloseInitiated(WebSocket conn, int code, String reason) { + } + + public void onClosing(WebSocket conn, int code, String reason, boolean remote) { + + } + + public final void setWebSocketFactory(WebSocketServerFactory wsf) { + if (this.wsf != null) { + this.wsf.close(); + } + this.wsf = wsf; + } + + public final WebSocketFactory getWebSocketFactory() { + return wsf; + } + + /** + * Returns whether a new connection shall be accepted or not.
Therefore method is well suited + * to implement some kind of connection limitation.
+ * + * @param key the SelectionKey for the new connection + * @return Can this new connection be accepted + * @see #onOpen(WebSocket, ClientHandshake) + * @see #onWebsocketHandshakeReceivedAsServer(WebSocket, Draft, ClientHandshake) + **/ + protected boolean onConnect(SelectionKey key) { + return true; + } + + /** + * Getter to return the socket used by this specific connection + * + * @param conn The specific connection + * @return The socket used by this connection + */ + private Socket getSocket(WebSocket conn) { + WebSocketImpl impl = (WebSocketImpl) conn; + return ((SocketChannel) impl.getSelectionKey().channel()).socket(); + } + + @Override + public InetSocketAddress getLocalSocketAddress(WebSocket conn) { + return (InetSocketAddress) getSocket(conn).getLocalSocketAddress(); + } + + @Override + public InetSocketAddress getRemoteSocketAddress(WebSocket conn) { + return (InetSocketAddress) getSocket(conn).getRemoteSocketAddress(); + } + + /** + * Called after an opening handshake has been performed and the given websocket is ready to be + * written on. + * + * @param conn The WebSocket instance this event is occurring on. + * @param handshake The handshake of the websocket instance + */ + public abstract void onOpen(WebSocket conn, ClientHandshake handshake); + + /** + * Called after the websocket connection has been closed. + * + * @param conn The WebSocket instance this event is occurring on. + * @param code The codes can be looked up here: {@link CloseFrame} + * @param reason Additional information string + * @param remote Returns whether or not the closing of the connection was initiated by the remote + * host. + **/ + public abstract void onClose(WebSocket conn, int code, String reason, boolean remote); + + /** + * Callback for string messages received from the remote host + * + * @param conn The WebSocket instance this event is occurring on. + * @param message The UTF-8 decoded message that was received. + * @see #onMessage(WebSocket, ByteBuffer) + **/ + public abstract void onMessage(WebSocket conn, String message); + + /** + * Called when errors occurs. If an error causes the websocket connection to fail {@link + * #onClose(WebSocket, int, String, boolean)} will be called additionally.
This method will be + * called primarily because of IO or protocol errors.
If the given exception is an + * RuntimeException that probably means that you encountered a bug.
+ * + * @param conn Can be null if there error does not belong to one specific websocket. For example + * if the servers port could not be bound. + * @param ex The exception causing this error + **/ + public abstract void onError(WebSocket conn, Exception ex); + + /** + * Called when the server started up successfully. + *

+ * If any error occurred, onError is called instead. + */ + public abstract void onStart(); + + /** + * Callback for binary messages received from the remote host + * + * @param conn The WebSocket instance this event is occurring on. + * @param message The binary message that was received. + * @see #onMessage(WebSocket, ByteBuffer) + **/ + public void onMessage(WebSocket conn, ByteBuffer message) { + } + + /** + * Send a text to all connected endpoints + * + * @param text the text to send to the endpoints + */ + public void broadcast(String text) { + broadcast(text, connections); + } + + /** + * Send a byte array to all connected endpoints + * + * @param data the data to send to the endpoints + */ + public void broadcast(byte[] data) { + broadcast(data, connections); + } + + /** + * Send a ByteBuffer to all connected endpoints + * + * @param data the data to send to the endpoints + */ + public void broadcast(ByteBuffer data) { + broadcast(data, connections); + } + + /** + * Send a byte array to a specific collection of websocket connections + * + * @param data the data to send to the endpoints + * @param clients a collection of endpoints to whom the text has to be send + */ + public void broadcast(byte[] data, Collection clients) { + if (data == null || clients == null) { + throw new IllegalArgumentException(); + } + broadcast(ByteBuffer.wrap(data), clients); + } + + /** + * Send a ByteBuffer to a specific collection of websocket connections + * + * @param data the data to send to the endpoints + * @param clients a collection of endpoints to whom the text has to be send + */ + public void broadcast(ByteBuffer data, Collection clients) { + if (data == null || clients == null) { + throw new IllegalArgumentException(); + } + doBroadcast(data, clients); + } + + /** + * Send a text to a specific collection of websocket connections + * + * @param text the text to send to the endpoints + * @param clients a collection of endpoints to whom the text has to be send + */ + public void broadcast(String text, Collection clients) { + if (text == null || clients == null) { + throw new IllegalArgumentException(); + } + doBroadcast(text, clients); + } + + /** + * Private method to cache all the frames to improve memory footprint and conversion time + * + * @param data the data to broadcast + * @param clients the clients to send the message to + */ + private void doBroadcast(Object data, Collection clients) { + String strData = null; + if (data instanceof String) { + strData = (String) data; + } + ByteBuffer byteData = null; + if (data instanceof ByteBuffer) { + byteData = (ByteBuffer) data; + } + if (strData == null && byteData == null) { + return; + } + Map> draftFrames = new HashMap<>(); + List clientCopy; + synchronized (clients) { + clientCopy = new ArrayList<>(clients); + } + for (WebSocket client : clientCopy) { + if (client != null) { + Draft draft = client.getDraft(); + fillFrames(draft, draftFrames, strData, byteData); + try { + client.sendFrame(draftFrames.get(draft)); + } catch (WebsocketNotConnectedException e) { + //Ignore this exception in this case + } + } + } + } + + /** + * Fills the draftFrames with new data for the broadcast + * + * @param draft The draft to use + * @param draftFrames The list of frames per draft to fill + * @param strData the string data, can be null + * @param byteData the byte buffer data, can be null + */ + private void fillFrames(Draft draft, Map> draftFrames, String strData, + ByteBuffer byteData) { + if (!draftFrames.containsKey(draft)) { + List frames = null; + if (strData != null) { + frames = draft.createFrames(strData, false); + } + if (byteData != null) { + frames = draft.createFrames(byteData, false); + } + if (frames != null) { + draftFrames.put(draft, frames); + } + } + } + + /** + * This class is used to process incoming data + */ + public class WebSocketWorker extends Thread { + + private BlockingQueue iqueue; + + public WebSocketWorker() { + iqueue = new LinkedBlockingQueue<>(); + setName("WebSocketWorker-" + getId()); + setUncaughtExceptionHandler(new UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + log.error("Uncaught exception in thread {}: {}", t.getName(), e); + } + }); + } + + public void put(WebSocketImpl ws) throws InterruptedException { + iqueue.put(ws); + } + + @Override + public void run() { + WebSocketImpl ws = null; + try { + while (true) { + ByteBuffer buf; + ws = iqueue.take(); + buf = ws.inQueue.poll(); + assert (buf != null); + doDecode(ws, buf); + ws = null; + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (VirtualMachineError | ThreadDeath | LinkageError e) { + log.error("Got fatal error in worker thread {}", getName()); + Exception exception = new Exception(e); + handleFatal(ws, exception); + } catch (Throwable e) { + log.error("Uncaught exception in thread {}: {}", getName(), e); + if (ws != null) { + Exception exception = new Exception(e); + onWebsocketError(ws, exception); + ws.close(); + } + } + } + + /** + * call ws.decode on the byteBuffer + * + * @param ws the Websocket + * @param buf the buffer to decode to + * @throws InterruptedException thrown by pushBuffer + */ + private void doDecode(WebSocketImpl ws, ByteBuffer buf) throws InterruptedException { + try { + ws.decode(buf); + } catch (Exception e) { + log.error("Error while reading from remote connection", e); + } finally { + pushBuffer(buf); + } + } + } } diff --git a/src/main/java/org/java_websocket/server/package-info.java b/src/main/java/org/java_websocket/server/package-info.java new file mode 100644 index 000000000..0676bafe6 --- /dev/null +++ b/src/main/java/org/java_websocket/server/package-info.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +/** + * This package encapsulates all implementations in relation with the WebSocketServer. + */ +package org.java_websocket.server; \ No newline at end of file diff --git a/src/main/java/org/java_websocket/util/Base64.java b/src/main/java/org/java_websocket/util/Base64.java index 7f243444d..067a027e1 100644 --- a/src/main/java/org/java_websocket/util/Base64.java +++ b/src/main/java/org/java_websocket/util/Base64.java @@ -1,31 +1,57 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.util; /** *

Encodes and decodes to and from Base64 notation.

*

Homepage: http://iharder.net/base64.

- * + * *

Example:

- * + * * String encoded = Base64.encode( myByteArray ); *
* byte[] myByteArray = Base64.decode( encoded ); * - *

The options parameter, which appears in a few places, is used to pass - * several pieces of information to the encoder. In the "higher level" methods such as - * encodeBytes( bytes, options ) the options parameter can be used to indicate such - * things as first gzipping the bytes before encoding them, not inserting linefeeds, - * and encoding using the URL-safe and Ordered dialects.

+ *

The options parameter, which appears in a few places, is used to pass + * several pieces of information to the encoder. In the "higher level" methods such as encodeBytes( + * bytes, options ) the options parameter can be used to indicate such things as first gzipping the + * bytes before encoding them, not inserting linefeeds, and encoding using the URL-safe and Ordered + * dialects.

* *

Note, according to RFC3548, - * Section 2.1, implementations should not add line feeds unless explicitly told - * to do so. I've got Base64 set to this behavior now, although earlier versions - * broke lines by default.

+ * Section 2.1, implementations should not add line feeds unless explicitly told to do so. I've got + * Base64 set to this behavior now, although earlier versions broke lines by default.

* - *

The constants defined in Base64 can be OR-ed together to combine options, so you + *

The constants defined in Base64 can be OR-ed together to combine options, so you * might make a call like this:

* - * String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES ); - *

to compress the data before encoding it and then making the output have newline characters.

+ * String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES + * ); + *

to compress the data before encoding it and then making the output have newline + * characters.

*

Also...

* String encoded = Base64.encodeBytes( crazyString.getBytes() ); * @@ -44,15 +70,11 @@ *
  • v2.3.6 - Fixed bug when breaking lines and the final byte of the encoded * string ended in the last column; the buffer was not properly shrunk and * contained an extra (null) byte that made it into the string.
  • - *
  • v2.3.5 - Fixed bug in {@link #encodeFromFile} where estimated buffer size - * was wrong for files of size 31, 34, and 37 bytes.
  • *
  • v2.3.4 - Fixed bug when working with gzipped streams whereby flushing * the Base64.OutputStream closed the Base64 encoding (by padding with equals * signs) too soon. Also added an option to suppress the automatic decoding * of gzipped streams. Also added experimental support for specifying a - * class loader when using the - * {@link #decodeToObject(java.lang.String, int, java.lang.ClassLoader)} - * method.
  • + * class loader when using the method. *
  • v2.3.3 - Changed default char encoding to US-ASCII which reduces the internal Java * footprint with its CharEncoders and so forth. Fixed some javadocs that were * inconsistent. Removed imports and specified things like java.io.IOException @@ -60,7 +82,7 @@ *
  • v2.3.2 - Reduced memory footprint! Finally refined the "guessing" of how big the * final encoded data will be so that the code doesn't have to create two output * arrays: an oversized initial one and then a final, exact-sized one. Big win - * when using the {@link #encodeBytesToBytes(byte[])} family of methods (and not + * when using the family of methods (and not * using the gzip options which uses a different mechanism with streams and stuff).
  • *
  • v2.3.1 - Added {@link #encodeBytesToBytes(byte[], int, int, int)} and some * similar helper methods to be more efficient with memory by not returning a @@ -68,7 +90,7 @@ *
  • v2.3 - This is not a drop-in replacement! This is two years of comments * and bug fixes queued up and finally executed. Thanks to everyone who sent * me stuff, and I'm sorry I wasn't able to distribute your fixes to everyone else. - * Much bad coding was cleaned up including throwing exceptions where necessary + * Much bad coding was cleaned up including throwing exceptions where necessary * instead of returning null values or something similar. Here are some changes * that may affect you: *
      @@ -76,14 +98,14 @@ * RFC3548. *
    • Throws exceptions instead of returning null values. Because some operations * (especially those that may permit the GZIP option) use IO streams, there - * is a possiblity of an java.io.IOException being thrown. After some discussion and + * is a possibility of an java.io.IOException being thrown. After some discussion and * thought, I've changed the behavior of the methods to throw java.io.IOExceptions * rather than return null if ever there's an error. I think this is more * appropriate, though it will require some changes to your code. Sorry, * it should have been done this way to begin with.
    • *
    • Removed all references to System.out, System.err, and the like. * Shame on me. All I can say is sorry they were ever there.
    • - *
    • Throws NullPointerExceptions and IllegalArgumentExceptions as needed + *
    • Throws IllegalArgumentExceptions as needed * such as when passed arrays are null or offsets are invalid.
    • *
    • Cleaned up as much javadoc as I could to avoid any javadoc warnings. * This was especially annoying before for people who were thorough in their @@ -106,24 +128,24 @@ * Special thanks to Jim Kellerman at http://www.powerset.com/ * for contributing the new Base64 dialects. *
    • - * + * *
    • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added * some convenience methods for reading and writing to and from files.
    • *
    • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems * with other encodings (like EBCDIC).
    • *
    • v2.0.1 - Fixed an error when decoding a single byte, that is, when the * encoded data was a single byte.
    • - *
    • v2.0 - I got rid of methods that used booleans to set options. + *
    • v2.0 - I got rid of methods that used booleans to set options. * Now everything is more consolidated and cleaner. The code now detects * when data that's being decoded is gzip-compressed and will decompress it * automatically. Generally things are cleaner. You'll probably have to * change some method calls that you were making to support the new - * options format (ints that you "OR" together).
    • - *
    • v1.5.1 - Fixed bug when decompressing and decoding to a - * byte[] using decode( String s, boolean gzipCompressed ). - * Added the ability to "suspend" encoding in the Output Stream so - * you can turn on and off the encoding if you need to embed base64 - * data in an otherwise "normal" stream (like an XML file).
    • + * options format (ints that you "OR" together). + *
    • v1.5.1 - Fixed bug when decompressing and decoding to a + * byte[] using decode( String s, boolean gzipCompressed ). + * Added the ability to "suspend" encoding in the Output Stream so + * you can turn on and off the encoding if you need to embed base64 + * data in an otherwise "normal" stream (like an XML file).
    • *
    • v1.5 - Output stream pases on flush() command but doesn't do anything itself. * This helps when using GZIP streams. * Added the ability to GZip-compress objects before encoding them.
    • @@ -147,1919 +169,881 @@ * @author rob@iharder.net * @version 2.3.7 */ -public class Base64 -{ - -/* ******** P U B L I C F I E L D S ******** */ - - - /** No options specified. Value is zero. */ - public final static int NO_OPTIONS = 0; - - /** Specify encoding in first bit. Value is one. */ - public final static int ENCODE = 1; - - - /** Specify decoding in first bit. Value is zero. */ - public final static int DECODE = 0; - - - /** Specify that data should be gzip-compressed in second bit. Value is two. */ - public final static int GZIP = 2; - - /** Specify that gzipped data should not be automatically gunzipped. */ - public final static int DONT_GUNZIP = 4; - - - /** Do break lines when encoding. Value is 8. */ - public final static int DO_BREAK_LINES = 8; - - /** - * Encode using Base64-like encoding that is URL- and Filename-safe as described - * in Section 4 of RFC3548: - * http://www.faqs.org/rfcs/rfc3548.html. - * It is important to note that data encoded this way is not officially valid Base64, - * or at the very least should not be called Base64 without also specifying that is - * was encoded using the URL- and Filename-safe dialect. - */ - public final static int URL_SAFE = 16; - - - /** - * Encode using the special "ordered" dialect of Base64 described here: - * http://www.faqs.org/qa/rfcc-1940.html. - */ - public final static int ORDERED = 32; - - -/* ******** P R I V A T E F I E L D S ******** */ - - - /** Maximum line length (76) of Base64 output. */ - private final static int MAX_LINE_LENGTH = 76; - - - /** The equals sign (=) as a byte. */ - private final static byte EQUALS_SIGN = (byte)'='; - - - /** The new line character (\n) as a byte. */ - private final static byte NEW_LINE = (byte)'\n'; - - - /** Preferred encoding. */ - private final static String PREFERRED_ENCODING = "US-ASCII"; - - - private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding - private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding - - -/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ - - /** The 64 valid Base64 values. */ - /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ - private final static byte[] _STANDARD_ALPHABET = { - (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', - (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', - (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', - (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', - (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', - (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', - (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', - (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', - (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' - }; - - - /** - * Translates a Base64 value to either its 6-bit reconstruction value - * or a negative number indicating some other meaning. - **/ - private final static byte[] _STANDARD_DECODABET = { - -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 - -5,-5, // Whitespace: Tab and Linefeed - -9,-9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 - -9,-9,-9,-9,-9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 - 62, // Plus sign at decimal 43 - -9,-9,-9, // Decimal 44 - 46 - 63, // Slash at decimal 47 - 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine - -9,-9,-9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9,-9,-9, // Decimal 62 - 64 - 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' - 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' - -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 - 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' - 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' - -9,-9,-9,-9,-9 // Decimal 123 - 127 - ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 - }; - - -/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ - - /** - * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: - * http://www.faqs.org/rfcs/rfc3548.html. - * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." - */ - private final static byte[] _URL_SAFE_ALPHABET = { - (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', - (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', - (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', - (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', - (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', - (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', - (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', - (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', - (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_' - }; - - /** - * Used in decoding URL- and Filename-safe dialects of Base64. - */ - private final static byte[] _URL_SAFE_DECODABET = { - -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 - -5,-5, // Whitespace: Tab and Linefeed - -9,-9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 - -9,-9,-9,-9,-9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 - -9, // Plus sign at decimal 43 - -9, // Decimal 44 - 62, // Minus sign at decimal 45 - -9, // Decimal 46 - -9, // Slash at decimal 47 - 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine - -9,-9,-9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9,-9,-9, // Decimal 62 - 64 - 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' - 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' - -9,-9,-9,-9, // Decimal 91 - 94 - 63, // Underscore at decimal 95 - -9, // Decimal 96 - 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' - 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' - -9,-9,-9,-9,-9 // Decimal 123 - 127 - ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 - }; - - - -/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ +public class Base64 { - /** - * I don't get the point of this technique, but someone requested it, - * and it is described here: - * http://www.faqs.org/qa/rfcc-1940.html. - */ - private final static byte[] _ORDERED_ALPHABET = { - (byte)'-', - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', - (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', - (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', - (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', - (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', - (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', - (byte)'_', - (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', - (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', - (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', - (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z' - }; - - /** - * Used in decoding the "ordered" dialect of Base64. - */ - private final static byte[] _ORDERED_DECODABET = { - -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 - -5,-5, // Whitespace: Tab and Linefeed - -9,-9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 - -9,-9,-9,-9,-9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 - -9, // Plus sign at decimal 43 - -9, // Decimal 44 - 0, // Minus sign at decimal 45 - -9, // Decimal 46 - -9, // Slash at decimal 47 - 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine - -9,-9,-9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9,-9,-9, // Decimal 62 - 64 - 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M' - 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z' - -9,-9,-9,-9, // Decimal 91 - 94 - 37, // Underscore at decimal 95 - -9, // Decimal 96 - 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm' - 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z' - -9,-9,-9,-9,-9 // Decimal 123 - 127 - ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 - }; - - -/* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ + /* ******** P U B L I C F I E L D S ******** */ - /** - * Returns one of the _SOMETHING_ALPHABET byte arrays depending on - * the options specified. - * It's possible, though silly, to specify ORDERED and URLSAFE - * in which case one of them will be picked, though there is - * no guarantee as to which one will be picked. - */ - private final static byte[] getAlphabet( int options ) { - if ((options & URL_SAFE) == URL_SAFE) { - return _URL_SAFE_ALPHABET; - } else if ((options & ORDERED) == ORDERED) { - return _ORDERED_ALPHABET; - } else { - return _STANDARD_ALPHABET; + /** + * No options specified. Value is zero. + */ + public static final int NO_OPTIONS = 0; + + /** + * Specify encoding in first bit. Value is one. + */ + public static final int ENCODE = 1; + + /** + * Specify that data should be gzip-compressed in second bit. Value is two. + */ + public static final int GZIP = 2; + + /** + * Do break lines when encoding. Value is 8. + */ + public static final int DO_BREAK_LINES = 8; + + /** + * Encode using Base64-like encoding that is URL- and Filename-safe as described in Section 4 of + * RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + * It is important to note that data encoded this way is not officially valid Base64, or + * at the very least should not be called Base64 without also specifying that is was encoded using + * the URL- and Filename-safe dialect. + */ + public static final int URL_SAFE = 16; + + + /** + * Encode using the special "ordered" dialect of Base64 described here: + * http://www.faqs.org/qa/rfcc-1940.html. + */ + public static final int ORDERED = 32; + + + /* ******** P R I V A T E F I E L D S ******** */ + + + /** + * Maximum line length (76) of Base64 output. + */ + private static final int MAX_LINE_LENGTH = 76; + + + /** + * The equals sign (=) as a byte. + */ + private static final byte EQUALS_SIGN = (byte) '='; + + + /** + * The new line character (\n) as a byte. + */ + private static final byte NEW_LINE = (byte) '\n'; + + + /** + * Preferred encoding. + */ + private static final String PREFERRED_ENCODING = "US-ASCII"; + + + private static final byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding + + + /* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ + + /** + * The 64 valid Base64 values. + */ + /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ + private static final byte[] _STANDARD_ALPHABET = { + (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', + (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', + (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', + (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', + (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', + (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', + (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', + (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', + (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', + (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/' + }; + + + /** + * Translates a Base64 value to either its 6-bit reconstruction value or a negative number + * indicating some other meaning. + **/ + private static final byte[] _STANDARD_DECODABET = { + -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9, -9, -9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' + -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' + -9, -9, -9, -9, -9, // Decimal 123 - 127 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 + }; + + + /* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ + + /** + * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." + */ + private static final byte[] _URL_SAFE_ALPHABET = { + (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', + (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', + (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', + (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', + (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', + (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', + (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', + (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', + (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', + (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '-', (byte) '_' + }; + + /** + * Used in decoding URL- and Filename-safe dialects of Base64. + */ + private static final byte[] _URL_SAFE_DECODABET = { + -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 62, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' + -9, -9, -9, -9, // Decimal 91 - 94 + 63, // Underscore at decimal 95 + -9, // Decimal 96 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' + -9, -9, -9, -9, -9, // Decimal 123 - 127 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 + }; + + + + /* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ + + /** + * I don't get the point of this technique, but someone requested it, and it is described here: + * http://www.faqs.org/qa/rfcc-1940.html. + */ + private static final byte[] _ORDERED_ALPHABET = { + (byte) '-', + (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', + (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', + (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', + (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', + (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', + (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', + (byte) '_', + (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', + (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', + (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', + (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z' + }; + + /** + * Used in decoding the "ordered" dialect of Base64. + */ + private static final byte[] _ORDERED_DECODABET = { + -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 0, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, // Letters 'A' through 'M' + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, // Letters 'N' through 'Z' + -9, -9, -9, -9, // Decimal 91 - 94 + 37, // Underscore at decimal 95 + -9, // Decimal 96 + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, // Letters 'a' through 'm' + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // Letters 'n' through 'z' + -9, -9, -9, -9, -9, // Decimal 123 - 127 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 + }; + + + /* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ + + + /** + * Returns one of the _SOMETHING_ALPHABET byte arrays depending on the options specified. It's + * possible, though silly, to specify ORDERED and URLSAFE in which case one of them will be + * picked, though there is no guarantee as to which one will be picked. + */ + private static final byte[] getAlphabet(int options) { + if ((options & URL_SAFE) == URL_SAFE) { + return _URL_SAFE_ALPHABET; + } else if ((options & ORDERED) == ORDERED) { + return _ORDERED_ALPHABET; + } else { + return _STANDARD_ALPHABET; + } + } // end getAlphabet + + + /** + * Returns one of the _SOMETHING_DECODABET byte arrays depending on the options specified. It's + * possible, though silly, to specify ORDERED and URL_SAFE in which case one of them will be + * picked, though there is no guarantee as to which one will be picked. + */ + private static final byte[] getDecodabet(int options) { + if ((options & URL_SAFE) == URL_SAFE) { + return _URL_SAFE_DECODABET; + } else if ((options & ORDERED) == ORDERED) { + return _ORDERED_DECODABET; + } else { + return _STANDARD_DECODABET; + } + } // end getAlphabet + + + /** + * Defeats instantiation. + */ + private Base64() { + } + + + + + /* ******** E N C O D I N G M E T H O D S ******** */ + + + /** + * Encodes up to the first three bytes of array threeBytes and returns a four-byte + * array in Base64 notation. The actual number of significant bytes in your array is given by + * numSigBytes. The array threeBytes needs only be as big as + * numSigBytes. + * Code can reuse a byte array by passing a four-byte array as b4. + * + * @param b4 A reusable byte array to reduce array instantiation + * @param threeBytes the array to convert + * @param numSigBytes the number of significant bytes in your array + * @return four byte array in Base64 notation. + * @since 1.5.1 + */ + private static byte[] encode3to4(byte[] b4, byte[] threeBytes, int numSigBytes, int options) { + encode3to4(threeBytes, 0, numSigBytes, b4, 0, options); + return b4; + } // end encode3to4 + + + /** + *

      Encodes up to three bytes of the array source + * and writes the resulting four Base64 bytes to destination. The source and + * destination arrays can be manipulated anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays are large enough to accommodate + * srcOffset + 3 for the source array or destOffset + 4 for the + * destination array. The actual number of significant bytes in your array is given by + * numSigBytes.

      + *

      This is the lowest level of the encoding methods with + * all possible parameters.

      + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param numSigBytes the number of significant bytes in your array + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @return the destination array + * @since 1.3 + */ + private static byte[] encode3to4( + byte[] source, int srcOffset, int numSigBytes, + byte[] destination, int destOffset, int options) { + + final byte[] ALPHABET = getAlphabet(options); + + // 1 2 3 + // 01234567890123456789012345678901 Bit position + // --------000000001111111122222222 Array position from threeBytes + // --------| || || || | Six bit groups to index ALPHABET + // >>18 >>12 >> 6 >> 0 Right shift necessary + // 0x3f 0x3f 0x3f Additional AND + + // Create buffer with zero-padding if there are only one or two + // significant bytes passed in the array. + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte to an int. + int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) + | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) + | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); + + switch (numSigBytes) { + case 3: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f]; + return destination; + + case 2: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + + case 1: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = EQUALS_SIGN; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + + default: + return destination; + } // end switch + } // end encode3to4 + + + /** + * Encodes a byte array into Base64 notation. Does not GZip-compress data. + * + * @param source The data to convert + * @return The data in Base64-encoded form + * @throws IllegalArgumentException if source array is null + * @since 1.4 + */ + public static String encodeBytes(byte[] source) { + // Since we're not going to have the GZIP encoding turned on, + // we're not going to have an java.io.IOException thrown, so + // we should not force the user to have to catch it. + String encoded = null; + try { + encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); + } catch (java.io.IOException ex) { + assert false : ex.getMessage(); + } // end catch + assert encoded != null; + return encoded; + } // end encodeBytes + + + /** + * Encodes a byte array into Base64 notation. + *

      + * Example options:

      +   *   GZIP: gzip-compresses object before encoding it.
      +   *   DO_BREAK_LINES: break lines at 76 characters
      +   *     Note: Technically, this makes your encoding non-compliant.
      +   * 
      + *

      + * Example: encodeBytes( myData, Base64.GZIP ) or + *

      + * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) + * + * + *

      As of v 2.3, if there is an error with the GZIP stream, + * the method will throw an java.io.IOException. This is new to v2.3! In earlier versions, + * it just returned a null value, but in retrospect that's a pretty poor way to handle it.

      + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param options Specified options + * @return The Base64-encoded data as a String + * @throws java.io.IOException if there is an error + * @throws IllegalArgumentException if source array is null, if source array, offset, or length + * are invalid + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @since 2.0 + */ + public static String encodeBytes(byte[] source, int off, int len, int options) + throws java.io.IOException { + byte[] encoded = encodeBytesToBytes(source, off, len, options); + + // Return value according to relevant encoding. + try { + return new String(encoded, PREFERRED_ENCODING); + } catch (java.io.UnsupportedEncodingException uue) { + return new String(encoded); + } // end catch + + } // end encodeBytes + + /** + * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns a byte array instead of + * instantiating a String. This is more efficient if you're working with I/O streams and have + * large data sets to encode. + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param options Specified options + * @return The Base64-encoded data as a String + * @throws java.io.IOException if there is an error + * @throws IllegalArgumentException if source array is null, if source array, offset, or length + * are invalid + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @since 2.3.1 + */ + public static byte[] encodeBytesToBytes(byte[] source, int off, int len, int options) + throws java.io.IOException { + + if (source == null) { + throw new IllegalArgumentException("Cannot serialize a null array."); + } // end if: null + + if (off < 0) { + throw new IllegalArgumentException("Cannot have negative offset: " + off); + } // end if: off < 0 + + if (len < 0) { + throw new IllegalArgumentException("Cannot have length offset: " + len); + } // end if: len < 0 + + if (off + len > source.length) { + throw new IllegalArgumentException( + String + .format("Cannot have offset of %d and length of %d with array of length %d", off, len, + source.length)); + } // end if: off < 0 + + // Compress? + if ((options & GZIP) != 0) { + java.io.ByteArrayOutputStream baos = null; + java.util.zip.GZIPOutputStream gzos = null; + Base64.OutputStream b64os = null; + + try { + // GZip -> Base64 -> ByteArray + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream(baos, ENCODE | options); + gzos = new java.util.zip.GZIPOutputStream(b64os); + + gzos.write(source, off, len); + gzos.close(); + } catch (java.io.IOException e) { + // Catch it and then throw it immediately so that + // the finally{} block is called for cleanup. + throw e; + } finally { + try { + if (gzos != null) { + gzos.close(); + } + } catch (Exception e) { + // do nothing } - } // end getAlphabet - - - /** - * Returns one of the _SOMETHING_DECODABET byte arrays depending on - * the options specified. - * It's possible, though silly, to specify ORDERED and URL_SAFE - * in which case one of them will be picked, though there is - * no guarantee as to which one will be picked. - */ - private final static byte[] getDecodabet( int options ) { - if( (options & URL_SAFE) == URL_SAFE) { - return _URL_SAFE_DECODABET; - } else if ((options & ORDERED) == ORDERED) { - return _ORDERED_DECODABET; - } else { - return _STANDARD_DECODABET; + try { + if (b64os != null) { + b64os.close(); + } + } catch (Exception e) { + // do nothing } - } // end getAlphabet - - - - /** Defeats instantiation. */ - private Base64(){} - - - - -/* ******** E N C O D I N G M E T H O D S ******** */ - - - /** - * Encodes up to the first three bytes of array threeBytes - * and returns a four-byte array in Base64 notation. - * The actual number of significant bytes in your array is - * given by numSigBytes. - * The array threeBytes needs only be as big as - * numSigBytes. - * Code can reuse a byte array by passing a four-byte array as b4. - * - * @param b4 A reusable byte array to reduce array instantiation - * @param threeBytes the array to convert - * @param numSigBytes the number of significant bytes in your array - * @return four byte array in Base64 notation. - * @since 1.5.1 - */ - private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options ) { - encode3to4( threeBytes, 0, numSigBytes, b4, 0, options ); - return b4; - } // end encode3to4 - - - /** - *

      Encodes up to three bytes of the array source - * and writes the resulting four Base64 bytes to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accomodate srcOffset + 3 for - * the source array or destOffset + 4 for - * the destination array. - * The actual number of significant bytes in your array is - * given by numSigBytes.

      - *

      This is the lowest level of the encoding methods with - * all possible parameters.

      - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param numSigBytes the number of significant bytes in your array - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @return the destination array - * @since 1.3 - */ - private static byte[] encode3to4( - byte[] source, int srcOffset, int numSigBytes, - byte[] destination, int destOffset, int options ) { - - byte[] ALPHABET = getAlphabet( options ); - - // 1 2 3 - // 01234567890123456789012345678901 Bit position - // --------000000001111111122222222 Array position from threeBytes - // --------| || || || | Six bit groups to index ALPHABET - // >>18 >>12 >> 6 >> 0 Right shift necessary - // 0x3f 0x3f 0x3f Additional AND - - // Create buffer with zero-padding if there are only one or two - // significant bytes passed in the array. - // We have to shift left 24 in order to flush out the 1's that appear - // when Java treats a value as negative that is cast from a byte to an int. - int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) - | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) - | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); - - switch( numSigBytes ) - { - case 3: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; - destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; - return destination; - - case 2: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; - destination[ destOffset + 3 ] = EQUALS_SIGN; - return destination; - - case 1: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = EQUALS_SIGN; - destination[ destOffset + 3 ] = EQUALS_SIGN; - return destination; - - default: - return destination; - } // end switch - } // end encode3to4 - - - - /** - * Performs Base64 encoding on the raw ByteBuffer, - * writing it to the encoded ByteBuffer. - * This is an experimental feature. Currently it does not - * pass along any options (such as {@link #DO_BREAK_LINES} - * or {@link #GZIP}. - * - * @param raw input buffer - * @param encoded output buffer - * @since 2.3 - */ - public static void encode( java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded ){ - byte[] raw3 = new byte[3]; - byte[] enc4 = new byte[4]; - - while( raw.hasRemaining() ){ - int rem = Math.min(3,raw.remaining()); - raw.get(raw3,0,rem); - Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); - encoded.put(enc4); - } // end input remaining + try { + if (baos != null) { + baos.close(); + } + } catch (Exception e) { + // do nothing + } + } // end finally + + return baos.toByteArray(); + } // end if: compress + + // Else, don't compress. Better not to use streams at all then. + else { + boolean breakLines = (options & DO_BREAK_LINES) != 0; + + //int len43 = len * 4 / 3; + //byte[] outBuff = new byte[ ( len43 ) // Main 4:3 + // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding + // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines + + // Try to determine more precisely how big the array needs to be. + // If we get it right, we don't have to do an array copy, and + // we save a bunch of memory. + int encLen = (len / 3) * 4 + (len % 3 > 0 ? 4 : 0); // Bytes needed for actual encoding + if (breakLines) { + encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters + } + byte[] outBuff = new byte[encLen]; + + int d = 0; + int e = 0; + int len2 = len - 2; + int lineLength = 0; + for (; d < len2; d += 3, e += 4) { + encode3to4(source, d + off, 3, outBuff, e, options); + + lineLength += 4; + if (breakLines && lineLength >= MAX_LINE_LENGTH) { + outBuff[e + 4] = NEW_LINE; + e++; + lineLength = 0; + } // end if: end of line + } // end for: each piece of array + + if (d < len) { + encode3to4(source, d + off, len - d, outBuff, e, options); + e += 4; + } // end if: some padding needed + + // Only resize array if we didn't guess it right. + if (e <= outBuff.length - 1) { + // If breaking lines and the last byte falls right at + // the line length (76 bytes per line), there will be + // one extra byte, and the array will need to be resized. + // Not too bad of an estimate on array size, I'd say. + byte[] finalOut = new byte[e]; + System.arraycopy(outBuff, 0, finalOut, 0, e); + //System.err.println("Having to resize array from " + outBuff.length + " to " + e ); + return finalOut; + } else { + //System.err.println("No need to resize array."); + return outBuff; + } + + } // end else: don't compress + + } // end encodeBytesToBytes + + + + + + /* ******** D E C O D I N G M E T H O D S ******** */ + + + /** + * Decodes four bytes from array source and writes the resulting bytes (up to three of + * them) to destination. The source and destination arrays can be manipulated anywhere + * along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays are large enough to accommodate + * srcOffset + 4 for the source array or destOffset + 3 for the + * destination array. This method returns the actual number of bytes that were + * converted from the Base64 encoding. + *

      This is the lowest level of the decoding methods with + * all possible parameters.

      + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @param options alphabet type is pulled from this (standard, url-safe, ordered) + * @return the number of decoded bytes converted + * @throws IllegalArgumentException if source or destination arrays are null, if srcOffset or + * destOffset are invalid or there is not enough room in the + * array. + * @since 1.3 + */ + private static int decode4to3( + byte[] source, int srcOffset, + byte[] destination, int destOffset, int options) { + + // Lots of error checking and exception throwing + if (source == null) { + throw new IllegalArgumentException("Source array was null."); + } // end if + if (destination == null) { + throw new IllegalArgumentException("Destination array was null."); + } // end if + if (srcOffset < 0 || srcOffset + 3 >= source.length) { + throw new IllegalArgumentException(String.format( + "Source array with length %d cannot have offset of %d and still process four bytes.", + source.length, srcOffset)); + } // end if + if (destOffset < 0 || destOffset + 2 >= destination.length) { + throw new IllegalArgumentException(String.format( + "Destination array with length %d cannot have offset of %d and still store three bytes.", + destination.length, destOffset)); + } // end if + + final byte[] DECODABET = getDecodabet(options); + + // Example: Dk== + if (source[srcOffset + 2] == EQUALS_SIGN) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12); + + destination[destOffset] = (byte) (outBuff >>> 16); + return 1; } - - /** - * Performs Base64 encoding on the raw ByteBuffer, - * writing it to the encoded CharBuffer. - * This is an experimental feature. Currently it does not - * pass along any options (such as {@link #DO_BREAK_LINES} - * or {@link #GZIP}. - * - * @param raw input buffer - * @param encoded output buffer - * @since 2.3 - */ - public static void encode( java.nio.ByteBuffer raw, java.nio.CharBuffer encoded ){ - byte[] raw3 = new byte[3]; - byte[] enc4 = new byte[4]; - - while( raw.hasRemaining() ){ - int rem = Math.min(3,raw.remaining()); - raw.get(raw3,0,rem); - Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); - for( int i = 0; i < 4; i++ ){ - encoded.put( (char)(enc4[i] & 0xFF) ); - } - } // end input remaining + // Example: DkL= + else if (source[srcOffset + 3] == EQUALS_SIGN) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) + | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6); + + destination[destOffset] = (byte) (outBuff >>> 16); + destination[destOffset + 1] = (byte) (outBuff >>> 8); + return 2; } - - - - /** - * Serializes an object and returns the Base64-encoded - * version of that serialized object. - * - *

      As of v 2.3, if the object - * cannot be serialized or there is another error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

      - * - * The object is not GZip-compressed before being encoded. - * - * @param serializableObject The object to encode - * @return The Base64-encoded object - * @throws java.io.IOException if there is an error - * @throws NullPointerException if serializedObject is null - * @since 1.4 - */ - public static String encodeObject( java.io.Serializable serializableObject ) - throws java.io.IOException { - return encodeObject( serializableObject, NO_OPTIONS ); - } // end encodeObject - - + // Example: DkLE + else { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) + // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) + | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6) + | ((DECODABET[source[srcOffset + 3]] & 0xFF)); + + destination[destOffset] = (byte) (outBuff >> 16); + destination[destOffset + 1] = (byte) (outBuff >> 8); + destination[destOffset + 2] = (byte) (outBuff); + + return 3; + } + } // end decodeToBytes + + + /** + * A {@link Base64.OutputStream} will write data to another + * java.io.OutputStream, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @since 1.3 + */ + public static class OutputStream extends java.io.FilterOutputStream { + + private boolean encode; + private int position; + private byte[] buffer; + private int bufferLength; + private int lineLength; + private boolean breakLines; + private byte[] b4; // Scratch used in a few places + private boolean suspendEncoding; + private int options; // Record for later + private byte[] decodabet; // Local copies to avoid extra method calls /** - * Serializes an object and returns the Base64-encoded - * version of that serialized object. - * - *

      As of v 2.3, if the object - * cannot be serialized or there is another error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

      - * - * The object is not GZip-compressed before being encoded. - *

      - * Example options:

      -     *   GZIP: gzip-compresses object before encoding it.
      -     *   DO_BREAK_LINES: break lines at 76 characters
      -     * 
      - *

      - * Example: encodeObject( myObj, Base64.GZIP ) or - *

      - * Example: encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES ) + * Constructs a {@link Base64.OutputStream} in ENCODE mode. * - * @param serializableObject The object to encode - * @param options Specified options - * @return The Base64-encoded object - * @see Base64#GZIP - * @see Base64#DO_BREAK_LINES - * @throws java.io.IOException if there is an error - * @since 2.0 - */ - public static String encodeObject( java.io.Serializable serializableObject, int options ) - throws java.io.IOException { - - if( serializableObject == null ){ - throw new NullPointerException( "Cannot serialize a null object." ); - } // end if: null - - // Streams - java.io.ByteArrayOutputStream baos = null; - java.io.OutputStream b64os = null; - java.util.zip.GZIPOutputStream gzos = null; - java.io.ObjectOutputStream oos = null; - - - try { - // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream - baos = new java.io.ByteArrayOutputStream(); - b64os = new Base64.OutputStream( baos, ENCODE | options ); - if( (options & GZIP) != 0 ){ - // Gzip - gzos = new java.util.zip.GZIPOutputStream(b64os); - oos = new java.io.ObjectOutputStream( gzos ); - } else { - // Not gzipped - oos = new java.io.ObjectOutputStream( b64os ); - } - oos.writeObject( serializableObject ); - } // end try - catch( java.io.IOException e ) { - // Catch it and then throw it immediately so that - // the finally{} block is called for cleanup. - throw e; - } // end catch - finally { - try{ oos.close(); } catch( Exception e ){} - try{ gzos.close(); } catch( Exception e ){} - try{ b64os.close(); } catch( Exception e ){} - try{ baos.close(); } catch( Exception e ){} - } // end finally - - // Return value according to relevant encoding. - try { - return new String( baos.toByteArray(), PREFERRED_ENCODING ); - } // end try - catch (java.io.UnsupportedEncodingException uue){ - // Fall back to some Java default - return new String( baos.toByteArray() ); - } // end catch - - } // end encode - - - - /** - * Encodes a byte array into Base64 notation. - * Does not GZip-compress data. - * - * @param source The data to convert - * @return The data in Base64-encoded form - * @throws NullPointerException if source array is null - * @since 1.4 + * @param out the java.io.OutputStream to which data will be written. + * @since 1.3 */ - public static String encodeBytes( byte[] source ) { - // Since we're not going to have the GZIP encoding turned on, - // we're not going to have an java.io.IOException thrown, so - // we should not force the user to have to catch it. - String encoded = null; - try { - encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); - } catch (java.io.IOException ex) { - assert false : ex.getMessage(); - } // end catch - assert encoded != null; - return encoded; - } // end encodeBytes - + public OutputStream(java.io.OutputStream out) { + this(out, ENCODE); + } // end constructor /** - * Encodes a byte array into Base64 notation. - *

      - * Example options:

      -     *   GZIP: gzip-compresses object before encoding it.
      -     *   DO_BREAK_LINES: break lines at 76 characters
      -     *     Note: Technically, this makes your encoding non-compliant.
      -     * 
      - *

      - * Example: encodeBytes( myData, Base64.GZIP ) or - *

      - * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) - * - * - *

      As of v 2.3, if there is an error with the GZIP stream, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

      - * - * - * @param source The data to convert - * @param options Specified options - * @return The Base64-encoded data as a String - * @see Base64#GZIP - * @see Base64#DO_BREAK_LINES - * @throws java.io.IOException if there is an error - * @throws NullPointerException if source array is null - * @since 2.0 - */ - public static String encodeBytes( byte[] source, int options ) throws java.io.IOException { - return encodeBytes( source, 0, source.length, options ); - } // end encodeBytes - - - /** - * Encodes a byte array into Base64 notation. - * Does not GZip-compress data. - * - *

      As of v 2.3, if there is an error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

      - * - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @return The Base64-encoded data as a String - * @throws NullPointerException if source array is null - * @throws IllegalArgumentException if source array, offset, or length are invalid - * @since 1.4 - */ - public static String encodeBytes( byte[] source, int off, int len ) { - // Since we're not going to have the GZIP encoding turned on, - // we're not going to have an java.io.IOException thrown, so - // we should not force the user to have to catch it. - String encoded = null; - try { - encoded = encodeBytes( source, off, len, NO_OPTIONS ); - } catch (java.io.IOException ex) { - assert false : ex.getMessage(); - } // end catch - assert encoded != null; - return encoded; - } // end encodeBytes - - - - /** - * Encodes a byte array into Base64 notation. + * Constructs a {@link Base64.OutputStream} in either ENCODE or DECODE mode. *

      - * Example options:

      -     *   GZIP: gzip-compresses object before encoding it.
      -     *   DO_BREAK_LINES: break lines at 76 characters
      -     *     Note: Technically, this makes your encoding non-compliant.
      +     * Valid options:
      +     *   ENCODE or DECODE: Encode or Decode as data is read.
      +     *   DO_BREAK_LINES: don't break lines at 76 characters
      +     *     (only meaningful when encoding)
            * 
      *

      - * Example: encodeBytes( myData, Base64.GZIP ) or - *

      - * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) - * - * - *

      As of v 2.3, if there is an error with the GZIP stream, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

      - * - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param options Specified options - * @return The Base64-encoded data as a String - * @see Base64#GZIP - * @see Base64#DO_BREAK_LINES - * @throws java.io.IOException if there is an error - * @throws NullPointerException if source array is null - * @throws IllegalArgumentException if source array, offset, or length are invalid - * @since 2.0 - */ - public static String encodeBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { - byte[] encoded = encodeBytesToBytes( source, off, len, options ); - - // Return value according to relevant encoding. - try { - return new String( encoded, PREFERRED_ENCODING ); - } // end try - catch (java.io.UnsupportedEncodingException uue) { - return new String( encoded ); - } // end catch - - } // end encodeBytes - - - - - /** - * Similar to {@link #encodeBytes(byte[])} but returns - * a byte array instead of instantiating a String. This is more efficient - * if you're working with I/O streams and have large data sets to encode. - * - * - * @param source The data to convert - * @return The Base64-encoded data as a byte[] (of ASCII characters) - * @throws NullPointerException if source array is null - * @since 2.3.1 - */ - public static byte[] encodeBytesToBytes( byte[] source ) { - byte[] encoded = null; - try { - encoded = encodeBytesToBytes( source, 0, source.length, Base64.NO_OPTIONS ); - } catch( java.io.IOException ex ) { - assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); - } - return encoded; - } - - - /** - * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns - * a byte array instead of instantiating a String. This is more efficient - * if you're working with I/O streams and have large data sets to encode. + * Example: new Base64.OutputStream( out, Base64.ENCODE ) * - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param options Specified options - * @return The Base64-encoded data as a String - * @see Base64#GZIP + * @param out the java.io.OutputStream to which data will be written. + * @param options Specified options. + * @see Base64#ENCODE * @see Base64#DO_BREAK_LINES - * @throws java.io.IOException if there is an error - * @throws NullPointerException if source array is null - * @throws IllegalArgumentException if source array, offset, or length are invalid - * @since 2.3.1 - */ - public static byte[] encodeBytesToBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { - - if( source == null ){ - throw new NullPointerException( "Cannot serialize a null array." ); - } // end if: null - - if( off < 0 ){ - throw new IllegalArgumentException( "Cannot have negative offset: " + off ); - } // end if: off < 0 - - if( len < 0 ){ - throw new IllegalArgumentException( "Cannot have length offset: " + len ); - } // end if: len < 0 - - if( off + len > source.length ){ - throw new IllegalArgumentException( - String.format( "Cannot have offset of %d and length of %d with array of length %d", off,len,source.length)); - } // end if: off < 0 - - - - // Compress? - if( (options & GZIP) != 0 ) { - java.io.ByteArrayOutputStream baos = null; - java.util.zip.GZIPOutputStream gzos = null; - Base64.OutputStream b64os = null; - - try { - // GZip -> Base64 -> ByteArray - baos = new java.io.ByteArrayOutputStream(); - b64os = new Base64.OutputStream( baos, ENCODE | options ); - gzos = new java.util.zip.GZIPOutputStream( b64os ); - - gzos.write( source, off, len ); - gzos.close(); - } // end try - catch( java.io.IOException e ) { - // Catch it and then throw it immediately so that - // the finally{} block is called for cleanup. - throw e; - } // end catch - finally { - try{ gzos.close(); } catch( Exception e ){} - try{ b64os.close(); } catch( Exception e ){} - try{ baos.close(); } catch( Exception e ){} - } // end finally - - return baos.toByteArray(); - } // end if: compress - - // Else, don't compress. Better not to use streams at all then. - else { - boolean breakLines = (options & DO_BREAK_LINES) != 0; - - //int len43 = len * 4 / 3; - //byte[] outBuff = new byte[ ( len43 ) // Main 4:3 - // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding - // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines - // Try to determine more precisely how big the array needs to be. - // If we get it right, we don't have to do an array copy, and - // we save a bunch of memory. - int encLen = ( len / 3 ) * 4 + ( len % 3 > 0 ? 4 : 0 ); // Bytes needed for actual encoding - if( breakLines ){ - encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters - } - byte[] outBuff = new byte[ encLen ]; - - - int d = 0; - int e = 0; - int len2 = len - 2; - int lineLength = 0; - for( ; d < len2; d+=3, e+=4 ) { - encode3to4( source, d+off, 3, outBuff, e, options ); - - lineLength += 4; - if( breakLines && lineLength >= MAX_LINE_LENGTH ) - { - outBuff[e+4] = NEW_LINE; - e++; - lineLength = 0; - } // end if: end of line - } // en dfor: each piece of array - - if( d < len ) { - encode3to4( source, d+off, len - d, outBuff, e, options ); - e += 4; - } // end if: some padding needed - - - // Only resize array if we didn't guess it right. - if( e <= outBuff.length - 1 ){ - // If breaking lines and the last byte falls right at - // the line length (76 bytes per line), there will be - // one extra byte, and the array will need to be resized. - // Not too bad of an estimate on array size, I'd say. - byte[] finalOut = new byte[e]; - System.arraycopy(outBuff,0, finalOut,0,e); - //System.err.println("Having to resize array from " + outBuff.length + " to " + e ); - return finalOut; - } else { - //System.err.println("No need to resize array."); - return outBuff; - } - - } // end else: don't compress - - } // end encodeBytesToBytes - - - - - -/* ******** D E C O D I N G M E T H O D S ******** */ - - - /** - * Decodes four bytes from array source - * and writes the resulting bytes (up to three of them) - * to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accomodate srcOffset + 4 for - * the source array or destOffset + 3 for - * the destination array. - * This method returns the actual number of bytes that - * were converted from the Base64 encoding. - *

      This is the lowest level of the decoding methods with - * all possible parameters.

      - * - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @param options alphabet type is pulled from this (standard, url-safe, ordered) - * @return the number of decoded bytes converted - * @throws NullPointerException if source or destination arrays are null - * @throws IllegalArgumentException if srcOffset or destOffset are invalid - * or there is not enough room in the array. * @since 1.3 */ - private static int decode4to3( - byte[] source, int srcOffset, - byte[] destination, int destOffset, int options ) { - - // Lots of error checking and exception throwing - if( source == null ){ - throw new NullPointerException( "Source array was null." ); - } // end if - if( destination == null ){ - throw new NullPointerException( "Destination array was null." ); - } // end if - if( srcOffset < 0 || srcOffset + 3 >= source.length ){ - throw new IllegalArgumentException( String.format( - "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) ); - } // end if - if( destOffset < 0 || destOffset +2 >= destination.length ){ - throw new IllegalArgumentException( String.format( - "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) ); - } // end if - - - byte[] DECODABET = getDecodabet( options ); - - // Example: Dk== - if( source[ srcOffset + 2] == EQUALS_SIGN ) { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); - - destination[ destOffset ] = (byte)( outBuff >>> 16 ); - return 1; - } - - // Example: DkL= - else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) - | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); - - destination[ destOffset ] = (byte)( outBuff >>> 16 ); - destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); - return 2; - } - - // Example: DkLE - else { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) - // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) - | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) - | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); - - - destination[ destOffset ] = (byte)( outBuff >> 16 ); - destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); - destination[ destOffset + 2 ] = (byte)( outBuff ); - - return 3; - } - } // end decodeToBytes - - - - + public OutputStream(java.io.OutputStream out, int options) { + super(out); + this.breakLines = (options & DO_BREAK_LINES) != 0; + this.encode = (options & ENCODE) != 0; + this.bufferLength = encode ? 3 : 4; + this.buffer = new byte[bufferLength]; + this.position = 0; + this.lineLength = 0; + this.suspendEncoding = false; + this.b4 = new byte[4]; + this.options = options; + this.decodabet = getDecodabet(options); + } // end constructor - /** - * Low-level access to decoding ASCII characters in - * the form of a byte array. Ignores GUNZIP option, if - * it's set. This is not generally a recommended method, - * although it is used internally as part of the decoding process. - * Special case: if len = 0, an empty array is returned. Still, - * if you need more speed and reduced memory footprint (and aren't - * gzipping), consider this method. - * - * @param source The Base64 encoded data - * @return decoded data - * @since 2.3.1 - */ - public static byte[] decode( byte[] source ) - throws java.io.IOException { - byte[] decoded = null; -// try { - decoded = decode( source, 0, source.length, Base64.NO_OPTIONS ); -// } catch( java.io.IOException ex ) { -// assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); -// } - return decoded; - } - - /** - * Low-level access to decoding ASCII characters in - * the form of a byte array. Ignores GUNZIP option, if - * it's set. This is not generally a recommended method, - * although it is used internally as part of the decoding process. - * Special case: if len = 0, an empty array is returned. Still, - * if you need more speed and reduced memory footprint (and aren't - * gzipping), consider this method. + * Writes the byte to the output stream after converting to/from Base64 notation. When encoding, + * bytes are buffered three at a time before the output stream actually gets a write() call. + * When decoding, bytes are buffered four at a time. * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @param options Can specify options such as alphabet type to use - * @return decoded data - * @throws java.io.IOException If bogus characters exist in source data + * @param theByte the byte to write * @since 1.3 */ - public static byte[] decode( byte[] source, int off, int len, int options ) - throws java.io.IOException { - - // Lots of error checking and exception throwing - if( source == null ){ - throw new NullPointerException( "Cannot decode null source array." ); - } // end if - if( off < 0 || off + len > source.length ){ - throw new IllegalArgumentException( String.format( - "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) ); - } // end if - - if( len == 0 ){ - return new byte[0]; - }else if( len < 4 ){ - throw new IllegalArgumentException( - "Base64-encoded string must have at least four characters, but length specified was " + len ); - } // end if - - byte[] DECODABET = getDecodabet( options ); - - int len34 = len * 3 / 4; // Estimate on array size - byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output - int outBuffPosn = 0; // Keep track of where we're writing - - byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space - int b4Posn = 0; // Keep track of four byte input buffer - int i = 0; // Source array counter - byte sbiDecode = 0; // Special value from DECODABET - - for( i = off; i < off+len; i++ ) { // Loop through source - - sbiDecode = DECODABET[ source[i]&0xFF ]; - - // White space, Equals sign, or legit Base64 character - // Note the values such as -5 and -9 in the - // DECODABETs at the top of the file. - if( sbiDecode >= WHITE_SPACE_ENC ) { - if( sbiDecode >= EQUALS_SIGN_ENC ) { - b4[ b4Posn++ ] = source[i]; // Save non-whitespace - if( b4Posn > 3 ) { // Time to decode? - outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); - b4Posn = 0; - - // If that was the equals sign, break out of 'for' loop - if( source[i] == EQUALS_SIGN ) { - break; - } // end if: equals sign - } // end if: quartet built - } // end if: equals sign or better - } // end if: white space, equals sign or better - else { - // There's a bad input character in the Base64 stream. - throw new java.io.IOException( String.format( - "Bad Base64 input character decimal %d in array position %d", ((int)source[i])&0xFF, i ) ); - } // end else: - } // each input character - - byte[] out = new byte[ outBuffPosn ]; - System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); - return out; - } // end decode - - - - - /** - * Decodes data from Base64 notation, automatically - * detecting gzip-compressed data and decompressing it. - * - * @param s the string to decode - * @return the decoded data - * @throws java.io.IOException If there is a problem - * @since 1.4 - */ - public static byte[] decode( String s ) throws java.io.IOException { - return decode( s, NO_OPTIONS ); - } + @Override + public void write(int theByte) + throws java.io.IOException { + // Encoding suspended? + if (suspendEncoding) { + this.out.write(theByte); + return; + } // end if: suspended + + // Encode? + if (encode) { + buffer[position++] = (byte) theByte; + if (position >= bufferLength) { // Enough to encode. + + this.out.write(encode3to4(b4, buffer, bufferLength, options)); + + lineLength += 4; + if (breakLines && lineLength >= MAX_LINE_LENGTH) { + this.out.write(NEW_LINE); + lineLength = 0; + } // end if: end of line + + position = 0; + } // end if: enough to output + } // end if: encoding + + // Else, Decoding + else { + // Meaningful Base64 character? + if (decodabet[theByte & 0x7f] > WHITE_SPACE_ENC) { + buffer[position++] = (byte) theByte; + if (position >= bufferLength) { // Enough to output. + + int len = Base64.decode4to3(buffer, 0, b4, 0, options); + out.write(b4, 0, len); + position = 0; + } // end if: enough to output + } // end if: meaningful base64 character + else if (decodabet[theByte & 0x7f] != WHITE_SPACE_ENC) { + throw new java.io.IOException("Invalid character in Base64 data."); + } // end else: not white space either + } // end else: decoding + } // end write - - /** - * Decodes data from Base64 notation, automatically - * detecting gzip-compressed data and decompressing it. + * Calls {@link #write(int)} repeatedly until len bytes are written. * - * @param s the string to decode - * @param options encode options such as URL_SAFE - * @return the decoded data - * @throws java.io.IOException if there is an error - * @throws NullPointerException if s is null - * @since 1.4 + * @param theBytes array from which to read bytes + * @param off offset for array + * @param len max number of bytes to read into array + * @since 1.3 */ - public static byte[] decode( String s, int options ) throws java.io.IOException { - - if( s == null ){ - throw new NullPointerException( "Input string was null." ); - } // end if - - byte[] bytes; - try { - bytes = s.getBytes( PREFERRED_ENCODING ); - } // end try - catch( java.io.UnsupportedEncodingException uee ) { - bytes = s.getBytes(); - } // end catch - // - - // Decode - bytes = decode( bytes, 0, bytes.length, options ); - - // Check to see if it's gzip-compressed - // GZIP Magic Two-Byte Number: 0x8b1f (35615) - boolean dontGunzip = (options & DONT_GUNZIP) != 0; - if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) { - - int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); - if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) { - java.io.ByteArrayInputStream bais = null; - java.util.zip.GZIPInputStream gzis = null; - java.io.ByteArrayOutputStream baos = null; - byte[] buffer = new byte[2048]; - int length = 0; - - try { - baos = new java.io.ByteArrayOutputStream(); - bais = new java.io.ByteArrayInputStream( bytes ); - gzis = new java.util.zip.GZIPInputStream( bais ); - - while( ( length = gzis.read( buffer ) ) >= 0 ) { - baos.write(buffer,0,length); - } // end while: reading input - - // No error? Get new bytes. - bytes = baos.toByteArray(); - - } // end try - catch( java.io.IOException e ) { - e.printStackTrace(); - // Just return originally-decoded bytes - } // end catch - finally { - try{ baos.close(); } catch( Exception e ){} - try{ gzis.close(); } catch( Exception e ){} - try{ bais.close(); } catch( Exception e ){} - } // end finally - - } // end if: gzipped - } // end if: bytes.length >= 2 - - return bytes; - } // end decode - + @Override + public void write(byte[] theBytes, int off, int len) + throws java.io.IOException { + // Encoding suspended? + if (suspendEncoding) { + this.out.write(theBytes, off, len); + return; + } // end if: suspended + for (int i = 0; i < len; i++) { + write(theBytes[off + i]); + } // end for: each byte written - /** - * Attempts to decode Base64 data and deserialize a Java - * Object within. Returns null if there was an error. - * - * @param encodedObject The Base64 data to decode - * @return The decoded and deserialized object - * @throws NullPointerException if encodedObject is null - * @throws java.io.IOException if there is a general error - * @throws ClassNotFoundException if the decoded object is of a - * class that cannot be found by the JVM - * @since 1.5 - */ - public static Object decodeToObject( String encodedObject ) - throws java.io.IOException, java.lang.ClassNotFoundException { - return decodeToObject(encodedObject,NO_OPTIONS,null); - } - + } // end write /** - * Attempts to decode Base64 data and deserialize a Java - * Object within. Returns null if there was an error. - * If loader is not null, it will be the class loader - * used when deserializing. - * - * @param encodedObject The Base64 data to decode - * @param options Various parameters related to decoding - * @param loader Optional class loader to use in deserializing classes. - * @return The decoded and deserialized object - * @throws NullPointerException if encodedObject is null - * @throws java.io.IOException if there is a general error - * @throws ClassNotFoundException if the decoded object is of a - * class that cannot be found by the JVM - * @since 2.3.4 - */ - public static Object decodeToObject( - String encodedObject, int options, final ClassLoader loader ) - throws java.io.IOException, java.lang.ClassNotFoundException { - - // Decode and gunzip if necessary - byte[] objBytes = decode( encodedObject, options ); - - java.io.ByteArrayInputStream bais = null; - java.io.ObjectInputStream ois = null; - Object obj = null; - - try { - bais = new java.io.ByteArrayInputStream( objBytes ); - - // If no custom class loader is provided, use Java's builtin OIS. - if( loader == null ){ - ois = new java.io.ObjectInputStream( bais ); - } // end if: no loader provided - - // Else make a customized object input stream that uses - // the provided class loader. - else { - ois = new java.io.ObjectInputStream(bais){ - @Override - public Class resolveClass(java.io.ObjectStreamClass streamClass) - throws java.io.IOException, ClassNotFoundException { - Class c = Class.forName(streamClass.getName(), false, loader); - if( c == null ){ - return super.resolveClass(streamClass); - } else { - return c; // Class loader knows of this class. - } // end else: not null - } // end resolveClass - }; // end ois - } // end else: no custom class loader - - obj = ois.readObject(); - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and throw in order to execute finally{} - } // end catch - catch( java.lang.ClassNotFoundException e ) { - throw e; // Catch and throw in order to execute finally{} - } // end catch - finally { - try{ bais.close(); } catch( Exception e ){} - try{ ois.close(); } catch( Exception e ){} - } // end finally - - return obj; - } // end decodeObject - - - - /** - * Convenience method for encoding data to a file. - * - *

      As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

      - * - * @param dataToEncode byte array of data to encode in base64 form - * @param filename Filename for saving encoded data - * @throws java.io.IOException if there is an error - * @throws NullPointerException if dataToEncode is null - * @since 2.1 - */ - public static void encodeToFile( byte[] dataToEncode, String filename ) - throws java.io.IOException { - - if( dataToEncode == null ){ - throw new NullPointerException( "Data to encode was null." ); - } // end iff - - Base64.OutputStream bos = null; - try { - bos = new Base64.OutputStream( - new java.io.FileOutputStream( filename ), Base64.ENCODE ); - bos.write( dataToEncode ); - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and throw to execute finally{} block - } // end catch: java.io.IOException - finally { - try{ bos.close(); } catch( Exception e ){} - } // end finally - - } // end encodeToFile - - - /** - * Convenience method for decoding data to a file. + * Method added by PHIL. [Thanks, PHIL. -Rob] This pads the buffer without closing the stream. * - *

      As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

      - * - * @param dataToDecode Base64-encoded data as a string - * @param filename Filename for saving decoded data - * @throws java.io.IOException if there is an error - * @since 2.1 + * @throws java.io.IOException if there's an error. */ - public static void decodeToFile( String dataToDecode, String filename ) - throws java.io.IOException { - - Base64.OutputStream bos = null; - try{ - bos = new Base64.OutputStream( - new java.io.FileOutputStream( filename ), Base64.DECODE ); - bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and throw to execute finally{} block - } // end catch: java.io.IOException - finally { - try{ bos.close(); } catch( Exception e ){} - } // end finally - - } // end decodeToFile - - - - - /** - * Convenience method for reading a base64-encoded - * file and decoding it. - * - *

      As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

      - * - * @param filename Filename for reading encoded data - * @return decoded byte array - * @throws java.io.IOException if there is an error - * @since 2.1 - */ - public static byte[] decodeFromFile( String filename ) - throws java.io.IOException { - - byte[] decodedData = null; - Base64.InputStream bis = null; - try - { - // Set up some useful variables - java.io.File file = new java.io.File( filename ); - byte[] buffer = null; - int length = 0; - int numBytes = 0; - - // Check for size of file - if( file.length() > Integer.MAX_VALUE ) - { - throw new java.io.IOException( "File is too big for this convenience method (" + file.length() + " bytes)." ); - } // end if: file too big for int index - buffer = new byte[ (int)file.length() ]; - - // Open a stream - bis = new Base64.InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream( file ) ), Base64.DECODE ); - - // Read until done - while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { - length += numBytes; - } // end while - - // Save in a variable to return - decodedData = new byte[ length ]; - System.arraycopy( buffer, 0, decodedData, 0, length ); - - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and release to execute finally{} - } // end catch: java.io.IOException - finally { - try{ bis.close(); } catch( Exception e) {} - } // end finally - - return decodedData; - } // end decodeFromFile - - - - /** - * Convenience method for reading a binary file - * and base64-encoding it. - * - *

      As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

      - * - * @param filename Filename for reading binary data - * @return base64-encoded string - * @throws java.io.IOException if there is an error - * @since 2.1 - */ - public static String encodeFromFile( String filename ) - throws java.io.IOException { - - String encodedData = null; - Base64.InputStream bis = null; - try - { - // Set up some useful variables - java.io.File file = new java.io.File( filename ); - byte[] buffer = new byte[ Math.max((int)(file.length() * 1.4+1),40) ]; // Need max() for math on small files (v2.2.1); Need +1 for a few corner cases (v2.3.5) - int length = 0; - int numBytes = 0; - - // Open a stream - bis = new Base64.InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream( file ) ), Base64.ENCODE ); - - // Read until done - while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { - length += numBytes; - } // end while - - // Save in a variable to return - encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); - - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and release to execute finally{} - } // end catch: java.io.IOException - finally { - try{ bis.close(); } catch( Exception e) {} - } // end finally - - return encodedData; - } // end encodeFromFile - - /** - * Reads infile and encodes it to outfile. - * - * @param infile Input file - * @param outfile Output file - * @throws java.io.IOException if there is an error - * @since 2.2 - */ - public static void encodeFileToFile( String infile, String outfile ) - throws java.io.IOException { - - String encoded = Base64.encodeFromFile( infile ); - java.io.OutputStream out = null; - try{ - out = new java.io.BufferedOutputStream( - new java.io.FileOutputStream( outfile ) ); - out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output. - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and release to execute finally{} - } // end catch - finally { - try { out.close(); } - catch( Exception ex ){} - } // end finally - } // end encodeFileToFile + public void flushBase64() throws java.io.IOException { + if (position > 0) { + if (encode) { + out.write(encode3to4(b4, buffer, position, options)); + position = 0; + } // end if: encoding + else { + throw new java.io.IOException("Base64 input not properly padded."); + } // end else: decoding + } // end if: buffer partially full + } // end flush /** - * Reads infile and decodes it to outfile. + * Flushes and closes (I think, in the superclass) the stream. * - * @param infile Input file - * @param outfile Output file - * @throws java.io.IOException if there is an error - * @since 2.2 - */ - public static void decodeFileToFile( String infile, String outfile ) - throws java.io.IOException { - - byte[] decoded = Base64.decodeFromFile( infile ); - java.io.OutputStream out = null; - try{ - out = new java.io.BufferedOutputStream( - new java.io.FileOutputStream( outfile ) ); - out.write( decoded ); - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and release to execute finally{} - } // end catch - finally { - try { out.close(); } - catch( Exception ex ){} - } // end finally - } // end decodeFileToFile - - - /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ - - - - /** - * A {@link Base64.InputStream} will read data from another - * java.io.InputStream, given in the constructor, - * and encode/decode to/from Base64 notation on the fly. - * - * @see Base64 - * @since 1.3 - */ - public static class InputStream extends java.io.FilterInputStream { - - private boolean encode; // Encoding or decoding - private int position; // Current position in the buffer - private byte[] buffer; // Small buffer holding converted data - private int bufferLength; // Length of buffer (3 or 4) - private int numSigBytes; // Number of meaningful bytes in the buffer - private int lineLength; - private boolean breakLines; // Break lines at less than 80 characters - private int options; // Record options used to create the stream. - private byte[] decodabet; // Local copies to avoid extra method calls - - - /** - * Constructs a {@link Base64.InputStream} in DECODE mode. - * - * @param in the java.io.InputStream from which to read data. - * @since 1.3 - */ - public InputStream( java.io.InputStream in ) { - this( in, DECODE ); - } // end constructor - - - /** - * Constructs a {@link Base64.InputStream} in - * either ENCODE or DECODE mode. - *

      - * Valid options:

      -         *   ENCODE or DECODE: Encode or Decode as data is read.
      -         *   DO_BREAK_LINES: break lines at 76 characters
      -         *     (only meaningful when encoding)
      -         * 
      - *

      - * Example: new Base64.InputStream( in, Base64.DECODE ) - * - * - * @param in the java.io.InputStream from which to read data. - * @param options Specified options - * @see Base64#ENCODE - * @see Base64#DECODE - * @see Base64#DO_BREAK_LINES - * @since 2.0 - */ - public InputStream( java.io.InputStream in, int options ) { - - super( in ); - this.options = options; // Record for later - this.breakLines = (options & DO_BREAK_LINES) > 0; - this.encode = (options & ENCODE) > 0; - this.bufferLength = encode ? 4 : 3; - this.buffer = new byte[ bufferLength ]; - this.position = -1; - this.lineLength = 0; - this.decodabet = getDecodabet(options); - } // end constructor - - /** - * Reads enough of the input stream to convert - * to/from Base64 and returns the next byte. - * - * @return next byte - * @since 1.3 - */ - @Override - public int read() throws java.io.IOException { - - // Do we need to get data? - if( position < 0 ) { - if( encode ) { - byte[] b3 = new byte[3]; - int numBinaryBytes = 0; - for( int i = 0; i < 3; i++ ) { - int b = in.read(); - - // If end of stream, b is -1. - if( b >= 0 ) { - b3[i] = (byte)b; - numBinaryBytes++; - } else { - break; // out of for loop - } // end else: end of stream - - } // end for: each needed input byte - - if( numBinaryBytes > 0 ) { - encode3to4( b3, 0, numBinaryBytes, buffer, 0, options ); - position = 0; - numSigBytes = 4; - } // end if: got data - else { - return -1; // Must be end of stream - } // end else - } // end if: encoding - - // Else decoding - else { - byte[] b4 = new byte[4]; - int i = 0; - for( i = 0; i < 4; i++ ) { - // Read four "meaningful" bytes: - int b = 0; - do{ b = in.read(); } - while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC ); - - if( b < 0 ) { - break; // Reads a -1 if end of stream - } // end if: end of stream - - b4[i] = (byte)b; - } // end for: each needed input byte - - if( i == 4 ) { - numSigBytes = decode4to3( b4, 0, buffer, 0, options ); - position = 0; - } // end if: got four characters - else if( i == 0 ){ - return -1; - } // end else if: also padded correctly - else { - // Must have broken out from above. - throw new java.io.IOException( "Improperly padded Base64 input." ); - } // end - - } // end else: decode - } // end else: get data - - // Got data? - if( position >= 0 ) { - // End of relevant data? - if( /*!encode &&*/ position >= numSigBytes ){ - return -1; - } // end if: got data - - if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) { - lineLength = 0; - return '\n'; - } // end if - else { - lineLength++; // This isn't important when decoding - // but throwing an extra "if" seems - // just as wasteful. - - int b = buffer[ position++ ]; - - if( position >= bufferLength ) { - position = -1; - } // end if: end - - return b & 0xFF; // This is how you "cast" a byte that's - // intended to be unsigned. - } // end else - } // end if: position >= 0 - - // Else error - else { - throw new java.io.IOException( "Error in Base64 code reading stream." ); - } // end else - } // end read - - - /** - * Calls {@link #read()} repeatedly until the end of stream - * is reached or len bytes are read. - * Returns number of bytes read into array or -1 if - * end of stream is encountered. - * - * @param dest array to hold values - * @param off offset for array - * @param len max number of bytes to read into array - * @return bytes read into array or -1 if end of stream is encountered. - * @since 1.3 - */ - @Override - public int read( byte[] dest, int off, int len ) - throws java.io.IOException { - int i; - int b; - for( i = 0; i < len; i++ ) { - b = read(); - - if( b >= 0 ) { - dest[off + i] = (byte) b; - } - else if( i == 0 ) { - return -1; - } - else { - break; // Out of 'for' loop - } // Out of 'for' loop - } // end for: each byte read - return i; - } // end read - - } // end inner class InputStream - - - - - - - /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ - - - - /** - * A {@link Base64.OutputStream} will write data to another - * java.io.OutputStream, given in the constructor, - * and encode/decode to/from Base64 notation on the fly. - * - * @see Base64 * @since 1.3 */ - public static class OutputStream extends java.io.FilterOutputStream { - - private boolean encode; - private int position; - private byte[] buffer; - private int bufferLength; - private int lineLength; - private boolean breakLines; - private byte[] b4; // Scratch used in a few places - private boolean suspendEncoding; - private int options; // Record for later - private byte[] decodabet; // Local copies to avoid extra method calls - - /** - * Constructs a {@link Base64.OutputStream} in ENCODE mode. - * - * @param out the java.io.OutputStream to which data will be written. - * @since 1.3 - */ - public OutputStream( java.io.OutputStream out ) { - this( out, ENCODE ); - } // end constructor - - - /** - * Constructs a {@link Base64.OutputStream} in - * either ENCODE or DECODE mode. - *

      - * Valid options:

      -         *   ENCODE or DECODE: Encode or Decode as data is read.
      -         *   DO_BREAK_LINES: don't break lines at 76 characters
      -         *     (only meaningful when encoding)
      -         * 
      - *

      - * Example: new Base64.OutputStream( out, Base64.ENCODE ) - * - * @param out the java.io.OutputStream to which data will be written. - * @param options Specified options. - * @see Base64#ENCODE - * @see Base64#DECODE - * @see Base64#DO_BREAK_LINES - * @since 1.3 - */ - public OutputStream( java.io.OutputStream out, int options ) { - super( out ); - this.breakLines = (options & DO_BREAK_LINES) != 0; - this.encode = (options & ENCODE) != 0; - this.bufferLength = encode ? 3 : 4; - this.buffer = new byte[ bufferLength ]; - this.position = 0; - this.lineLength = 0; - this.suspendEncoding = false; - this.b4 = new byte[4]; - this.options = options; - this.decodabet = getDecodabet(options); - } // end constructor - - - /** - * Writes the byte to the output stream after - * converting to/from Base64 notation. - * When encoding, bytes are buffered three - * at a time before the output stream actually - * gets a write() call. - * When decoding, bytes are buffered four - * at a time. - * - * @param theByte the byte to write - * @since 1.3 - */ - @Override - public void write(int theByte) - throws java.io.IOException { - // Encoding suspended? - if( suspendEncoding ) { - this.out.write( theByte ); - return; - } // end if: supsended - - // Encode? - if( encode ) { - buffer[ position++ ] = (byte)theByte; - if( position >= bufferLength ) { // Enough to encode. - - this.out.write( encode3to4( b4, buffer, bufferLength, options ) ); - - lineLength += 4; - if( breakLines && lineLength >= MAX_LINE_LENGTH ) { - this.out.write( NEW_LINE ); - lineLength = 0; - } // end if: end of line - - position = 0; - } // end if: enough to output - } // end if: encoding - - // Else, Decoding - else { - // Meaningful Base64 character? - if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC ) { - buffer[ position++ ] = (byte)theByte; - if( position >= bufferLength ) { // Enough to output. - - int len = Base64.decode4to3( buffer, 0, b4, 0, options ); - out.write( b4, 0, len ); - position = 0; - } // end if: enough to output - } // end if: meaningful base64 character - else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) { - throw new java.io.IOException( "Invalid character in Base64 data." ); - } // end else: not white space either - } // end else: decoding - } // end write - - - - /** - * Calls {@link #write(int)} repeatedly until len - * bytes are written. - * - * @param theBytes array from which to read bytes - * @param off offset for array - * @param len max number of bytes to read into array - * @since 1.3 - */ - @Override - public void write( byte[] theBytes, int off, int len ) - throws java.io.IOException { - // Encoding suspended? - if( suspendEncoding ) { - this.out.write( theBytes, off, len ); - return; - } // end if: supsended - - for( int i = 0; i < len; i++ ) { - write( theBytes[ off + i ] ); - } // end for: each byte written - - } // end write - - - - /** - * Method added by PHIL. [Thanks, PHIL. -Rob] - * This pads the buffer without closing the stream. - * @throws java.io.IOException if there's an error. - */ - public void flushBase64() throws java.io.IOException { - if( position > 0 ) { - if( encode ) { - out.write( encode3to4( b4, buffer, position, options ) ); - position = 0; - } // end if: encoding - else { - throw new java.io.IOException( "Base64 input not properly padded." ); - } // end else: decoding - } // end if: buffer partially full - - } // end flush - - - /** - * Flushes and closes (I think, in the superclass) the stream. - * - * @since 1.3 - */ - @Override - public void close() throws java.io.IOException { - // 1. Ensure that pending characters are written - flushBase64(); - - // 2. Actually close the stream - // Base class both flushes and closes. - super.close(); - - buffer = null; - out = null; - } // end close - - - - /** - * Suspends encoding of the stream. - * May be helpful if you need to embed a piece of - * base64-encoded data in a stream. - * - * @throws java.io.IOException if there's an error flushing - * @since 1.5.1 - */ - public void suspendEncoding() throws java.io.IOException { - flushBase64(); - this.suspendEncoding = true; - } // end suspendEncoding - - - /** - * Resumes encoding of the stream. - * May be helpful if you need to embed a piece of - * base64-encoded data in a stream. - * - * @since 1.5.1 - */ - public void resumeEncoding() { - this.suspendEncoding = false; - } // end resumeEncoding - - - - } // end inner class OutputStream - - + @Override + public void close() throws java.io.IOException { + // 1. Ensure that pending characters are written + flushBase64(); + + // 2. Actually close the stream + // Base class both flushes and closes. + super.close(); + + buffer = null; + out = null; + } // end close + } // end inner class OutputStream } // end class Base64 diff --git a/src/main/java/org/java_websocket/util/ByteBufferUtils.java b/src/main/java/org/java_websocket/util/ByteBufferUtils.java new file mode 100644 index 000000000..e43447984 --- /dev/null +++ b/src/main/java/org/java_websocket/util/ByteBufferUtils.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.util; + +import java.nio.ByteBuffer; + +/** + * Utility class for ByteBuffers + */ +public class ByteBufferUtils { + + /** + * Private constructor for static class + */ + private ByteBufferUtils() { + } + + /** + * Transfer from one ByteBuffer to another ByteBuffer + * + * @param source the ByteBuffer to copy from + * @param dest the ByteBuffer to copy to + * @return the number of transferred bytes + */ + public static int transferByteBuffer(ByteBuffer source, ByteBuffer dest) { + if (source == null || dest == null) { + throw new IllegalArgumentException(); + } + int fremain = source.remaining(); + int toremain = dest.remaining(); + if (fremain > toremain) { + int limit = Math.min(fremain, toremain); + source.limit(limit); + dest.put(source); + return limit; + } else { + dest.put(source); + return fremain; + } + } + + /** + * Get a ByteBuffer with zero capacity + * + * @return empty ByteBuffer + */ + public static ByteBuffer getEmptyByteBuffer() { + return ByteBuffer.allocate(0); + } +} diff --git a/src/main/java/org/java_websocket/util/Charsetfunctions.java b/src/main/java/org/java_websocket/util/Charsetfunctions.java index bd8ad2997..0cea88ec4 100644 --- a/src/main/java/org/java_websocket/util/Charsetfunctions.java +++ b/src/main/java/org/java_websocket/util/Charsetfunctions.java @@ -1,90 +1,154 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.util; -import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; -import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; - +import java.nio.charset.StandardCharsets; import org.java_websocket.exceptions.InvalidDataException; import org.java_websocket.framing.CloseFrame; public class Charsetfunctions { - public static CodingErrorAction codingErrorAction = CodingErrorAction.REPORT; + /** + * Private constructor for real static class + */ + private Charsetfunctions() { + } + + private static final CodingErrorAction codingErrorAction = CodingErrorAction.REPORT; + + /* + * @return UTF-8 encoding in bytes + */ + public static byte[] utf8Bytes(String s) { + return s.getBytes(StandardCharsets.UTF_8); + } + + /* + * @return ASCII encoding in bytes + */ + public static byte[] asciiBytes(String s) { + return s.getBytes(StandardCharsets.US_ASCII); + } - /* - * @return UTF-8 encoding in bytes - */ - public static byte[] utf8Bytes( String s ) { - try { - return s.getBytes( "UTF8" ); - } catch ( UnsupportedEncodingException e ) { - throw new RuntimeException( e ); - } - } + public static String stringAscii(byte[] bytes) { + return stringAscii(bytes, 0, bytes.length); + } - /* - * @return ASCII encoding in bytes - */ - public static byte[] asciiBytes( String s ) { - try { - return s.getBytes( "ASCII" ); - } catch ( UnsupportedEncodingException e ) { - throw new RuntimeException( e ); - } - } + public static String stringAscii(byte[] bytes, int offset, int length) { + return new String(bytes, offset, length, StandardCharsets.US_ASCII); + } - public static String stringAscii( byte[] bytes ) { - return stringAscii( bytes, 0, bytes.length ); - } - - public static String stringAscii( byte[] bytes, int offset, int length ){ - try { - return new String( bytes, offset, length, "ASCII" ); - } catch ( UnsupportedEncodingException e ) { - throw new RuntimeException( e ); - } - } + public static String stringUtf8(byte[] bytes) throws InvalidDataException { + return stringUtf8(ByteBuffer.wrap(bytes)); + } - public static String stringUtf8( byte[] bytes ) throws InvalidDataException { - return stringUtf8( ByteBuffer.wrap( bytes ) ); - } + public static String stringUtf8(ByteBuffer bytes) throws InvalidDataException { + CharsetDecoder decode = StandardCharsets.UTF_8.newDecoder(); + decode.onMalformedInput(codingErrorAction); + decode.onUnmappableCharacter(codingErrorAction); + String s; + try { + bytes.mark(); + s = decode.decode(bytes).toString(); + bytes.reset(); + } catch (CharacterCodingException e) { + throw new InvalidDataException(CloseFrame.NO_UTF8, e); + } + return s; + } - /*public static String stringUtf8( byte[] bytes, int off, int length ) throws InvalidDataException { - CharsetDecoder decode = Charset.forName( "UTF8" ).newDecoder(); - decode.onMalformedInput( codingErrorAction ); - decode.onUnmappableCharacter( codingErrorAction ); - //decode.replaceWith( "X" ); - String s; - try { - s = decode.decode( ByteBuffer.wrap( bytes, off, length ) ).toString(); - } catch ( CharacterCodingException e ) { - throw new InvalidDataException( CloseFrame.NO_UTF8, e ); - } - return s; - }*/ + /** + * Implementation of the "Flexible and Economical UTF-8 Decoder" algorithm by Björn Höhrmann + * (http://bjoern.hoehrmann.de/utf-8/decoder/dfa/) + */ + private static final int[] utf8d = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, // 00..1f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, // 20..3f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, // 40..5f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, // 60..7f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, // 80..9f + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, // a0..bf + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, // c0..df + 0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // e0..ef + 0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // f0..ff + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, + 1, // s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, + 1, // s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, + 1, // s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + // s7..s8 + }; - public static String stringUtf8( ByteBuffer bytes ) throws InvalidDataException { - CharsetDecoder decode = Charset.forName( "UTF8" ).newDecoder(); - decode.onMalformedInput( codingErrorAction ); - decode.onUnmappableCharacter( codingErrorAction ); - // decode.replaceWith( "X" ); - String s; - try { - bytes.mark(); - s = decode.decode( bytes ).toString(); - bytes.reset(); - } catch ( CharacterCodingException e ) { - throw new InvalidDataException( CloseFrame.NO_UTF8, e ); - } - return s; - } + /** + * Check if the provided BytebBuffer contains a valid utf8 encoded string. + *

      + * Using the algorithm "Flexible and Economical UTF-8 Decoder" by Björn Höhrmann + * (http://bjoern.hoehrmann.de/utf-8/decoder/dfa/) + * + * @param data the ByteBuffer + * @param off offset (for performance reasons) + * @return does the ByteBuffer contain a valid utf8 encoded string + */ + public static boolean isValidUTF8(ByteBuffer data, int off) { + int len = data.remaining(); + if (len < off) { + return false; + } + int state = 0; + for (int i = off; i < len; ++i) { + state = utf8d[256 + (state << 4) + utf8d[(0xff & data.get(i))]]; + if (state == 1) { + return false; + } + } + return true; + } - public static void main( String[] args ) throws InvalidDataException { - stringUtf8( utf8Bytes( "\0" ) ); - stringAscii( asciiBytes( "\0" ) ); - } + /** + * Calling isValidUTF8 with offset 0 + * + * @param data the ByteBuffer + * @return does the ByteBuffer contain a valid utf8 encoded string + */ + public static boolean isValidUTF8(ByteBuffer data) { + return isValidUTF8(data, 0); + } } diff --git a/src/main/java/org/java_websocket/util/NamedThreadFactory.java b/src/main/java/org/java_websocket/util/NamedThreadFactory.java new file mode 100644 index 000000000..19091c01c --- /dev/null +++ b/src/main/java/org/java_websocket/util/NamedThreadFactory.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.util; + +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +public class NamedThreadFactory implements ThreadFactory { + + private final ThreadFactory defaultThreadFactory = Executors.defaultThreadFactory(); + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String threadPrefix; + private final boolean daemon; + + public NamedThreadFactory(String threadPrefix) { + this.threadPrefix = threadPrefix; + this.daemon = false; + } + + public NamedThreadFactory(String threadPrefix, boolean daemon) { + this.threadPrefix = threadPrefix; + this.daemon = daemon; + } + + @Override + public Thread newThread(Runnable runnable) { + Thread thread = defaultThreadFactory.newThread(runnable); + thread.setDaemon(daemon); + thread.setName(threadPrefix + "-" + threadNumber); + return thread; + } +} diff --git a/src/main/java/org/java_websocket/util/package-info.java b/src/main/java/org/java_websocket/util/package-info.java new file mode 100644 index 000000000..af4d1afe2 --- /dev/null +++ b/src/main/java/org/java_websocket/util/package-info.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +/** + * This package encapsulates the utility classes. + */ +package org.java_websocket.util; \ No newline at end of file diff --git a/src/main/java9/module-info.java b/src/main/java9/module-info.java new file mode 100644 index 000000000..35ad67c89 --- /dev/null +++ b/src/main/java9/module-info.java @@ -0,0 +1,19 @@ +/** + * This module implements a barebones WebSocket server and client. + */ +module org.java_websocket { + requires transitive org.slf4j; + + exports org.java_websocket; + exports org.java_websocket.client; + exports org.java_websocket.drafts; + exports org.java_websocket.enums; + exports org.java_websocket.exceptions; + exports org.java_websocket.extensions; + exports org.java_websocket.extensions.permessage_deflate; + exports org.java_websocket.framing; + exports org.java_websocket.handshake; + exports org.java_websocket.interfaces; + exports org.java_websocket.protocols; + exports org.java_websocket.server; +} diff --git a/src/test/java/org/java_websocket/AutobahnClientScenario.java b/src/test/java/org/java_websocket/AutobahnClientScenario.java deleted file mode 100644 index bbc422f8c..000000000 --- a/src/test/java/org/java_websocket/AutobahnClientScenario.java +++ /dev/null @@ -1,236 +0,0 @@ -package org.java_websocket; - -import cucumber.annotation.After; -import cucumber.annotation.en.Given; -import cucumber.annotation.en.Then; -import cucumber.annotation.en.When; -import org.java_websocket.client.WebSocketClient; -import org.java_websocket.drafts.Draft; -import org.java_websocket.handshake.ClientHandshake; -import org.java_websocket.handshake.ServerHandshake; -import org.java_websocket.server.WebSocketServer; -import org.junit.Assert; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.UnknownHostException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.CountDownLatch; - -public class AutobahnClientScenario { - - private class AutobahnServer extends WebSocketServer { - - public AutobahnServer(int port, Draft d) throws UnknownHostException { - super(new InetSocketAddress(port), Collections.singletonList(d)); - } - - @Override - public void onOpen(WebSocket conn, ClientHandshake handshake) { - //throw new UnsupportedOperationException("Not supported yet."); - } - - @Override - public void onClose(WebSocket conn, int code, String reason, boolean remote) { - //throw new UnsupportedOperationException("Not supported yet."); - } - - @Override - public void onMessage(WebSocket conn, String message) { - //throw new UnsupportedOperationException("Not supported yet."); - } - - @Override - public void onError(WebSocket conn, Exception ex) { - //throw new UnsupportedOperationException("Not supported yet."); - } - - } - - private class AutobahnClient extends WebSocketClient { - - private final CountDownLatch connectionOpenedLatch; - private final Map openHandShakeFields; - private String message; - - public AutobahnClient(Draft draft, URI uri) { - super(uri, draft); - connectionOpenedLatch = new CountDownLatch(1); - openHandShakeFields = new HashMap(); - } - - @Override - public void onOpen(ServerHandshake handshakedata) { - Iterator it = handshakedata.iterateHttpFields(); - while(it.hasNext()) { - String key = it.next(); - System.out.printf("%s %s%n", key, handshakedata.getFieldValue(key)); // TODO Remove this - openHandShakeFields.put(key, handshakedata.getFieldValue(key)); - } - connectionOpenedLatch.countDown(); - } - - @Override - public void onMessage(String message) { - // TODO Test message receiving - } - - @Override - public void onClose(int code, String reason, boolean remote) { - // TODO Check connection closing - } - - @Override - public void onError(Exception ex) { - // TODO Check error handling - ex.printStackTrace(); - connectionOpenedLatch.countDown(); - } - - } - - private static Draft getDraft(int number) { - Exception exception; - try { - return (Draft) Class.forName("org.java_websocket.drafts.Draft_" + number).newInstance(); - } catch(InstantiationException e) { - exception = e; - } catch(IllegalAccessException e) { - exception = e; - } catch(ClassNotFoundException e) { - exception = e; - } - throw new RuntimeException(exception); - } - - private String protocol; - private String host; - private Integer port; - private String query; - private Draft draft; - - private AutobahnServer autobahnServer; - - @Given("^the Autobahn Server is running using Draft_(\\d+) on port (\\d+)$") - public void startAutobahnServer(int draft, int port) throws UnknownHostException { - autobahnServer = new AutobahnServer(port, getDraft(draft)); - autobahnServer.start(); - } - - @Given("^protocol is (.+)$") - public void createProtocol(String protocol) { - this.protocol = protocol; - } - - @Given("^the host is (.+)$") - public void createHost(String host) { - this.host = host; - } - - @Given("^the port is (\\d+)$") - public void createPort(int port) { - this.port = port; - } - - @Given("^the query string is (.+)$") - public void createQuery(String query) { - this.query = query; - } - - @Given("^the draft is Draft_(\\d+)") - public void createDraft(int draft) { - this.draft = getDraft(draft); - } - - private AutobahnClient autobahnClient; - - @When("^the client connects to the server$") - public void connectToServer() { - URI uri; - try { - uri = new URI(this.protocol, null, this.host, this.port, null, this.query, null); - } catch(URISyntaxException e) { - throw new RuntimeException(e); - } - - System.out.println(uri); - autobahnClient = new AutobahnClient(this.draft, uri); - try { - autobahnClient.connectBlocking(); - autobahnClient.connectionOpenedLatch.await(); - } catch(InterruptedException e) { - Assert.assertTrue(e.getMessage(), false); - e.printStackTrace(); - } - } - - @Then("^the server response should contain (.+)$") - public void checkMethod(String method) { - // TODO Implement check - //assertTrue(method.contains("GET")); - } - - @Then("^the response's query should contain (.+)$") - public void checkQuery(String query) { - // TODO Implement check - //assertTrue(query.contains(this.query)); - } - - @Then("^the response's http version should contain (.+)$") - public void checkHttpVersion(String httpversion) { - // TODO Implement check - //assertTrue(.contains("HTTP/" + major + "." + minor)); - } - - @Then("^the response's handshake should contain (.+)$") - public void checkHandshake(String handshake) { - Assert.assertEquals(handshake, autobahnClient.openHandShakeFields.get("Connection")); - } - - @Then("^the response's host should contain (.+)$") - public void checkHost(String host) { - // TODO Implement check - //assertTrue(host.contains(this.host)); - } - - @Then("^the response's websocket key should contain (.+)$") - public void checkWebSocketKey(String websocketKey) { - // TODO Implement check - //Assert.assertTrue(autobahnClient.openHandShakeFields.containsKey(websocketKey)); - //assertTrue(websocketKey.contains("Sec-WebSocket-Key:")); - } - - @Then("^the response's websocket version should contain (.+)$") - public void checkWebSocketVersion(String websocketVersion) { - // TODO Implement check - //assertTrue(websocketVersion.contains("Sec-WebSocket-Version:")); - } - - @Then("^the response's upgraded protocol should contain (.+)$") - public void checkUpgradedProtocol(String upgradedProtocol) { - Assert.assertEquals(upgradedProtocol, autobahnClient.openHandShakeFields.get("Upgrade")); - } - - @After - public void cleanup() { - try { - autobahnClient.closeBlocking(); - } catch(InterruptedException e) { - e.printStackTrace(); - } - - try { - autobahnServer.stop(); - } catch(IOException e) { - e.printStackTrace(); - } catch(InterruptedException e) { - e.printStackTrace(); - } - } - -} diff --git a/src/test/java/org/java_websocket/AutobahnClientTest.java b/src/test/java/org/java_websocket/AutobahnClientTest.java deleted file mode 100644 index 600054066..000000000 --- a/src/test/java/org/java_websocket/AutobahnClientTest.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.java_websocket; - -import org.junit.runner.RunWith; - -import cucumber.junit.Cucumber; - -@RunWith(Cucumber.class) -public class AutobahnClientTest { - -} diff --git a/src/test/java/org/java_websocket/client/AttachmentTest.java b/src/test/java/org/java_websocket/client/AttachmentTest.java new file mode 100644 index 000000000..217bdf1b3 --- /dev/null +++ b/src/test/java/org/java_websocket/client/AttachmentTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.client; + + +import java.net.URI; +import java.net.URISyntaxException; +import org.java_websocket.handshake.ServerHandshake; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class AttachmentTest { + + @Test + public void testDefaultValue() throws URISyntaxException { + WebSocketClient client = new WebSocketClient(new URI("ws://localhost")) { + @Override + public void onOpen(ServerHandshake handshakedata) { + + } + + @Override + public void onMessage(String message) { + + } + + @Override + public void onClose(int code, String reason, boolean remote) { + + } + + @Override + public void onError(Exception ex) { + + } + }; + assertNull(client.getAttachment()); + } + + @Test + public void testSetter() throws URISyntaxException { + WebSocketClient client = new WebSocketClient(new URI("ws://localhost")) { + @Override + public void onOpen(ServerHandshake handshakedata) { + + } + + @Override + public void onMessage(String message) { + + } + + @Override + public void onClose(int code, String reason, boolean remote) { + + } + + @Override + public void onError(Exception ex) { + + } + }; + assertNull(client.getAttachment()); + client.setAttachment(client); + assertEquals(client.getAttachment(), client); + client.setAttachment(null); + assertNull(client.getAttachment()); + } +} diff --git a/src/test/java/org/java_websocket/client/ConnectBlockingTest.java b/src/test/java/org/java_websocket/client/ConnectBlockingTest.java new file mode 100644 index 000000000..bb4741043 --- /dev/null +++ b/src/test/java/org/java_websocket/client/ConnectBlockingTest.java @@ -0,0 +1,78 @@ +package org.java_websocket.client; + +import java.io.IOException; +import java.net.*; +import java.util.Set; +import java.util.concurrent.*; + +import org.java_websocket.WebSocket; +import org.java_websocket.handshake.*; +import org.java_websocket.client.*; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.java_websocket.enums.ReadyState; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import static org.junit.jupiter.api.Assertions.*; + +public class ConnectBlockingTest { + + @Test + @Timeout(1000) + public void test_ConnectBlockingCleanup() throws Throwable { + + Set threadSet1 = Thread.getAllStackTraces().keySet(); + final CountDownLatch ready = new CountDownLatch(1); + final CountDownLatch accepted = new CountDownLatch(1); + + final int port = SocketUtil.getAvailablePort(); + + /* TCP server which listens to a port, but does not answer handshake */ + Thread server = new Thread(new Runnable() { + @Override + public void run() { + try { + ServerSocket serverSocket = new ServerSocket(port); + serverSocket.setReuseAddress(true); + ready.countDown(); + Socket clientSocket = serverSocket.accept(); + accepted.countDown(); + } catch (Throwable t) { + assertInstanceOf(InterruptedException.class, t); + } + } + }); + server.start(); + ready.await(); + + WebSocketClient client = new WebSocketClient(URI.create("ws://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshake) { + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onMessage(String message) { + } + + @Override + public void onError(Exception ex) { + ex.printStackTrace(); + } + }; + boolean connected = client.connectBlocking(100, TimeUnit.MILLISECONDS); + assertEquals( 0, accepted.getCount(), "TCP socket should have been accepted"); + assertFalse(connected, "WebSocket should not be connected (as server didn't send handshake)"); + + server.interrupt(); + server.join(); + + Set threadSet2 = Thread.getAllStackTraces().keySet(); + assertEquals(threadSet1, threadSet2, "no threads left over"); + assertTrue(client.getReadyState() == ReadyState.CLOSED || client.getReadyState() == ReadyState.NOT_YET_CONNECTED, "WebSocket is in closed state"); + } +} diff --git a/src/test/java/org/java_websocket/client/HeadersTest.java b/src/test/java/org/java_websocket/client/HeadersTest.java new file mode 100644 index 000000000..8d60cf005 --- /dev/null +++ b/src/test/java/org/java_websocket/client/HeadersTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.client; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; +import org.java_websocket.handshake.ServerHandshake; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class HeadersTest { + + @Test + public void testHttpHeaders() throws URISyntaxException { + Map httpHeaders = new HashMap(); + httpHeaders.put("Cache-Control", "only-if-cached"); + httpHeaders.put("Keep-Alive", "1000"); + + WebSocketClient client = new WebSocketClient(new URI("ws://localhost"), httpHeaders) { + @Override + public void onOpen(ServerHandshake handshakedata) { + + } + + @Override + public void onMessage(String message) { + + } + + @Override + public void onClose(int code, String reason, boolean remote) { + + } + + @Override + public void onError(Exception ex) { + + } + }; + + assertEquals("only-if-cached", client.removeHeader("Cache-Control")); + assertEquals("1000", client.removeHeader("Keep-Alive")); + } + + @Test + public void test_Add_RemoveHeaders() throws URISyntaxException { + Map httpHeaders = null; + WebSocketClient client = new WebSocketClient(new URI("ws://localhost"), httpHeaders) { + @Override + public void onOpen(ServerHandshake handshakedata) { + + } + + @Override + public void onMessage(String message) { + + } + + @Override + public void onClose(int code, String reason, boolean remote) { + + } + + @Override + public void onError(Exception ex) { + + } + }; + client.addHeader("Cache-Control", "only-if-cached"); + assertEquals("only-if-cached", client.removeHeader("Cache-Control")); + assertNull(client.removeHeader("Cache-Control")); + + client.addHeader("Cache-Control", "only-if-cached"); + client.clearHeaders(); + assertNull(client.removeHeader("Cache-Control")); + } + + @Test + public void testGetURI() throws URISyntaxException { + WebSocketClient client = new WebSocketClient(new URI("ws://localhost")) { + @Override + public void onOpen(ServerHandshake handshakedata) { + + } + + @Override + public void onMessage(String message) { + + } + + @Override + public void onClose(int code, String reason, boolean remote) { + + } + + @Override + public void onError(Exception ex) { + + } + }; + String actualURI = client.getURI().getScheme() + "://" + client.getURI().getHost(); + + assertEquals("ws://localhost", actualURI); + } +} diff --git a/src/test/java/org/java_websocket/client/SchemaCheckTest.java b/src/test/java/org/java_websocket/client/SchemaCheckTest.java new file mode 100644 index 000000000..af36e5c66 --- /dev/null +++ b/src/test/java/org/java_websocket/client/SchemaCheckTest.java @@ -0,0 +1,86 @@ +package org.java_websocket.client; + + +import java.net.URI; +import java.net.URISyntaxException; + +import org.java_websocket.handshake.ServerHandshake; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class SchemaCheckTest { + + @Test + public void testSchemaCheck() throws URISyntaxException { + final String[] invalidCase = { + "http://localhost:80", + "http://localhost:81", + "http://localhost", + "https://localhost:443", + "https://localhost:444", + "https://localhost", + "any://localhost", + "any://localhost:82", + }; + final Exception[] exs = new Exception[invalidCase.length]; + for (int i = 0; i < invalidCase.length; i++) { + final int finalI = i; + new WebSocketClient(new URI(invalidCase[finalI])) { + @Override + public void onOpen(ServerHandshake handshakedata) { + + } + + @Override + public void onMessage(String message) { + + } + + @Override + public void onClose(int code, String reason, boolean remote) { + + } + + @Override + public void onError(Exception ex) { + exs[finalI] = ex; + } + }.run(); + } + for (Exception exception : exs) { + assertInstanceOf(IllegalArgumentException.class, exception); + } + final String[] validCase = { + "ws://localhost", + "ws://localhost:80", + "ws://localhost:81", + "wss://localhost", + "wss://localhost:443", + "wss://localhost:444" + }; + for (String s : validCase) { + new WebSocketClient(new URI(s)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + + } + + @Override + public void onMessage(String message) { + + } + + @Override + public void onClose(int code, String reason, boolean remote) { + + } + + @Override + public void onError(Exception ex) { + assertFalse(ex instanceof IllegalArgumentException); + } + }.run(); + } + } +} diff --git a/src/test/java/org/java_websocket/drafts/Draft_6455Test.java b/src/test/java/org/java_websocket/drafts/Draft_6455Test.java new file mode 100644 index 000000000..41850b32e --- /dev/null +++ b/src/test/java/org/java_websocket/drafts/Draft_6455Test.java @@ -0,0 +1,573 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.drafts; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.java_websocket.enums.CloseHandshakeType; +import org.java_websocket.enums.HandshakeState; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.extensions.DefaultExtension; +import org.java_websocket.extensions.IExtension; +import org.java_websocket.framing.BinaryFrame; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.TextFrame; +import org.java_websocket.handshake.HandshakeImpl1Client; +import org.java_websocket.handshake.HandshakeImpl1Server; +import org.java_websocket.protocols.IProtocol; +import org.java_websocket.protocols.Protocol; +import org.java_websocket.util.Charsetfunctions; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class Draft_6455Test { + + HandshakeImpl1Client handshakedataProtocolExtension; + HandshakeImpl1Client handshakedataProtocol; + HandshakeImpl1Client handshakedataExtension; + HandshakeImpl1Client handshakedata; + + public Draft_6455Test() { + handshakedataProtocolExtension = new HandshakeImpl1Client(); + handshakedataProtocolExtension.put("Upgrade", "websocket"); + handshakedataProtocolExtension.put("Connection", "Upgrade"); + handshakedataProtocolExtension.put("Sec-WebSocket-Version", "13"); + handshakedataProtocolExtension.put("Sec-WebSocket-Extension", "permessage-deflate"); + handshakedataProtocolExtension.put("Sec-WebSocket-Protocol", "chat, test"); + handshakedataProtocol = new HandshakeImpl1Client(); + handshakedataProtocol.put("Upgrade", "websocket"); + handshakedataProtocol.put("Connection", "Upgrade"); + handshakedataProtocol.put("Sec-WebSocket-Version", "13"); + handshakedataProtocol.put("Sec-WebSocket-Protocol", "chat, test"); + handshakedataExtension = new HandshakeImpl1Client(); + handshakedataExtension.put("Upgrade", "websocket"); + handshakedataExtension.put("Connection", "Upgrade"); + handshakedataExtension.put("Sec-WebSocket-Version", "13"); + handshakedataExtension.put("Sec-WebSocket-Extension", "permessage-deflate"); + handshakedata = new HandshakeImpl1Client(); + handshakedata.put("Upgrade", "websocket"); + handshakedata.put("Connection", "Upgrade"); + handshakedata.put("Sec-WebSocket-Version", "13"); + } + + @Test + public void testConstructor() throws Exception { + try { + Draft_6455 draft_6455 = new Draft_6455(null, null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException e) { + //Fine + } + try { + Draft_6455 draft_6455 = new Draft_6455(Collections.emptyList(), null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException e) { + //Fine + } + try { + Draft_6455 draft_6455 = new Draft_6455(null, Collections.emptyList()); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException e) { + //Fine + } + try { + Draft_6455 draft_6455 = new Draft_6455(Collections.emptyList(), + Collections.emptyList(), -1); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException e) { + //Fine + } + try { + Draft_6455 draft_6455 = new Draft_6455(Collections.emptyList(), + Collections.emptyList(), 0); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException e) { + //Fine + } + Draft_6455 draft_6455 = new Draft_6455(Collections.emptyList(), + Collections.emptyList()); + assertEquals(1, draft_6455.getKnownExtensions().size()); + assertEquals(0, draft_6455.getKnownProtocols().size()); + } + + @Test + public void testGetExtension() throws Exception { + Draft_6455 draft_6455 = new Draft_6455(); + assertNotNull(draft_6455.getExtension()); + assert (draft_6455.getExtension() instanceof DefaultExtension); + } + + @Test + public void testGetKnownExtensions() throws Exception { + Draft_6455 draft_6455 = new Draft_6455(); + assertEquals(1, draft_6455.getKnownExtensions().size()); + draft_6455 = new Draft_6455(new DefaultExtension()); + assertEquals(1, draft_6455.getKnownExtensions().size()); + draft_6455 = new Draft_6455(new TestExtension()); + assertEquals(2, draft_6455.getKnownExtensions().size()); + } + + @Test + public void testGetProtocol() throws Exception { + Draft_6455 draft_6455 = new Draft_6455(Collections.emptyList(), + Collections.emptyList()); + assertNull(draft_6455.getProtocol()); + draft_6455.acceptHandshakeAsServer(handshakedataProtocolExtension); + assertNull(draft_6455.getProtocol()); + draft_6455 = new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat"))); + assertNull(draft_6455.getProtocol()); + draft_6455.acceptHandshakeAsServer(handshakedataProtocolExtension); + assertNotNull(draft_6455.getProtocol()); + } + + @Test + public void testGetKnownProtocols() throws Exception { + Draft_6455 draft_6455 = new Draft_6455(); + assertEquals(1, draft_6455.getKnownProtocols().size()); + draft_6455 = new Draft_6455(Collections.emptyList(), + Collections.emptyList()); + assertEquals(0, draft_6455.getKnownProtocols().size()); + draft_6455 = new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat"))); + assertEquals(1, draft_6455.getKnownProtocols().size()); + ArrayList protocols = new ArrayList(); + protocols.add(new Protocol("chat")); + protocols.add(new Protocol("test")); + draft_6455 = new Draft_6455(Collections.emptyList(), protocols); + assertEquals(2, draft_6455.getKnownProtocols().size()); + } + + @Test + public void testCopyInstance() throws Exception { + Draft_6455 draft_6455 = new Draft_6455( + Collections.singletonList(new TestExtension()), + Collections.singletonList(new Protocol("chat"))); + Draft_6455 draftCopy = (Draft_6455) draft_6455.copyInstance(); + draft_6455.acceptHandshakeAsServer(handshakedataProtocolExtension); + assertNotEquals(draft_6455, draftCopy); + assertEquals(draft_6455.getKnownProtocols(), draftCopy.getKnownProtocols()); + assertEquals(draft_6455.getKnownExtensions(), draftCopy.getKnownExtensions()); + assertNotEquals(draft_6455.getProtocol(), draftCopy.getProtocol()); + assertNotEquals(draft_6455.getExtension(), draftCopy.getExtension()); + } + + @Test + public void testReset() throws Exception { + Draft_6455 draft_6455 = new Draft_6455( + Collections.singletonList(new TestExtension()), 100); + draft_6455.acceptHandshakeAsServer(handshakedataProtocolExtension); + List extensionList = new ArrayList(draft_6455.getKnownExtensions()); + List protocolList = new ArrayList(draft_6455.getKnownProtocols()); + draft_6455.reset(); + //Protocol and extension should be reset + assertEquals(new DefaultExtension(), draft_6455.getExtension()); + assertNull(draft_6455.getProtocol()); + assertEquals(extensionList, draft_6455.getKnownExtensions()); + assertEquals(protocolList, draft_6455.getKnownProtocols()); + } + + @Test + public void testGetCloseHandshakeType() throws Exception { + Draft_6455 draft_6455 = new Draft_6455(); + assertEquals(CloseHandshakeType.TWOWAY, draft_6455.getCloseHandshakeType()); + } + + @Test + public void testToString() throws Exception { + Draft_6455 draft_6455 = new Draft_6455(); + assertEquals("Draft_6455 extension: DefaultExtension max frame size: 2147483647", + draft_6455.toString()); + draft_6455.acceptHandshakeAsServer(handshakedataProtocolExtension); + assertEquals("Draft_6455 extension: DefaultExtension protocol: max frame size: 2147483647", + draft_6455.toString()); + draft_6455 = new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat"))); + assertEquals("Draft_6455 extension: DefaultExtension max frame size: 2147483647", + draft_6455.toString()); + draft_6455.acceptHandshakeAsServer(handshakedataProtocolExtension); + assertEquals("Draft_6455 extension: DefaultExtension protocol: chat max frame size: 2147483647", + draft_6455.toString()); + draft_6455 = new Draft_6455(Collections.singletonList(new TestExtension()), + Collections.singletonList(new Protocol("chat"))); + assertEquals("Draft_6455 extension: DefaultExtension max frame size: 2147483647", + draft_6455.toString()); + draft_6455.acceptHandshakeAsServer(handshakedataProtocolExtension); + assertEquals("Draft_6455 extension: TestExtension protocol: chat max frame size: 2147483647", + draft_6455.toString()); + draft_6455 = new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat")), 10); + assertEquals("Draft_6455 extension: DefaultExtension max frame size: 10", + draft_6455.toString()); + draft_6455.acceptHandshakeAsServer(handshakedataProtocolExtension); + assertEquals("Draft_6455 extension: DefaultExtension protocol: chat max frame size: 10", + draft_6455.toString()); + } + + @Test + public void testEquals() throws Exception { + Draft draft0 = new Draft_6455(); + Draft draft1 = draft0.copyInstance(); + assertEquals(draft0, draft1); + Draft draft2 = new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat"))); + Draft draft3 = draft2.copyInstance(); + assertEquals(draft2, draft3); + assertEquals(draft0, draft2); + //unequal for draft2 due to a provided protocol + draft2.acceptHandshakeAsServer(handshakedataProtocolExtension); + draft1.acceptHandshakeAsServer(handshakedataProtocolExtension); + assertNotEquals(draft2, draft3); + assertNotEquals(draft0, draft2); + assertNotEquals(draft0, draft1); + draft2 = draft2.copyInstance(); + draft1 = draft1.copyInstance(); + //unequal for draft draft2 due to a provided protocol + draft2.acceptHandshakeAsServer(handshakedataProtocol); + draft1.acceptHandshakeAsServer(handshakedataProtocol); + assertNotEquals(draft2, draft3); + assertNotEquals(draft0, draft2); + assertNotEquals(draft0, draft1); + draft2 = draft2.copyInstance(); + draft1 = draft1.copyInstance(); + //unequal for draft draft0 due to a provided protocol (no protocol) + draft2.acceptHandshakeAsServer(handshakedataExtension); + draft1.acceptHandshakeAsServer(handshakedataExtension); + assertEquals(draft2, draft3); + assertEquals(draft0, draft2); + assertNotEquals(draft0, draft1); + draft2 = draft2.copyInstance(); + draft1 = draft1.copyInstance(); + //unequal for draft draft0 due to a provided protocol (no protocol) + draft2.acceptHandshakeAsServer(handshakedata); + draft1.acceptHandshakeAsServer(handshakedata); + assertEquals(draft2, draft3); + assertEquals(draft0, draft2); + assertNotEquals(draft0, draft1); + } + + @Test + public void testHashCode() throws Exception { + Draft draft0 = new Draft_6455(); + Draft draft1 = draft0.copyInstance(); + Draft draft2 = new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat"))); + Draft draft3 = draft2.copyInstance(); + assertEquals(draft2.hashCode(), draft3.hashCode()); + assertEquals(draft0.hashCode(), draft2.hashCode()); + assertEquals(draft0.hashCode(), draft1.hashCode()); + //Hashcode changes for draft2 due to a provided protocol + draft2.acceptHandshakeAsServer(handshakedataProtocolExtension); + draft1.acceptHandshakeAsServer(handshakedataProtocolExtension); + assertNotEquals(draft2.hashCode(), draft3.hashCode()); + assertNotEquals(draft0.hashCode(), draft2.hashCode()); + assertEquals(draft0.hashCode(), draft1.hashCode()); + draft2 = draft2.copyInstance(); + draft1 = draft1.copyInstance(); + //Hashcode changes for draft draft2 due to a provided protocol + draft2.acceptHandshakeAsServer(handshakedataProtocol); + draft1.acceptHandshakeAsServer(handshakedataProtocol); + assertNotEquals(draft2.hashCode(), draft3.hashCode()); + assertNotEquals(draft0.hashCode(), draft2.hashCode()); + assertEquals(draft0.hashCode(), draft1.hashCode()); + draft2 = draft2.copyInstance(); + draft1 = draft1.copyInstance(); + //Hashcode changes for draft draft0 due to a provided protocol (no protocol) + draft2.acceptHandshakeAsServer(handshakedataExtension); + draft1.acceptHandshakeAsServer(handshakedataExtension); + assertEquals(draft2.hashCode(), draft3.hashCode()); + assertEquals(draft0.hashCode(), draft2.hashCode()); + // THIS IS A DIFFERENCE BETWEEN equals and hashcode since the hashcode of an empty string = 0 + assertEquals(draft0.hashCode(), draft1.hashCode()); + draft2 = draft2.copyInstance(); + draft1 = draft1.copyInstance(); + //Hashcode changes for draft draft0 due to a provided protocol (no protocol) + draft2.acceptHandshakeAsServer(handshakedata); + draft1.acceptHandshakeAsServer(handshakedata); + assertEquals(draft2.hashCode(), draft3.hashCode()); + assertEquals(draft0.hashCode(), draft2.hashCode()); + // THIS IS A DIFFERENCE BETWEEN equals and hashcode since the hashcode of an empty string = 0 + assertEquals(draft0.hashCode(), draft1.hashCode()); + } + + @Test + public void acceptHandshakeAsServer() throws Exception { + Draft_6455 draft_6455 = new Draft_6455(); + assertEquals(HandshakeState.MATCHED, draft_6455.acceptHandshakeAsServer(handshakedata)); + assertEquals(HandshakeState.MATCHED, draft_6455.acceptHandshakeAsServer(handshakedataProtocol)); + assertEquals(HandshakeState.MATCHED, + draft_6455.acceptHandshakeAsServer(handshakedataExtension)); + assertEquals(HandshakeState.MATCHED, + draft_6455.acceptHandshakeAsServer(handshakedataProtocolExtension)); + draft_6455 = new Draft_6455(new TestExtension()); + assertEquals(HandshakeState.MATCHED, draft_6455.acceptHandshakeAsServer(handshakedata)); + assertEquals(HandshakeState.MATCHED, draft_6455.acceptHandshakeAsServer(handshakedataProtocol)); + assertEquals(HandshakeState.MATCHED, + draft_6455.acceptHandshakeAsServer(handshakedataExtension)); + assertEquals(HandshakeState.MATCHED, + draft_6455.acceptHandshakeAsServer(handshakedataProtocolExtension)); + draft_6455 = new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat"))); + assertEquals(HandshakeState.NOT_MATCHED, draft_6455.acceptHandshakeAsServer(handshakedata)); + assertEquals(HandshakeState.MATCHED, draft_6455.acceptHandshakeAsServer(handshakedataProtocol)); + assertEquals(HandshakeState.NOT_MATCHED, + draft_6455.acceptHandshakeAsServer(handshakedataExtension)); + assertEquals(HandshakeState.MATCHED, + draft_6455.acceptHandshakeAsServer(handshakedataProtocolExtension)); + ArrayList protocols = new ArrayList(); + protocols.add(new Protocol("chat")); + protocols.add(new Protocol("")); + draft_6455 = new Draft_6455(Collections.emptyList(), protocols); + assertEquals(HandshakeState.MATCHED, draft_6455.acceptHandshakeAsServer(handshakedata)); + assertEquals(HandshakeState.MATCHED, draft_6455.acceptHandshakeAsServer(handshakedataProtocol)); + assertEquals(HandshakeState.MATCHED, + draft_6455.acceptHandshakeAsServer(handshakedataExtension)); + assertEquals(HandshakeState.MATCHED, + draft_6455.acceptHandshakeAsServer(handshakedataProtocolExtension)); + } + + @Test + public void acceptHandshakeAsClient() throws Exception { + HandshakeImpl1Server response = new HandshakeImpl1Server(); + HandshakeImpl1Client request = new HandshakeImpl1Client(); + Draft_6455 draft_6455 = new Draft_6455(); + response.put("Upgrade", "websocket"); + assertEquals(HandshakeState.NOT_MATCHED, draft_6455.acceptHandshakeAsClient(request, response)); + response.put("Connection", "upgrade"); + assertEquals(HandshakeState.NOT_MATCHED, draft_6455.acceptHandshakeAsClient(request, response)); + response.put("Sec-WebSocket-Version", "13"); + assertEquals(HandshakeState.NOT_MATCHED, draft_6455.acceptHandshakeAsClient(request, response)); + request.put("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); + assertEquals(HandshakeState.NOT_MATCHED, draft_6455.acceptHandshakeAsClient(request, response)); + response.put("Sec-WebSocket-Accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="); + assertEquals(HandshakeState.MATCHED, draft_6455.acceptHandshakeAsClient(request, response)); + response.put("Sec-WebSocket-Protocol", "chat"); + assertEquals(HandshakeState.MATCHED, draft_6455.acceptHandshakeAsClient(request, response)); + draft_6455 = new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat"))); + assertEquals(HandshakeState.MATCHED, draft_6455.acceptHandshakeAsClient(request, response)); + ArrayList protocols = new ArrayList(); + protocols.add(new Protocol("")); + protocols.add(new Protocol("chat")); + draft_6455 = new Draft_6455(Collections.emptyList(), protocols); + assertEquals(HandshakeState.MATCHED, draft_6455.acceptHandshakeAsClient(request, response)); + draft_6455 = new Draft_6455(Collections.emptyList(), + Collections.emptyList()); + assertEquals(HandshakeState.NOT_MATCHED, draft_6455.acceptHandshakeAsClient(request, response)); + protocols.clear(); + protocols.add(new Protocol("chat3")); + protocols.add(new Protocol("3chat")); + draft_6455 = new Draft_6455(Collections.emptyList(), protocols); + assertEquals(HandshakeState.NOT_MATCHED, draft_6455.acceptHandshakeAsClient(request, response)); + } + + @Test + public void postProcessHandshakeRequestAsClient() throws Exception { + Draft_6455 draft_6455 = new Draft_6455(); + HandshakeImpl1Client request = new HandshakeImpl1Client(); + draft_6455.postProcessHandshakeRequestAsClient(request); + assertEquals("websocket", request.getFieldValue("Upgrade")); + assertEquals("Upgrade", request.getFieldValue("Connection")); + assertEquals("13", request.getFieldValue("Sec-WebSocket-Version")); + assertTrue(request.hasFieldValue("Sec-WebSocket-Key")); + assertFalse(request.hasFieldValue("Sec-WebSocket-Extensions")); + assertFalse(request.hasFieldValue("Sec-WebSocket-Protocol")); + ArrayList protocols = new ArrayList(); + protocols.add(new Protocol("chat")); + draft_6455 = new Draft_6455(Collections.emptyList(), protocols); + request = new HandshakeImpl1Client(); + draft_6455.postProcessHandshakeRequestAsClient(request); + assertFalse(request.hasFieldValue("Sec-WebSocket-Extensions")); + assertEquals("chat", request.getFieldValue("Sec-WebSocket-Protocol")); + protocols.add(new Protocol("chat2")); + draft_6455 = new Draft_6455(Collections.emptyList(), protocols); + request = new HandshakeImpl1Client(); + draft_6455.postProcessHandshakeRequestAsClient(request); + assertFalse(request.hasFieldValue("Sec-WebSocket-Extensions")); + assertEquals("chat, chat2", request.getFieldValue("Sec-WebSocket-Protocol")); + protocols.clear(); + protocols.add(new Protocol("")); + draft_6455 = new Draft_6455(Collections.emptyList(), protocols); + request = new HandshakeImpl1Client(); + draft_6455.postProcessHandshakeRequestAsClient(request); + assertFalse(request.hasFieldValue("Sec-WebSocket-Extensions")); + assertFalse(request.hasFieldValue("Sec-WebSocket-Protocol")); + } + + @Test + public void postProcessHandshakeResponseAsServer() throws Exception { + Draft_6455 draft_6455 = new Draft_6455(); + HandshakeImpl1Server response = new HandshakeImpl1Server(); + HandshakeImpl1Client request = new HandshakeImpl1Client(); + request.put("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); + request.put("Connection", "upgrade"); + draft_6455.postProcessHandshakeResponseAsServer(request, response); + assertTrue(response.hasFieldValue("Date")); + assertTrue(response.hasFieldValue("Sec-WebSocket-Accept")); + assertEquals("Web Socket Protocol Handshake", response.getHttpStatusMessage()); + assertEquals("TooTallNate Java-WebSocket", response.getFieldValue("Server")); + assertEquals("upgrade", response.getFieldValue("Connection")); + assertEquals("websocket", response.getFieldValue("Upgrade")); + assertFalse(response.hasFieldValue("Sec-WebSocket-Protocol")); + response = new HandshakeImpl1Server(); + draft_6455.acceptHandshakeAsServer(handshakedata); + draft_6455.postProcessHandshakeResponseAsServer(request, response); + assertFalse(response.hasFieldValue("Sec-WebSocket-Protocol")); + assertFalse(response.hasFieldValue("Sec-WebSocket-Extensions")); + response = new HandshakeImpl1Server(); + draft_6455.acceptHandshakeAsServer(handshakedataProtocol); + draft_6455.postProcessHandshakeResponseAsServer(request, response); + assertFalse(response.hasFieldValue("Sec-WebSocket-Protocol")); + assertFalse(response.hasFieldValue("Sec-WebSocket-Extensions")); + response = new HandshakeImpl1Server(); + draft_6455.acceptHandshakeAsServer(handshakedataExtension); + draft_6455.postProcessHandshakeResponseAsServer(request, response); + assertFalse(response.hasFieldValue("Sec-WebSocket-Protocol")); + assertFalse(response.hasFieldValue("Sec-WebSocket-Extensions")); + response = new HandshakeImpl1Server(); + draft_6455.acceptHandshakeAsServer(handshakedataProtocolExtension); + draft_6455.postProcessHandshakeResponseAsServer(request, response); + assertFalse(response.hasFieldValue("Sec-WebSocket-Protocol")); + assertFalse(response.hasFieldValue("Sec-WebSocket-Extensions")); + response = new HandshakeImpl1Server(); + draft_6455 = new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat"))); + draft_6455.acceptHandshakeAsServer(handshakedataProtocol); + draft_6455.postProcessHandshakeResponseAsServer(request, response); + assertEquals("chat", response.getFieldValue("Sec-WebSocket-Protocol")); + assertFalse(response.hasFieldValue("Sec-WebSocket-Extensions")); + response = new HandshakeImpl1Server(); + draft_6455.reset(); + draft_6455.acceptHandshakeAsServer(handshakedataExtension); + draft_6455.postProcessHandshakeResponseAsServer(request, response); + assertFalse(response.hasFieldValue("Sec-WebSocket-Protocol")); + assertFalse(response.hasFieldValue("Sec-WebSocket-Extensions")); + response = new HandshakeImpl1Server(); + draft_6455.reset(); + draft_6455.acceptHandshakeAsServer(handshakedataProtocolExtension); + draft_6455.postProcessHandshakeResponseAsServer(request, response); + assertEquals("chat", response.getFieldValue("Sec-WebSocket-Protocol")); + assertFalse(response.hasFieldValue("Sec-WebSocket-Extensions")); + ArrayList protocols = new ArrayList(); + protocols.add(new Protocol("test")); + protocols.add(new Protocol("chat")); + draft_6455 = new Draft_6455(Collections.emptyList(), protocols); + draft_6455.acceptHandshakeAsServer(handshakedataProtocol); + draft_6455.postProcessHandshakeResponseAsServer(request, response); + assertEquals("test", response.getFieldValue("Sec-WebSocket-Protocol")); + assertFalse(response.hasFieldValue("Sec-WebSocket-Extensions")); + response = new HandshakeImpl1Server(); + draft_6455.reset(); + draft_6455.acceptHandshakeAsServer(handshakedataExtension); + draft_6455.postProcessHandshakeResponseAsServer(request, response); + assertFalse(response.hasFieldValue("Sec-WebSocket-Protocol")); + assertFalse(response.hasFieldValue("Sec-WebSocket-Extensions")); + response = new HandshakeImpl1Server(); + draft_6455.reset(); + draft_6455.acceptHandshakeAsServer(handshakedataProtocolExtension); + draft_6455.postProcessHandshakeResponseAsServer(request, response); + assertEquals("test", response.getFieldValue("Sec-WebSocket-Protocol")); + assertFalse(response.hasFieldValue("Sec-WebSocket-Extensions")); + + // issue #1053 : check the exception - missing Sec-WebSocket-Key + response = new HandshakeImpl1Server(); + request = new HandshakeImpl1Client(); + draft_6455.reset(); + request.put("Connection", "upgrade"); + + try { + draft_6455.postProcessHandshakeResponseAsServer(request, response); + fail("InvalidHandshakeException should be thrown"); + } catch (InvalidHandshakeException e) { + + } + } + + + @Test + public void createFramesBinary() throws Exception { + Draft_6455 draft_6455 = new Draft_6455(); + BinaryFrame curframe = new BinaryFrame(); + ByteBuffer test0 = ByteBuffer.wrap("Test0".getBytes()); + curframe.setPayload(test0); + curframe.setTransferemasked(false); + List createdFrame = draft_6455.createFrames(test0, false); + assertEquals(1, createdFrame.size()); + assertEquals(curframe, createdFrame.get(0)); + curframe = new BinaryFrame(); + ByteBuffer test1 = ByteBuffer.wrap("Test1".getBytes()); + curframe.setPayload(test1); + curframe.setTransferemasked(true); + createdFrame = draft_6455.createFrames(test1, true); + assertEquals(1, createdFrame.size()); + assertEquals(curframe, createdFrame.get(0)); + } + + @Test + public void createFramesText() throws Exception { + Draft_6455 draft_6455 = new Draft_6455(); + TextFrame curframe = new TextFrame(); + curframe.setPayload(ByteBuffer.wrap(Charsetfunctions.utf8Bytes("Test0"))); + curframe.setTransferemasked(false); + List createdFrame = draft_6455.createFrames("Test0", false); + assertEquals(1, createdFrame.size()); + assertEquals(curframe, createdFrame.get(0)); + curframe = new TextFrame(); + curframe.setPayload(ByteBuffer.wrap(Charsetfunctions.utf8Bytes("Test0"))); + curframe.setTransferemasked(true); + createdFrame = draft_6455.createFrames("Test0", true); + assertEquals(1, createdFrame.size()); + assertEquals(curframe, createdFrame.get(0)); + } + + + private static class TestExtension extends DefaultExtension { + + @Override + public int hashCode() { + return getClass().hashCode(); + } + + @Override + public IExtension copyInstance() { + return new TestExtension(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + return getClass() == o.getClass(); + } + } +} diff --git a/src/test/java/org/java_websocket/example/AutobahnClientTest.java b/src/test/java/org/java_websocket/example/AutobahnClientTest.java index 0bb093ac4..5f726ddfe 100644 --- a/src/test/java/org/java_websocket/example/AutobahnClientTest.java +++ b/src/test/java/org/java_websocket/example/AutobahnClientTest.java @@ -1,3 +1,28 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.example; import java.io.BufferedReader; @@ -5,161 +30,150 @@ import java.io.InputStreamReader; import java.net.URI; import java.nio.ByteBuffer; - -import org.java_websocket.WebSocket; -import org.java_websocket.WebSocketImpl; import org.java_websocket.client.WebSocketClient; import org.java_websocket.drafts.Draft; -import org.java_websocket.drafts.Draft_17; -import org.java_websocket.framing.FrameBuilder; -import org.java_websocket.framing.Framedata; +import org.java_websocket.drafts.Draft_6455; import org.java_websocket.handshake.ServerHandshake; public class AutobahnClientTest extends WebSocketClient { - public AutobahnClientTest( Draft d , URI uri ) { - super( uri, d ); - } - /** - * @param args - */ - public static void main( String[] args ) { - System.out.println( "Testutility to profile/test this implementation using the Autobahn suit.\n" ); - System.out.println( "Type 'r ' to run a testcase. Example: r 1" ); - System.out.println( "Type 'r ' to run a testcase. Example: r 1 295" ); - System.out.println( "Type 'u' to update the test results." ); - System.out.println( "Type 'ex' to terminate the program." ); - System.out.println( "During sequences of cases the debugoutput will be turned of." ); - - System.out.println( "You can now enter in your commands:" ); - - try { - BufferedReader sysin = new BufferedReader( new InputStreamReader( System.in ) ); - - /*First of the thinks a programmer might want to change*/ - Draft d = new Draft_17(); - String clientname = "tootallnate/websocket"; - - String protocol = "ws"; - String host = "localhost"; - int port = 9001; - - String serverlocation = protocol + "://" + host + ":" + port; - String line = ""; - AutobahnClientTest e; - URI uri = null; - String perviousline = ""; - String nextline = null; - Integer start = null; - Integer end = null; - - while ( !line.contains( "ex" ) ) { - try { - if( nextline != null ) { - line = nextline; - nextline = null; - WebSocketImpl.DEBUG = false; - } else { - System.out.print( ">" ); - line = sysin.readLine(); - WebSocketImpl.DEBUG = true; - } - if( line.equals( "l" ) ) { - line = perviousline; - } - String[] spl = line.split( " " ); - if( line.startsWith( "r" ) ) { - if( spl.length == 3 ) { - start = new Integer( spl[ 1 ] ); - end = new Integer( spl[ 2 ] ); - } - if( start != null && end != null ) { - if( start > end ) { - start = null; - end = null; - } else { - nextline = "r " + start; - start++; - if( spl.length == 3 ) - continue; - } - } - uri = URI.create( serverlocation + "/runCase?case=" + spl[ 1 ] + "&agent=" + clientname ); - - } else if( line.startsWith( "u" ) ) { - WebSocketImpl.DEBUG = false; - uri = URI.create( serverlocation + "/updateReports?agent=" + clientname ); - } else if( line.startsWith( "d" ) ) { - try { - d = (Draft) Class.forName( "Draft_" + spl[ 1 ] ).getConstructor().newInstance(); - } catch ( Exception ex ) { - System.out.println( "Could not change draft" + ex ); - } - } - if( uri == null ) { - System.out.println( "Do not understand the input." ); - continue; - } - System.out.println( "//////////////////////Exec: " + uri.getQuery() ); - e = new AutobahnClientTest( d, uri ); - Thread t = new Thread( e ); - t.start(); - try { - t.join(); - - } catch ( InterruptedException e1 ) { - e1.printStackTrace(); - } finally { - e.close(); - } - } catch ( ArrayIndexOutOfBoundsException e1 ) { - System.out.println( "Bad Input r 1, u 1, d 10, ex" ); - } catch ( IllegalArgumentException e2 ) { - e2.printStackTrace(); - } - - } - } catch ( ArrayIndexOutOfBoundsException e ) { - System.out.println( "Missing server uri" ); - } catch ( IllegalArgumentException e ) { - e.printStackTrace(); - System.out.println( "URI should look like ws://localhost:8887 or wss://echo.websocket.org" ); - } catch ( IOException e ) { - e.printStackTrace(); // for System.in reader - } - System.exit( 0 ); - } - - @Override - public void onMessage( String message ) { - send( message ); - } - - @Override - public void onMessage( ByteBuffer blob ) { - getConnection().send( blob ); - } - - @Override - public void onError( Exception ex ) { - System.out.println( "Error: " ); - ex.printStackTrace(); - } - - @Override - public void onOpen( ServerHandshake handshake ) { - } - - @Override - public void onClose( int code, String reason, boolean remote ) { - System.out.println( "Closed: " + code + " " + reason ); - } - - @Override - public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { - FrameBuilder builder = (FrameBuilder) frame; - builder.setTransferemasked( true ); - getConnection().sendFrame( frame ); - } + public AutobahnClientTest(Draft d, URI uri) { + super(uri, d); + } + + /** + * @param args + */ + public static void main(String[] args) { + System.out + .println("Testutility to profile/test this implementation using the Autobahn suit.\n"); + System.out.println("Type 'r ' to run a testcase. Example: r 1"); + System.out.println( + "Type 'r ' to run a testcase. Example: r 1 295"); + System.out.println("Type 'u' to update the test results."); + System.out.println("Type 'ex' to terminate the program."); + System.out.println("During sequences of cases the debugoutput will be turned of."); + + System.out.println("You can now enter in your commands:"); + + try { + BufferedReader sysin = new BufferedReader(new InputStreamReader(System.in)); + + /*First of the thinks a programmer might want to change*/ + Draft d = new Draft_6455(); + String clientname = "tootallnate/websocket"; + + String protocol = "ws"; + String host = "localhost"; + int port = 9003; + + String serverlocation = protocol + "://" + host + ":" + port; + String line = ""; + AutobahnClientTest e; + URI uri = null; + String perviousline = ""; + String nextline = null; + Integer start = null; + Integer end = null; + + while (!line.contains("ex")) { + try { + if (nextline != null) { + line = nextline; + nextline = null; + } else { + System.out.print(">"); + line = sysin.readLine(); + } + if (line.equals("l")) { + line = perviousline; + } + String[] spl = line.split(" "); + if (line.startsWith("r")) { + if (spl.length == 3) { + start = Integer.parseInt(spl[1]); + end = Integer.parseInt(spl[2]); + } + if (start != null && end != null) { + if (start > end) { + start = null; + end = null; + } else { + nextline = "r " + start; + start++; + if (spl.length == 3) { + continue; + } + } + } + uri = URI.create(serverlocation + "/runCase?case=" + spl[1] + "&agent=" + clientname); + + } else if (line.startsWith("u")) { + uri = URI.create(serverlocation + "/updateReports?agent=" + clientname); + } else if (line.startsWith("d")) { + try { + d = (Draft) Class.forName("Draft_" + spl[1]).getConstructor().newInstance(); + } catch (Exception ex) { + System.out.println("Could not change draft" + ex); + } + } + if (uri == null) { + System.out.println("Do not understand the input."); + continue; + } + System.out.println("//////////////////////Exec: " + uri.getQuery()); + e = new AutobahnClientTest(d, uri); + Thread t = new Thread(e); + t.start(); + try { + t.join(); + + } catch (InterruptedException e1) { + e1.printStackTrace(); + } finally { + e.close(); + } + } catch (ArrayIndexOutOfBoundsException e1) { + System.out.println("Bad Input r 1, u 1, d 10, ex"); + } catch (IllegalArgumentException e2) { + e2.printStackTrace(); + } + + } + } catch (ArrayIndexOutOfBoundsException e) { + System.out.println("Missing server uri"); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + System.out.println("URI should look like ws://localhost:8887 or wss://echo.websocket.org"); + } catch (IOException e) { + e.printStackTrace(); // for System.in reader + } + System.exit(0); + } + + @Override + public void onMessage(String message) { + send(message); + } + + @Override + public void onMessage(ByteBuffer blob) { + getConnection().send(blob); + } + + @Override + public void onError(Exception ex) { + System.out.println("Error: "); + ex.printStackTrace(); + } + + @Override + public void onOpen(ServerHandshake handshake) { + } + + @Override + public void onClose(int code, String reason, boolean remote) { + System.out.println("Closed: " + code + " " + reason); + } } diff --git a/src/test/java/org/java_websocket/example/AutobahnSSLServerTest.java b/src/test/java/org/java_websocket/example/AutobahnSSLServerTest.java new file mode 100644 index 000000000..cc6f5ef7b --- /dev/null +++ b/src/test/java/org/java_websocket/example/AutobahnSSLServerTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.example; + +import java.io.File; +import java.io.FileInputStream; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.file.Paths; +import java.security.KeyStore; +import java.util.Collections; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import org.java_websocket.WebSocket; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.DefaultSSLWebSocketServerFactory; +import org.java_websocket.server.WebSocketServer; + +public class AutobahnSSLServerTest extends WebSocketServer { + + private static int counter = 0; + + public AutobahnSSLServerTest(int port, Draft d) throws UnknownHostException { + super(new InetSocketAddress(port), Collections.singletonList(d)); + } + + public AutobahnSSLServerTest(InetSocketAddress address, Draft d) { + super(address, Collections.singletonList(d)); + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + counter++; + System.out.println("///////////Opened connection number" + counter); + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + System.out.println("closed"); + } + + @Override + public void onError(WebSocket conn, Exception ex) { + System.out.println("Error:"); + ex.printStackTrace(); + } + + @Override + public void onStart() { + System.out.println("Server started!"); + } + + @Override + public void onMessage(WebSocket conn, String message) { + conn.send(message); + } + + @Override + public void onMessage(WebSocket conn, ByteBuffer blob) { + conn.send(blob); + } + + public static void main(String[] args) throws UnknownHostException { + int port; + try { + port = Integer.parseInt(args[0]); + } catch (Exception e) { + System.out.println("No port specified. Defaulting to 9003"); + port = 9003; + } + AutobahnSSLServerTest test = new AutobahnSSLServerTest(port, new Draft_6455()); + try { + // load up the key store + String STORETYPE = "JKS"; + String KEYSTORE = Paths.get("src", "test", "java", "org", "java_websocket", "keystore.jks") + .toString(); + String STOREPASSWORD = "storepassword"; + String KEYPASSWORD = "keypassword"; + + KeyStore ks = KeyStore.getInstance(STORETYPE); + File kf = new File(KEYSTORE); + ks.load(new FileInputStream(kf), STOREPASSWORD.toCharArray()); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, KEYPASSWORD.toCharArray()); + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(ks); + + SSLContext sslContext = null; + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + test.setWebSocketFactory(new DefaultSSLWebSocketServerFactory(sslContext)); + } catch (Exception e) { + e.printStackTrace(); + } + test.setConnectionLostTimeout(0); + test.start(); + } + +} diff --git a/src/test/java/org/java_websocket/example/AutobahnServerTest.java b/src/test/java/org/java_websocket/example/AutobahnServerTest.java index e77c637c8..b0b9bc847 100644 --- a/src/test/java/org/java_websocket/example/AutobahnServerTest.java +++ b/src/test/java/org/java_websocket/example/AutobahnServerTest.java @@ -1,74 +1,112 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + package org.java_websocket.example; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.Collections; - import org.java_websocket.WebSocket; -import org.java_websocket.WebSocketImpl; import org.java_websocket.drafts.Draft; -import org.java_websocket.drafts.Draft_17; -import org.java_websocket.framing.FrameBuilder; -import org.java_websocket.framing.Framedata; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.extensions.permessage_deflate.PerMessageDeflateExtension; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.server.WebSocketServer; public class AutobahnServerTest extends WebSocketServer { - private static int counter = 0; - - public AutobahnServerTest( int port , Draft d ) throws UnknownHostException { - super( new InetSocketAddress( port ), Collections.singletonList( d ) ); - } - - public AutobahnServerTest( InetSocketAddress address, Draft d ) { - super( address, Collections.singletonList( d ) ); - } - @Override - public void onOpen( WebSocket conn, ClientHandshake handshake ) { - counter++; - System.out.println( "///////////Opened connection number" + counter ); - } + private static int openCounter = 0; + private static int closeCounter = 0; + private int limit = Integer.MAX_VALUE; + + public AutobahnServerTest(int port, int limit, Draft d) throws UnknownHostException { + super(new InetSocketAddress(port), Collections.singletonList(d)); + this.limit = limit; + } + + public AutobahnServerTest(InetSocketAddress address, Draft d) { + super(address, Collections.singletonList(d)); + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + openCounter++; + System.out.println("///////////Opened connection number" + openCounter); + } - @Override - public void onClose( WebSocket conn, int code, String reason, boolean remote ) { - System.out.println( "closed" ); - } + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + closeCounter++; + System.out.println("closed"); + if (closeCounter >= limit) { + System.exit(0); + } + } - @Override - public void onError( WebSocket conn, Exception ex ) { - System.out.println( "Error:" ); - ex.printStackTrace(); - } + @Override + public void onError(WebSocket conn, Exception ex) { + System.out.println("Error:"); + ex.printStackTrace(); + } - @Override - public void onMessage( WebSocket conn, String message ) { - conn.send( message ); - } + @Override + public void onStart() { + System.out.println("Server started!"); + } - @Override - public void onMessage( WebSocket conn, ByteBuffer blob ) { - conn.send( blob ); - } + @Override + public void onMessage(WebSocket conn, String message) { + conn.send(message); + } - @Override - public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { - FrameBuilder builder = (FrameBuilder) frame; - builder.setTransferemasked( false ); - conn.sendFrame( frame ); - } + @Override + public void onMessage(WebSocket conn, ByteBuffer blob) { + conn.send(blob); + } - public static void main( String[] args ) throws UnknownHostException { - WebSocketImpl.DEBUG = false; - int port; - try { - port = new Integer( args[ 0 ] ); - } catch ( Exception e ) { - System.out.println( "No port specified. Defaulting to 9003" ); - port = 9003; - } - new AutobahnServerTest( port, new Draft_17() ).start(); - } + public static void main(String[] args) throws UnknownHostException { + int port, limit; + try { + port = Integer.parseInt(args[0]); + } catch (Exception e) { + System.out.println("No port specified. Defaulting to 9003"); + port = 9003; + } + try { + limit = Integer.parseInt(args[1]); + } catch (Exception e) { + System.out.println("No limit specified. Defaulting to MaxInteger"); + limit = Integer.MAX_VALUE; + } + PerMessageDeflateExtension perMessageDeflateExtension = new PerMessageDeflateExtension(); + perMessageDeflateExtension.setThreshold(0); + AutobahnServerTest test = new AutobahnServerTest(port, limit, + new Draft_6455(perMessageDeflateExtension)); + test.setConnectionLostTimeout(0); + test.start(); + } } diff --git a/src/test/java/org/java_websocket/exceptions/IncompleteExceptionTest.java b/src/test/java/org/java_websocket/exceptions/IncompleteExceptionTest.java new file mode 100644 index 000000000..de831c393 --- /dev/null +++ b/src/test/java/org/java_websocket/exceptions/IncompleteExceptionTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.exceptions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * JUnit Test for the IncompleteException class + */ +public class IncompleteExceptionTest { + + @Test + public void testConstructor() { + IncompleteException incompleteException = new IncompleteException(42); + assertEquals(42, incompleteException.getPreferredSize(), "The argument should be set"); + } +} diff --git a/src/test/java/org/java_websocket/exceptions/IncompleteHandshakeExceptionTest.java b/src/test/java/org/java_websocket/exceptions/IncompleteHandshakeExceptionTest.java new file mode 100644 index 000000000..0506c1530 --- /dev/null +++ b/src/test/java/org/java_websocket/exceptions/IncompleteHandshakeExceptionTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.exceptions; + + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * JUnit Test for the IncompleteHandshakeException class + */ +public class IncompleteHandshakeExceptionTest { + + @Test + public void testConstructor() { + IncompleteHandshakeException incompleteHandshakeException = new IncompleteHandshakeException( + 42); + assertEquals( 42, incompleteHandshakeException.getPreferredSize(), "The argument should be set"); + incompleteHandshakeException = new IncompleteHandshakeException(); + assertEquals(0, incompleteHandshakeException.getPreferredSize(), "The default has to be 0"); + } +} diff --git a/src/test/java/org/java_websocket/exceptions/InvalidDataExceptionTest.java b/src/test/java/org/java_websocket/exceptions/InvalidDataExceptionTest.java new file mode 100644 index 000000000..332f8fd22 --- /dev/null +++ b/src/test/java/org/java_websocket/exceptions/InvalidDataExceptionTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.exceptions; + + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * JUnit Test for the InvalidDataException class + */ +public class InvalidDataExceptionTest { + + @Test + public void testConstructor() { + InvalidDataException invalidDataException = new InvalidDataException(42); + assertEquals(42, invalidDataException.getCloseCode(), "The close code has to be the argument"); + invalidDataException = new InvalidDataException(42, "Message"); + assertEquals(42, invalidDataException.getCloseCode(), "The close code has to be the argument"); + assertEquals( "Message", + invalidDataException.getMessage(), "The message has to be the argument"); + Exception e = new Exception(); + invalidDataException = new InvalidDataException(42, "Message", e); + assertEquals( 42, invalidDataException.getCloseCode(), "The close code has to be the argument"); + assertEquals( "Message", + invalidDataException.getMessage(), "The message has to be the argument"); + assertEquals(e, invalidDataException.getCause(), "The throwable has to be the argument"); + invalidDataException = new InvalidDataException(42, e); + assertEquals(42, invalidDataException.getCloseCode(), "The close code has to be the argument"); + assertEquals(e, invalidDataException.getCause(), "The throwable has to be the argument"); + } +} diff --git a/src/test/java/org/java_websocket/exceptions/InvalidEncodingExceptionTest.java b/src/test/java/org/java_websocket/exceptions/InvalidEncodingExceptionTest.java new file mode 100644 index 000000000..2edb0ad31 --- /dev/null +++ b/src/test/java/org/java_websocket/exceptions/InvalidEncodingExceptionTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.exceptions; + +import org.junit.jupiter.api.Test; + +import java.io.UnsupportedEncodingException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * JUnit Test for the InvalidEncodingException class + */ +public class InvalidEncodingExceptionTest { + + @Test + public void testConstructor() { + UnsupportedEncodingException unsupportedEncodingException = new UnsupportedEncodingException(); + InvalidEncodingException invalidEncodingException = new InvalidEncodingException( + unsupportedEncodingException); + assertEquals(unsupportedEncodingException, + invalidEncodingException.getEncodingException(), "The argument has to be the provided exception"); + try { + invalidEncodingException = new InvalidEncodingException(null); + fail("IllegalArgumentException should be thrown"); + } catch (IllegalArgumentException e) { + //Null is not allowed + } + } +} diff --git a/src/test/java/org/java_websocket/exceptions/InvalidFrameExceptionTest.java b/src/test/java/org/java_websocket/exceptions/InvalidFrameExceptionTest.java new file mode 100644 index 000000000..359e6d69b --- /dev/null +++ b/src/test/java/org/java_websocket/exceptions/InvalidFrameExceptionTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.exceptions; + + +import org.java_websocket.framing.CloseFrame; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * JUnit Test for the InvalidFrameException class + */ +public class InvalidFrameExceptionTest { + + @Test + public void testConstructor() { + InvalidFrameException invalidFrameException = new InvalidFrameException(); + assertEquals( CloseFrame.PROTOCOL_ERROR, + invalidFrameException.getCloseCode(), "The close code has to be PROTOCOL_ERROR"); + invalidFrameException = new InvalidFrameException("Message"); + assertEquals(CloseFrame.PROTOCOL_ERROR, + invalidFrameException.getCloseCode(), "The close code has to be PROTOCOL_ERROR"); + assertEquals("Message", + invalidFrameException.getMessage(), "The message has to be the argument"); + Exception e = new Exception(); + invalidFrameException = new InvalidFrameException("Message", e); + assertEquals(CloseFrame.PROTOCOL_ERROR, + invalidFrameException.getCloseCode(), "The close code has to be PROTOCOL_ERROR"); + assertEquals("Message", + invalidFrameException.getMessage(), "The message has to be the argument"); + assertEquals(e, invalidFrameException.getCause(), "The throwable has to be the argument"); + invalidFrameException = new InvalidFrameException(e); + assertEquals(CloseFrame.PROTOCOL_ERROR, + invalidFrameException.getCloseCode(), "The close code has to be PROTOCOL_ERROR"); + assertEquals(e, invalidFrameException.getCause(), "The throwable has to be the argument"); + } + + @Test + public void testExtends() { + InvalidFrameException invalidFrameException = new InvalidFrameException(); + assertInstanceOf(InvalidDataException.class, invalidFrameException, "InvalidFrameException must extend InvalidDataException"); + } +} diff --git a/src/test/java/org/java_websocket/exceptions/InvalidHandshakeExceptionTest.java b/src/test/java/org/java_websocket/exceptions/InvalidHandshakeExceptionTest.java new file mode 100644 index 000000000..0b8863c0b --- /dev/null +++ b/src/test/java/org/java_websocket/exceptions/InvalidHandshakeExceptionTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.exceptions; + +import org.java_websocket.framing.CloseFrame; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * JUnit Test for the InvalidHandshakeException class + */ +public class InvalidHandshakeExceptionTest { + + @Test + public void testConstructor() { + InvalidHandshakeException invalidHandshakeException = new InvalidHandshakeException(); + assertEquals( CloseFrame.PROTOCOL_ERROR, + invalidHandshakeException.getCloseCode(), "The close code has to be PROTOCOL_ERROR"); + invalidHandshakeException = new InvalidHandshakeException("Message"); + assertEquals( CloseFrame.PROTOCOL_ERROR, + invalidHandshakeException.getCloseCode(), "The close code has to be PROTOCOL_ERROR"); + assertEquals( "Message", + invalidHandshakeException.getMessage(), "The message has to be the argument"); + Exception e = new Exception(); + invalidHandshakeException = new InvalidHandshakeException("Message", e); + assertEquals(CloseFrame.PROTOCOL_ERROR, + invalidHandshakeException.getCloseCode(), "The close code has to be PROTOCOL_ERROR"); + assertEquals( "Message", + invalidHandshakeException.getMessage(), "The message has to be the argument"); + assertEquals(e, invalidHandshakeException.getCause(), "The throwable has to be the argument"); + invalidHandshakeException = new InvalidHandshakeException(e); + assertEquals(CloseFrame.PROTOCOL_ERROR, + invalidHandshakeException.getCloseCode(), "The close code has to be PROTOCOL_ERROR"); + assertEquals(e, invalidHandshakeException.getCause(), "The throwable has to be the argument"); + + } + + @Test + public void testExtends() { + InvalidHandshakeException invalidHandshakeException = new InvalidHandshakeException(); + assertInstanceOf(InvalidDataException.class, invalidHandshakeException, "InvalidHandshakeException must extend InvalidDataException"); + } +} diff --git a/src/test/java/org/java_websocket/exceptions/LimitExceededExceptionTest.java b/src/test/java/org/java_websocket/exceptions/LimitExceededExceptionTest.java new file mode 100644 index 000000000..1677da7b0 --- /dev/null +++ b/src/test/java/org/java_websocket/exceptions/LimitExceededExceptionTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.exceptions; + +import org.java_websocket.framing.CloseFrame; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * JUnit Test for the InvalidEncodingException class + */ +public class LimitExceededExceptionTest { + + @Test + public void testConstructor() { + LimitExceededException limitExceededException = new LimitExceededException(); + assertEquals(CloseFrame.TOOBIG, + limitExceededException.getCloseCode(), "The close code has to be TOOBIG"); + assertNull(limitExceededException.getMessage(), "The message has to be empty"); + limitExceededException = new LimitExceededException("Message"); + assertEquals(CloseFrame.TOOBIG, + limitExceededException.getCloseCode(), "The close code has to be TOOBIG"); + assertEquals( "Message", + limitExceededException.getMessage(), "The message has to be the argument"); + } + + @Test + public void testExtends() { + LimitExceededException limitExceededException = new LimitExceededException(); + assertInstanceOf(InvalidDataException.class, limitExceededException, "LimitExceededException must extend InvalidDataException"); + } +} diff --git a/src/test/java/org/java_websocket/exceptions/NotSendableExceptionTest.java b/src/test/java/org/java_websocket/exceptions/NotSendableExceptionTest.java new file mode 100644 index 000000000..044e84d2c --- /dev/null +++ b/src/test/java/org/java_websocket/exceptions/NotSendableExceptionTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.exceptions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * JUnit Test for the NotSendableException class + */ +public class NotSendableExceptionTest { + + @Test + public void testConstructor() { + NotSendableException notSendableException = new NotSendableException("Message"); + assertEquals("Message", + notSendableException.getMessage(), "The message has to be the argument"); + Exception e = new Exception(); + notSendableException = new NotSendableException(e); + assertEquals( e, notSendableException.getCause(), "The throwable has to be the argument"); + notSendableException = new NotSendableException("Message", e); + assertEquals("Message", + notSendableException.getMessage(), "The message has to be the argument"); + assertEquals(e, notSendableException.getCause(), "The throwable has to be the argument"); + } +} diff --git a/src/test/java/org/java_websocket/exceptions/WebsocketNotConnectedExceptionTest.java b/src/test/java/org/java_websocket/exceptions/WebsocketNotConnectedExceptionTest.java new file mode 100644 index 000000000..3f21c4460 --- /dev/null +++ b/src/test/java/org/java_websocket/exceptions/WebsocketNotConnectedExceptionTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.exceptions; + + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * JUnit Test for the WebsocketNotConnectedException class + */ +public class WebsocketNotConnectedExceptionTest { + + @Test + public void testConstructor() { + WebsocketNotConnectedException websocketNotConnectedException = new WebsocketNotConnectedException(); + assertNotNull(websocketNotConnectedException); + } +} diff --git a/src/test/java/org/java_websocket/extensions/CompressionExtensionTest.java b/src/test/java/org/java_websocket/extensions/CompressionExtensionTest.java new file mode 100644 index 000000000..74b8e3fb1 --- /dev/null +++ b/src/test/java/org/java_websocket/extensions/CompressionExtensionTest.java @@ -0,0 +1,78 @@ +package org.java_websocket.extensions; + + +import org.java_websocket.framing.PingFrame; +import org.java_websocket.framing.TextFrame; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.fail; + +public class CompressionExtensionTest { + + + @Test + public void testIsFrameValid() { + CustomCompressionExtension customCompressionExtension = new CustomCompressionExtension(); + TextFrame textFrame = new TextFrame(); + try { + customCompressionExtension.isFrameValid(textFrame); + } catch (Exception e) { + fail("This frame is valid"); + } + textFrame.setRSV1(true); + try { + customCompressionExtension.isFrameValid(textFrame); + } catch (Exception e) { + fail("This frame is valid"); + } + textFrame.setRSV1(false); + textFrame.setRSV2(true); + try { + customCompressionExtension.isFrameValid(textFrame); + fail("This frame is not valid"); + } catch (Exception e) { + // + } + textFrame.setRSV2(false); + textFrame.setRSV3(true); + try { + customCompressionExtension.isFrameValid(textFrame); + fail("This frame is not valid"); + } catch (Exception e) { + // + } + PingFrame pingFrame = new PingFrame(); + try { + customCompressionExtension.isFrameValid(pingFrame); + } catch (Exception e) { + fail("This frame is valid"); + } + pingFrame.setRSV1(true); + try { + customCompressionExtension.isFrameValid(pingFrame); + fail("This frame is not valid"); + } catch (Exception e) { + // + } + pingFrame.setRSV1(false); + pingFrame.setRSV2(true); + try { + customCompressionExtension.isFrameValid(pingFrame); + fail("This frame is not valid"); + } catch (Exception e) { + // + } + pingFrame.setRSV2(false); + pingFrame.setRSV3(true); + try { + customCompressionExtension.isFrameValid(pingFrame); + fail("This frame is not valid"); + } catch (Exception e) { + // + } + } + + private static class CustomCompressionExtension extends CompressionExtension { + + } +} diff --git a/src/test/java/org/java_websocket/extensions/DefaultExtensionTest.java b/src/test/java/org/java_websocket/extensions/DefaultExtensionTest.java new file mode 100644 index 000000000..e4b29907d --- /dev/null +++ b/src/test/java/org/java_websocket/extensions/DefaultExtensionTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.extensions; + +import java.nio.ByteBuffer; + +import org.java_websocket.framing.BinaryFrame; +import org.java_websocket.framing.TextFrame; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class DefaultExtensionTest { + + @Test + public void testDecodeFrame() throws Exception { + DefaultExtension defaultExtension = new DefaultExtension(); + BinaryFrame binaryFrame = new BinaryFrame(); + binaryFrame.setPayload(ByteBuffer.wrap("test".getBytes())); + defaultExtension.decodeFrame(binaryFrame); + assertEquals(ByteBuffer.wrap("test".getBytes()), binaryFrame.getPayloadData()); + } + + @Test + public void testEncodeFrame() throws Exception { + DefaultExtension defaultExtension = new DefaultExtension(); + BinaryFrame binaryFrame = new BinaryFrame(); + binaryFrame.setPayload(ByteBuffer.wrap("test".getBytes())); + defaultExtension.encodeFrame(binaryFrame); + assertEquals(ByteBuffer.wrap("test".getBytes()), binaryFrame.getPayloadData()); + } + + @Test + public void testAcceptProvidedExtensionAsServer() throws Exception { + DefaultExtension defaultExtension = new DefaultExtension(); + assertTrue(defaultExtension.acceptProvidedExtensionAsServer("Test")); + assertTrue(defaultExtension.acceptProvidedExtensionAsServer("")); + assertTrue(defaultExtension.acceptProvidedExtensionAsServer("Test, ASDC, as, ad")); + assertTrue(defaultExtension.acceptProvidedExtensionAsServer("ASDC, as,ad")); + assertTrue(defaultExtension.acceptProvidedExtensionAsServer("permessage-deflate")); + } + + @Test + public void testAcceptProvidedExtensionAsClient() throws Exception { + DefaultExtension defaultExtension = new DefaultExtension(); + assertTrue(defaultExtension.acceptProvidedExtensionAsClient("Test")); + assertTrue(defaultExtension.acceptProvidedExtensionAsClient("")); + assertTrue(defaultExtension.acceptProvidedExtensionAsClient("Test, ASDC, as, ad")); + assertTrue(defaultExtension.acceptProvidedExtensionAsClient("ASDC, as,ad")); + assertTrue(defaultExtension.acceptProvidedExtensionAsClient("permessage-deflate")); + } + + @Test + public void testIsFrameValid() throws Exception { + DefaultExtension defaultExtension = new DefaultExtension(); + TextFrame textFrame = new TextFrame(); + try { + defaultExtension.isFrameValid(textFrame); + } catch (Exception e) { + fail("This frame is valid"); + } + textFrame.setRSV1(true); + try { + defaultExtension.isFrameValid(textFrame); + fail("This frame is not valid"); + } catch (Exception e) { + // + } + textFrame.setRSV1(false); + textFrame.setRSV2(true); + try { + defaultExtension.isFrameValid(textFrame); + fail("This frame is not valid"); + } catch (Exception e) { + // + } + textFrame.setRSV2(false); + textFrame.setRSV3(true); + try { + defaultExtension.isFrameValid(textFrame); + fail("This frame is not valid"); + } catch (Exception e) { + // + } + } + + @Test + public void testGetProvidedExtensionAsClient() throws Exception { + DefaultExtension defaultExtension = new DefaultExtension(); + assertEquals("", defaultExtension.getProvidedExtensionAsClient()); + } + + @Test + public void testGetProvidedExtensionAsServer() throws Exception { + DefaultExtension defaultExtension = new DefaultExtension(); + assertEquals("", defaultExtension.getProvidedExtensionAsServer()); + } + + @Test + public void testCopyInstance() throws Exception { + DefaultExtension defaultExtension = new DefaultExtension(); + IExtension extensionCopy = defaultExtension.copyInstance(); + assertEquals(defaultExtension, extensionCopy); + } + + @Test + public void testToString() throws Exception { + DefaultExtension defaultExtension = new DefaultExtension(); + assertEquals("DefaultExtension", defaultExtension.toString()); + } + + @Test + public void testHashCode() throws Exception { + DefaultExtension defaultExtension0 = new DefaultExtension(); + DefaultExtension defaultExtension1 = new DefaultExtension(); + assertEquals(defaultExtension0.hashCode(), defaultExtension1.hashCode()); + } + + @Test + public void testEquals() throws Exception { + DefaultExtension defaultExtension0 = new DefaultExtension(); + DefaultExtension defaultExtension1 = new DefaultExtension(); + assertEquals(defaultExtension0, defaultExtension1); + assertNotEquals(null, defaultExtension0); + assertNotEquals(defaultExtension0, new Object()); + } + +} \ No newline at end of file diff --git a/src/test/java/org/java_websocket/extensions/PerMessageDeflateExtensionTest.java b/src/test/java/org/java_websocket/extensions/PerMessageDeflateExtensionTest.java new file mode 100644 index 000000000..5a86648f3 --- /dev/null +++ b/src/test/java/org/java_websocket/extensions/PerMessageDeflateExtensionTest.java @@ -0,0 +1,304 @@ +package org.java_websocket.extensions; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.zip.Deflater; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.extensions.permessage_deflate.PerMessageDeflateExtension; +import org.java_websocket.framing.ContinuousFrame; +import org.java_websocket.framing.TextFrame; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class PerMessageDeflateExtensionTest { + + @Test + public void testDecodeFrame() throws InvalidDataException { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(); + deflateExtension.setThreshold(0); + String str = "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text"; + byte[] message = str.getBytes(); + TextFrame frame = new TextFrame(); + frame.setPayload(ByteBuffer.wrap(message)); + deflateExtension.encodeFrame(frame); + assertTrue(frame.isRSV1()); + deflateExtension.decodeFrame(frame); + assertArrayEquals(message, frame.getPayloadData().array()); + } + @Test + public void testDecodeFrameIfRSVIsNotSet() throws InvalidDataException { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(); + String str = "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text"; + byte[] message = str.getBytes(); + TextFrame frame = new TextFrame(); + frame.setPayload(ByteBuffer.wrap(message)); + deflateExtension.decodeFrame(frame); + assertArrayEquals(message, frame.getPayloadData().array()); + assertFalse(frame.isRSV1()); + } + + @Test + public void testDecodeFrameNoCompression() throws InvalidDataException { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(Deflater.NO_COMPRESSION); + deflateExtension.setThreshold(0); + String str = "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text"; + byte[] message = str.getBytes(); + TextFrame frame = new TextFrame(); + frame.setPayload(ByteBuffer.wrap(message)); + deflateExtension.encodeFrame(frame); + byte[] payloadArray = frame.getPayloadData().array(); + assertArrayEquals(message, Arrays.copyOfRange(payloadArray, 5,payloadArray.length-5)); + assertTrue(frame.isRSV1()); + deflateExtension.decodeFrame(frame); + assertArrayEquals(message, frame.getPayloadData().array()); + } + + @Test + public void testDecodeFrameBestSpeedCompression() throws InvalidDataException { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(Deflater.BEST_SPEED); + deflateExtension.setThreshold(0); + String str = "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text"; + byte[] message = str.getBytes(); + TextFrame frame = new TextFrame(); + frame.setPayload(ByteBuffer.wrap(message)); + + Deflater localDeflater = new Deflater(Deflater.BEST_SPEED,true); + localDeflater.setInput(ByteBuffer.wrap(message).array()); + byte[] buffer = new byte[1024]; + int bytesCompressed = localDeflater.deflate(buffer, 0, buffer.length, Deflater.SYNC_FLUSH); + + deflateExtension.encodeFrame(frame); + byte[] payloadArray = frame.getPayloadData().array(); + assertArrayEquals(Arrays.copyOfRange(buffer,0, bytesCompressed), Arrays.copyOfRange(payloadArray,0,payloadArray.length)); + assertTrue(frame.isRSV1()); + deflateExtension.decodeFrame(frame); + assertArrayEquals(message, frame.getPayloadData().array()); + } + + @Test + public void testDecodeFrameBestCompression() throws InvalidDataException { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(Deflater.BEST_COMPRESSION); + deflateExtension.setThreshold(0); + String str = "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text"; + byte[] message = str.getBytes(); + TextFrame frame = new TextFrame(); + frame.setPayload(ByteBuffer.wrap(message)); + + Deflater localDeflater = new Deflater(Deflater.BEST_COMPRESSION,true); + localDeflater.setInput(ByteBuffer.wrap(message).array()); + byte[] buffer = new byte[1024]; + int bytesCompressed = localDeflater.deflate(buffer, 0, buffer.length, Deflater.SYNC_FLUSH); + + deflateExtension.encodeFrame(frame); + byte[] payloadArray = frame.getPayloadData().array(); + assertArrayEquals(Arrays.copyOfRange(buffer,0, bytesCompressed), Arrays.copyOfRange(payloadArray,0,payloadArray.length)); + assertTrue(frame.isRSV1()); + deflateExtension.decodeFrame(frame); + assertArrayEquals(message, frame.getPayloadData().array()); + } + + + @Test + public void testEncodeFrame() { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(); + deflateExtension.setThreshold(0); + String str = "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text" + + "This is a highly compressable text"; + byte[] message = str.getBytes(); + TextFrame frame = new TextFrame(); + frame.setPayload(ByteBuffer.wrap(message)); + deflateExtension.encodeFrame(frame); + assertTrue(message.length > frame.getPayloadData().array().length); + } + @Test + public void testEncodeFrameBelowThreshold() { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(); + deflateExtension.setThreshold(11); + String str = "Hello World"; + byte[] message = str.getBytes(); + TextFrame frame = new TextFrame(); + frame.setPayload(ByteBuffer.wrap(message)); + deflateExtension.encodeFrame(frame); + // Message length is equal to the threshold --> encode + assertTrue(frame.isRSV1()); + str = "Hello Worl"; + message = str.getBytes(); + frame = new TextFrame(); + frame.setPayload(ByteBuffer.wrap(message)); + deflateExtension.encodeFrame(frame); + // Message length is below to the threshold --> do NOT encode + assertFalse(frame.isRSV1()); + } + + @Test + public void testAcceptProvidedExtensionAsServer() { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(); + assertTrue(deflateExtension.acceptProvidedExtensionAsServer("permessage-deflate")); + assertTrue(deflateExtension + .acceptProvidedExtensionAsServer("some-other-extension, permessage-deflate")); + assertFalse(deflateExtension.acceptProvidedExtensionAsServer("wrong-permessage-deflate")); + } + + @Test + public void testAcceptProvidedExtensionAsClient() { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(); + assertTrue(deflateExtension.acceptProvidedExtensionAsClient("permessage-deflate")); + assertTrue(deflateExtension + .acceptProvidedExtensionAsClient("some-other-extension, permessage-deflate")); + assertFalse(deflateExtension.acceptProvidedExtensionAsClient("wrong-permessage-deflate")); + } + + @Test + public void testIsFrameValid() { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(); + TextFrame frame = new TextFrame(); + try { + deflateExtension.isFrameValid(frame); + } catch (Exception e) { + fail("RSV1 is optional and should therefore not fail"); + } + frame.setRSV1(true); + try { + deflateExtension.isFrameValid(frame); + } catch (Exception e) { + fail("Frame is valid."); + } + frame.setRSV2(true); + try { + deflateExtension.isFrameValid(frame); + fail("Only RSV1 bit must be set."); + } catch (Exception e) { + // + } + ContinuousFrame contFrame = new ContinuousFrame(); + contFrame.setRSV1(true); + try { + deflateExtension.isFrameValid(contFrame); + fail("RSV1 must only be set for first fragments.Continuous frames can't have RSV1 bit set."); + } catch (Exception e) { + // + } + contFrame.setRSV1(false); + try { + deflateExtension.isFrameValid(contFrame); + } catch (Exception e) { + fail("Continuous frame is valid."); + } + } + + @Test + public void testGetProvidedExtensionAsClient() { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(); + assertEquals("permessage-deflate; server_no_context_takeover; client_no_context_takeover", + deflateExtension.getProvidedExtensionAsClient()); + } + + @Test + public void testGetProvidedExtensionAsServer() { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(); + assertEquals("permessage-deflate; server_no_context_takeover", + deflateExtension.getProvidedExtensionAsServer()); + } + + @Test + public void testToString() { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(); + assertEquals("PerMessageDeflateExtension", deflateExtension.toString()); + } + + @Test + public void testIsServerNoContextTakeover() { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(); + assertTrue(deflateExtension.isServerNoContextTakeover()); + } + + @Test + public void testSetServerNoContextTakeover() { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(); + deflateExtension.setServerNoContextTakeover(false); + assertFalse(deflateExtension.isServerNoContextTakeover()); + } + + @Test + public void testIsClientNoContextTakeover() { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(); + assertFalse(deflateExtension.isClientNoContextTakeover()); + } + + @Test + public void testSetClientNoContextTakeover() { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(); + deflateExtension.setClientNoContextTakeover(true); + assertTrue(deflateExtension.isClientNoContextTakeover()); + } + + @Test + public void testCopyInstance() { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(); + PerMessageDeflateExtension newDeflateExtension = (PerMessageDeflateExtension)deflateExtension.copyInstance(); + assertEquals("PerMessageDeflateExtension", newDeflateExtension.toString()); + // Also check the values + assertEquals(deflateExtension.getThreshold(), newDeflateExtension.getThreshold()); + assertEquals(deflateExtension.isClientNoContextTakeover(), newDeflateExtension.isClientNoContextTakeover()); + assertEquals(deflateExtension.isServerNoContextTakeover(), newDeflateExtension.isServerNoContextTakeover()); + assertEquals(deflateExtension.getCompressionLevel(), newDeflateExtension.getCompressionLevel()); + + + deflateExtension = new PerMessageDeflateExtension(Deflater.BEST_COMPRESSION); + deflateExtension.setThreshold(512); + deflateExtension.setServerNoContextTakeover(false); + deflateExtension.setClientNoContextTakeover(true); + newDeflateExtension = (PerMessageDeflateExtension)deflateExtension.copyInstance(); + + assertEquals(deflateExtension.getThreshold(), newDeflateExtension.getThreshold()); + assertEquals(deflateExtension.isClientNoContextTakeover(), newDeflateExtension.isClientNoContextTakeover()); + assertEquals(deflateExtension.isServerNoContextTakeover(), newDeflateExtension.isServerNoContextTakeover()); + assertEquals(deflateExtension.getCompressionLevel(), newDeflateExtension.getCompressionLevel()); + + + deflateExtension = new PerMessageDeflateExtension(Deflater.NO_COMPRESSION); + deflateExtension.setThreshold(64); + deflateExtension.setServerNoContextTakeover(true); + deflateExtension.setClientNoContextTakeover(false); + newDeflateExtension = (PerMessageDeflateExtension)deflateExtension.copyInstance(); + + assertEquals(deflateExtension.getThreshold(), newDeflateExtension.getThreshold()); + assertEquals(deflateExtension.isClientNoContextTakeover(), newDeflateExtension.isClientNoContextTakeover()); + assertEquals(deflateExtension.isServerNoContextTakeover(), newDeflateExtension.isServerNoContextTakeover()); + assertEquals(deflateExtension.getCompressionLevel(), newDeflateExtension.getCompressionLevel()); + } + + @Test + public void testDefaults() { + PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension(); + assertFalse(deflateExtension.isClientNoContextTakeover()); + assertTrue(deflateExtension.isServerNoContextTakeover()); + assertEquals(1024, deflateExtension.getThreshold()); + assertEquals(Deflater.DEFAULT_COMPRESSION, deflateExtension.getCompressionLevel()); + } +} diff --git a/src/test/java/org/java_websocket/framing/BinaryFrameTest.java b/src/test/java/org/java_websocket/framing/BinaryFrameTest.java new file mode 100644 index 000000000..92a839717 --- /dev/null +++ b/src/test/java/org/java_websocket/framing/BinaryFrameTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.framing; + + +import org.java_websocket.enums.Opcode; +import org.java_websocket.exceptions.InvalidDataException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * JUnit Test for the BinaryFrame class + */ +public class BinaryFrameTest { + + @Test + public void testConstructor() { + BinaryFrame frame = new BinaryFrame(); + assertEquals(Opcode.BINARY, frame.getOpcode(), "Opcode must be equal"); + assertTrue(frame.isFin(), "Fin must be set"); + assertFalse(frame.getTransfereMasked(), "TransferedMask must not be set"); + assertEquals(0, frame.getPayloadData().capacity(), "Payload must be empty"); + assertFalse(frame.isRSV1(), "RSV1 must be false"); + assertFalse(frame.isRSV2(), "RSV2 must be false"); + assertFalse(frame.isRSV3(), "RSV3 must be false"); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + } + + @Test + public void testExtends() { + BinaryFrame frame = new BinaryFrame(); + assertInstanceOf(DataFrame.class, frame, "Frame must extend dataframe"); + } + + @Test + public void testIsValid() { + BinaryFrame frame = new BinaryFrame(); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + } +} diff --git a/src/test/java/org/java_websocket/framing/CloseFrameTest.java b/src/test/java/org/java_websocket/framing/CloseFrameTest.java new file mode 100644 index 000000000..b20224634 --- /dev/null +++ b/src/test/java/org/java_websocket/framing/CloseFrameTest.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.framing; + +import org.java_websocket.enums.Opcode; +import org.java_websocket.exceptions.InvalidDataException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * JUnit Test for the CloseFrame class + */ +public class CloseFrameTest { + + @Test + public void testConstructor() { + CloseFrame frame = new CloseFrame(); + assertEquals(Opcode.CLOSING, frame.getOpcode(), "Opcode must be equal"); + assertTrue(frame.isFin(), "Fin must be set"); + assertFalse(frame.getTransfereMasked(), "TransferedMask must not be set"); + assertEquals( 2, frame.getPayloadData().capacity(), "Payload must be 2 (close code)"); + assertFalse(frame.isRSV1(), "RSV1 must be false"); + assertFalse(frame.isRSV2(), "RSV2 must be false"); + assertFalse(frame.isRSV3(), "RSV3 must be false"); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + } + + @Test + public void testExtends() { + CloseFrame frame = new CloseFrame(); + assertInstanceOf(ControlFrame.class, frame, "Frame must extend dataframe"); + } + + @Test + public void testToString() { + CloseFrame frame = new CloseFrame(); + String frameString = frame.toString(); + frameString = frameString.replaceAll("payload:(.*)}", "payload: *}"); + assertEquals( + "Framedata{ opcode:CLOSING, fin:true, rsv1:false, rsv2:false, rsv3:false, payload length:[pos:0, len:2], payload: *}code: 1000", + frameString, "Frame toString must include a close code"); + } + + + @Test + public void testIsValid() { + CloseFrame frame = new CloseFrame(); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + frame.setFin(false); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //Fine + } + frame.setFin(true); + frame.setRSV1(true); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //Fine + } + frame.setRSV1(false); + frame.setRSV2(true); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //Fine + } + frame.setRSV2(false); + frame.setRSV3(true); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //Fine + } + frame.setRSV3(false); + frame.setCode(CloseFrame.NORMAL); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + frame.setCode(CloseFrame.GOING_AWAY); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + frame.setCode(CloseFrame.PROTOCOL_ERROR); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + frame.setCode(CloseFrame.REFUSE); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + frame.setCode(CloseFrame.NOCODE); + assertEquals(0, frame.getPayloadData().capacity()); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //fine + } + frame.setCode(CloseFrame.ABNORMAL_CLOSE); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //fine + } + frame.setCode(CloseFrame.POLICY_VALIDATION); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + frame.setCode(CloseFrame.TOOBIG); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + frame.setCode(CloseFrame.EXTENSION); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + frame.setCode(CloseFrame.UNEXPECTED_CONDITION); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + frame.setCode(CloseFrame.SERVICE_RESTART); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + frame.setCode(CloseFrame.TRY_AGAIN_LATER); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + frame.setCode(CloseFrame.BAD_GATEWAY); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + frame.setCode(CloseFrame.TLS_ERROR); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //fine + } + frame.setCode(CloseFrame.NEVER_CONNECTED); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //fine + } + frame.setCode(CloseFrame.BUGGYCLOSE); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //fine + } + frame.setCode(CloseFrame.FLASHPOLICY); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //fine + } + frame.setCode(CloseFrame.NOCODE); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //fine + } + frame.setCode(CloseFrame.NO_UTF8); + frame.setReason(null); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //fine + } + frame.setCode(CloseFrame.NOCODE); + frame.setReason("Close"); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //fine + } + } +} diff --git a/src/test/java/org/java_websocket/framing/ContinuousFrameTest.java b/src/test/java/org/java_websocket/framing/ContinuousFrameTest.java new file mode 100644 index 000000000..98c1fa266 --- /dev/null +++ b/src/test/java/org/java_websocket/framing/ContinuousFrameTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.framing; + + +import org.java_websocket.enums.Opcode; +import org.java_websocket.exceptions.InvalidDataException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + + +/** + * JUnit Test for the ContinuousFrame class + */ +public class ContinuousFrameTest { + + @Test + public void testConstructor() { + ContinuousFrame frame = new ContinuousFrame(); + assertEquals(Opcode.CONTINUOUS, frame.getOpcode(), "Opcode must be equal"); + assertTrue(frame.isFin(), "Fin must be set"); + assertFalse(frame.getTransfereMasked(), "TransferedMask must not be set"); + assertEquals( 0, frame.getPayloadData().capacity(), "Payload must be empty"); + assertFalse(frame.isRSV1(), "RSV1 must be false"); + assertFalse(frame.isRSV2(), "RSV2 must be false"); + assertFalse(frame.isRSV3(), "RSV3 must be false"); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + } + + @Test + public void testExtends() { + ContinuousFrame frame = new ContinuousFrame(); + assertInstanceOf(DataFrame.class, frame, "Frame must extend dataframe"); + } + + @Test + public void testIsValid() { + ContinuousFrame frame = new ContinuousFrame(); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + } +} diff --git a/src/test/java/org/java_websocket/framing/FramedataImpl1Test.java b/src/test/java/org/java_websocket/framing/FramedataImpl1Test.java new file mode 100644 index 000000000..b952091be --- /dev/null +++ b/src/test/java/org/java_websocket/framing/FramedataImpl1Test.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.framing; + + +import java.nio.ByteBuffer; +import org.java_websocket.enums.Opcode; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * JUnit Test for the FramedataImpl1 class + */ +public class FramedataImpl1Test { + + @Test + public void testDefaultValues() { + FramedataImpl1 binary = FramedataImpl1.get(Opcode.BINARY); + assertEquals(Opcode.BINARY, binary.getOpcode(), "Opcode must be equal"); + assertTrue(binary.isFin(), "Fin must be set"); + assertFalse(binary.getTransfereMasked(), "TransferedMask must not be set"); + assertEquals( 0, binary.getPayloadData().capacity(), "Payload must be empty"); + assertFalse(binary.isRSV1(), "RSV1 must be false"); + assertFalse(binary.isRSV2(), "RSV2 must be false"); + assertFalse(binary.isRSV3(), "RSV3 must be false"); + } + + @Test + public void testGet() { + FramedataImpl1 binary = FramedataImpl1.get(Opcode.BINARY); + assertInstanceOf(BinaryFrame.class, binary, "Frame must be binary"); + FramedataImpl1 text = FramedataImpl1.get(Opcode.TEXT); + assertInstanceOf(TextFrame.class, text, "Frame must be text"); + FramedataImpl1 closing = FramedataImpl1.get(Opcode.CLOSING); + assertInstanceOf(CloseFrame.class, closing, "Frame must be closing"); + FramedataImpl1 continuous = FramedataImpl1.get(Opcode.CONTINUOUS); + assertInstanceOf(ContinuousFrame.class, continuous, "Frame must be continuous"); + FramedataImpl1 ping = FramedataImpl1.get(Opcode.PING); + assertInstanceOf(PingFrame.class, ping, "Frame must be ping"); + FramedataImpl1 pong = FramedataImpl1.get(Opcode.PONG); + assertInstanceOf(PongFrame.class, pong, "Frame must be pong"); + try { + FramedataImpl1.get(null); + fail("IllegalArgumentException should be thrown"); + } catch (IllegalArgumentException e) { + //Fine + } + } + + @Test + public void testSetters() { + FramedataImpl1 frame = FramedataImpl1.get(Opcode.BINARY); + frame.setFin(false); + assertFalse(frame.isFin(), "Fin must not be set"); + frame.setTransferemasked(true); + assertTrue(frame.getTransfereMasked(), "TransferedMask must be set"); + ByteBuffer buffer = ByteBuffer.allocate(100); + frame.setPayload(buffer); + assertEquals( 100, frame.getPayloadData().capacity(), "Payload must be of size 100"); + frame.setRSV1(true); + assertTrue(frame.isRSV1(), "RSV1 must be true"); + frame.setRSV2(true); + assertTrue(frame.isRSV2(), "RSV2 must be true"); + frame.setRSV3(true); + assertTrue(frame.isRSV3(), "RSV3 must be true"); + } + + @Test + public void testAppend() { + FramedataImpl1 frame0 = FramedataImpl1.get(Opcode.BINARY); + frame0.setFin(false); + frame0.setPayload(ByteBuffer.wrap("first".getBytes())); + FramedataImpl1 frame1 = FramedataImpl1.get(Opcode.BINARY); + frame1.setPayload(ByteBuffer.wrap("second".getBytes())); + frame0.append(frame1); + assertTrue(frame0.isFin(), "Fin must be set"); + assertArrayEquals( "firstsecond".getBytes(), + frame0.getPayloadData().array(), "Payload must be equal"); + } +} diff --git a/src/test/java/org/java_websocket/framing/PingFrameTest.java b/src/test/java/org/java_websocket/framing/PingFrameTest.java new file mode 100644 index 000000000..9ccdc7580 --- /dev/null +++ b/src/test/java/org/java_websocket/framing/PingFrameTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.framing; + +import org.java_websocket.enums.Opcode; +import org.java_websocket.exceptions.InvalidDataException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * JUnit Test for the PingFrame class + */ +public class PingFrameTest { + + @Test + public void testConstructor() { + PingFrame frame = new PingFrame(); + assertEquals(Opcode.PING, frame.getOpcode(), "Opcode must be equal"); + assertTrue(frame.isFin(), "Fin must be set"); + assertFalse(frame.getTransfereMasked(), "TransferedMask must not be set"); + assertEquals(0, frame.getPayloadData().capacity(), "Payload must be empty"); + assertFalse(frame.isRSV1(), "RSV1 must be false"); + assertFalse(frame.isRSV2(), "RSV2 must be false"); + assertFalse(frame.isRSV3(), "RSV3 must be false"); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + } + + @Test + public void testExtends() { + PingFrame frame = new PingFrame(); + assertInstanceOf(ControlFrame.class, frame, "Frame must extend dataframe"); + } + + @Test + public void testIsValid() { + PingFrame frame = new PingFrame(); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + frame.setFin(false); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //Fine + } + frame.setFin(true); + frame.setRSV1(true); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //Fine + } + frame.setRSV1(false); + frame.setRSV2(true); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //Fine + } + frame.setRSV2(false); + frame.setRSV3(true); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //Fine + } + } +} diff --git a/src/test/java/org/java_websocket/framing/PongFrameTest.java b/src/test/java/org/java_websocket/framing/PongFrameTest.java new file mode 100644 index 000000000..d2985b7a9 --- /dev/null +++ b/src/test/java/org/java_websocket/framing/PongFrameTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.framing; + + +import java.nio.ByteBuffer; +import org.java_websocket.enums.Opcode; +import org.java_websocket.exceptions.InvalidDataException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * JUnit Test for the PongFrame class + */ +public class PongFrameTest { + + @Test + public void testConstructor() { + PongFrame frame = new PongFrame(); + assertEquals(Opcode.PONG, frame.getOpcode(), "Opcode must be equal"); + assertTrue(frame.isFin(), "Fin must be set"); + assertFalse(frame.getTransfereMasked(), "TransferedMask must not be set"); + assertEquals( 0, frame.getPayloadData().capacity(),"Payload must be empty"); + assertFalse(frame.isRSV1(), "RSV1 must be false"); + assertFalse(frame.isRSV2(), "RSV2 must be false"); + assertFalse(frame.isRSV3(), "RSV3 must be false"); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + } + + @Test + public void testCopyConstructor() { + PingFrame pingFrame = new PingFrame(); + pingFrame.setPayload(ByteBuffer.allocate(100)); + PongFrame pongFrame = new PongFrame(pingFrame); + assertEquals( pingFrame.getPayloadData(), pongFrame.getPayloadData(), "Payload must be equal"); + } + + @Test + public void testExtends() { + PongFrame frame = new PongFrame(); + assertInstanceOf(ControlFrame.class, frame, "Frame must extend dataframe"); + } + + @Test + public void testIsValid() { + PongFrame frame = new PongFrame(); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + frame.setFin(false); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //Fine + } + frame.setFin(true); + frame.setRSV1(true); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //Fine + } + frame.setRSV1(false); + frame.setRSV2(true); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //Fine + } + frame.setRSV2(false); + frame.setRSV3(true); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //Fine + } + } +} diff --git a/src/test/java/org/java_websocket/framing/TextFrameTest.java b/src/test/java/org/java_websocket/framing/TextFrameTest.java new file mode 100644 index 000000000..21f83f1c9 --- /dev/null +++ b/src/test/java/org/java_websocket/framing/TextFrameTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.framing; + + +import java.nio.ByteBuffer; +import org.java_websocket.enums.Opcode; +import org.java_websocket.exceptions.InvalidDataException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * JUnit Test for the TextFrame class + */ +public class TextFrameTest { + + @Test + public void testConstructor() { + TextFrame frame = new TextFrame(); + assertEquals(Opcode.TEXT, frame.getOpcode(), "Opcode must be equal"); + assertTrue(frame.isFin(), "Fin must be set"); + assertFalse(frame.getTransfereMasked(), "TransferedMask must not be set"); + assertEquals( 0, frame.getPayloadData().capacity(), "Payload must be empty"); + assertFalse(frame.isRSV1(), "RSV1 must be false"); + assertFalse(frame.isRSV2(), "RSV2 must be false"); + assertFalse(frame.isRSV3(), "RSV3 must be false"); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + } + + @Test + public void testExtends() { + TextFrame frame = new TextFrame(); + assertInstanceOf(DataFrame.class, frame, "Frame must extend dataframe"); + } + + @Test + public void testIsValid() { + TextFrame frame = new TextFrame(); + try { + frame.isValid(); + } catch (InvalidDataException e) { + fail("InvalidDataException should not be thrown"); + } + + frame = new TextFrame(); + frame.setPayload(ByteBuffer.wrap(new byte[]{ + (byte) 0xD0, (byte) 0x9F, // 'П' + (byte) 0xD1, (byte) 0x80, // 'р' + (byte) 0xD0, // corrupted UTF-8, was 'и' + (byte) 0xD0, (byte) 0xB2, // 'в' + (byte) 0xD0, (byte) 0xB5, // 'е' + (byte) 0xD1, (byte) 0x82 // 'т' + })); + try { + frame.isValid(); + fail("InvalidDataException should be thrown"); + } catch (InvalidDataException e) { + //Utf8 Check should work + } + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue1142Test.java b/src/test/java/org/java_websocket/issues/Issue1142Test.java new file mode 100644 index 000000000..38ff93e4d --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue1142Test.java @@ -0,0 +1,139 @@ +package org.java_websocket.issues; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.concurrent.CountDownLatch; +import javax.net.ssl.SSLContext; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.DefaultSSLWebSocketServerFactory; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SSLContextUtil; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import static org.junit.jupiter.api.Assertions.*; + +public class Issue1142Test { + + + + @Test + @Timeout(4000) + public void testWithoutSSLSession() + throws IOException, URISyntaxException, InterruptedException { + int port = SocketUtil.getAvailablePort(); + final CountDownLatch countServerDownLatch = new CountDownLatch(1); + final WebSocketClient webSocket = new WebSocketClient(new URI("ws://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + countServerDownLatch.countDown(); + } + + @Override + public void onMessage(String message) { + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onError(Exception ex) { + } + }; + WebSocketServer server = new MyWebSocketServer(port, countServerDownLatch); + server.start(); + countServerDownLatch.await(); + webSocket.connectBlocking(); + assertFalse(webSocket.hasSSLSupport()); + try { + webSocket.getSSLSession(); + assertFalse(false); + } catch (IllegalArgumentException e) { + // Fine + } + server.stop(); + } + + @Test + @Timeout(4000) + public void testWithSSLSession() + throws IOException, URISyntaxException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException, UnrecoverableKeyException, CertificateException, InterruptedException { + int port = SocketUtil.getAvailablePort(); + final CountDownLatch countServerDownLatch = new CountDownLatch(1); + final WebSocketClient webSocket = new WebSocketClient(new URI("wss://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + countServerDownLatch.countDown(); + } + + @Override + public void onMessage(String message) { + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onError(Exception ex) { + } + }; + WebSocketServer server = new MyWebSocketServer(port, countServerDownLatch); + SSLContext sslContext = SSLContextUtil.getContext(); + + server.setWebSocketFactory(new DefaultSSLWebSocketServerFactory(sslContext)); + webSocket.setSocketFactory(sslContext.getSocketFactory()); + server.start(); + countServerDownLatch.await(); + webSocket.connectBlocking(); + assertTrue(webSocket.hasSSLSupport()); + assertNotNull(webSocket.getSSLSession()); + server.stop(); + } + + private static class MyWebSocketServer extends WebSocketServer { + + private final CountDownLatch countServerLatch; + + + public MyWebSocketServer(int port, CountDownLatch serverDownLatch) { + super(new InetSocketAddress(port)); + this.countServerLatch = serverDownLatch; + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, String message) { + + } + + @Override + public void onError(WebSocket conn, Exception ex) { + ex.printStackTrace(); + } + + @Override + public void onStart() { + countServerLatch.countDown(); + } + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue1160Test.java b/src/test/java/org/java_websocket/issues/Issue1160Test.java new file mode 100644 index 000000000..57983cdea --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue1160Test.java @@ -0,0 +1,165 @@ +package org.java_websocket.issues; + +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.net.InetSocketAddress; +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class Issue1160Test { + private final CountDownLatch countServerStart = new CountDownLatch(1); + + static class TestClient extends WebSocketClient { + private final CountDownLatch onCloseLatch; + + public TestClient(URI uri, CountDownLatch latch) { + super(uri); + onCloseLatch = latch; + } + + @Override + public void onOpen(ServerHandshake handshakedata) { + } + + @Override + public void onMessage(String message) { + } + + @Override + public void onClose(int code, String reason, boolean remote) { + onCloseLatch.countDown(); + } + + @Override + public void onError(Exception ex) { + } + } + + + @Test + @Timeout(5000) + public void nonFatalErrorShallBeHandledByServer() throws Exception { + final AtomicInteger isServerOnErrorCalledCounter = new AtomicInteger(0); + + int port = SocketUtil.getAvailablePort(); + WebSocketServer server = new WebSocketServer(new InetSocketAddress(port)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, ByteBuffer message) { + throw new Error("Some error"); + } + + @Override + public void onMessage(WebSocket conn, String message) { + throw new Error("Some error"); + } + + @Override + public void onError(WebSocket conn, Exception ex) { + isServerOnErrorCalledCounter.incrementAndGet(); + } + + @Override + public void onStart() { + countServerStart.countDown(); + } + }; + + + server.setConnectionLostTimeout(10); + server.start(); + countServerStart.await(); + + URI uri = new URI("ws://localhost:" + port); + + int CONNECTION_COUNT = 3; + for (int i = 0; i < CONNECTION_COUNT; i++) { + CountDownLatch countClientDownLatch = new CountDownLatch(1); + WebSocketClient client = new TestClient(uri, countClientDownLatch); + client.setConnectionLostTimeout(10); + + client.connectBlocking(); + client.send(new byte[100]); + countClientDownLatch.await(); + client.closeBlocking(); + } + + assertEquals(CONNECTION_COUNT, isServerOnErrorCalledCounter.get()); + + server.stop(); + } + + @Test + @Timeout(5000) + public void fatalErrorShallNotBeHandledByServer() throws Exception { + int port = SocketUtil.getAvailablePort(); + + final CountDownLatch countServerDownLatch = new CountDownLatch(1); + WebSocketServer server = new WebSocketServer(new InetSocketAddress(port)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + countServerDownLatch.countDown(); + } + + @Override + public void onMessage(WebSocket conn, ByteBuffer message) { + throw new OutOfMemoryError("Some intentional error"); + } + + @Override + public void onMessage(WebSocket conn, String message) { + throw new OutOfMemoryError("Some error"); + } + + @Override + public void onError(WebSocket conn, Exception ex) { + } + + @Override + public void onStart() { + countServerStart.countDown(); + } + }; + + + server.setConnectionLostTimeout(10); + server.start(); + countServerStart.await(); + + URI uri = new URI("ws://localhost:" + port); + + CountDownLatch countClientDownLatch = new CountDownLatch(1); + WebSocketClient client = new TestClient(uri, countClientDownLatch); + client.setConnectionLostTimeout(10); + + client.connectBlocking(); + client.send(new byte[100]); + countClientDownLatch.await(); + countServerDownLatch.await(); + assertEquals(0,countClientDownLatch.getCount()); + assertEquals(0, countServerDownLatch.getCount()); + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue1203Test.java b/src/test/java/org/java_websocket/issues/Issue1203Test.java new file mode 100644 index 000000000..45c4cd593 --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue1203Test.java @@ -0,0 +1,96 @@ +package org.java_websocket.issues; + + +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.CountDownLatch; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import static org.junit.jupiter.api.Assertions.assertFalse; + + +public class Issue1203Test { + private final CountDownLatch countServerDownLatch = new CountDownLatch(1); + private final CountDownLatch countClientDownLatch = new CountDownLatch(1); + boolean isClosedCalled = false; + @Test + @Timeout(50000) + public void testIssue() throws Exception { + int port = SocketUtil.getAvailablePort(); + WebSocketServer server = new WebSocketServer(new InetSocketAddress(port)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, String message) { + } + + @Override + public void onError(WebSocket conn, Exception ex) { + } + + @Override + public void onStart() { + countServerDownLatch.countDown(); + } + }; + final WebSocketClient client = new WebSocketClient(new URI("ws://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + countClientDownLatch.countDown(); + } + + @Override + public void onMessage(String message) { + + } + + @Override + public void onClose(int code, String reason, boolean remote) { + isClosedCalled = true; + } + + @Override + public void onError(Exception ex) { + + } + }; + + server.setConnectionLostTimeout(10); + server.start(); + countServerDownLatch.await(); + + client.setConnectionLostTimeout(10); + Timer timer = new Timer(); + TimerTask task = new TimerTask() { + @Override + public void run() { + try { + client.connectBlocking(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }; + timer.schedule(task, 15000); + countClientDownLatch.await(); + Thread.sleep(30000); + assertFalse(isClosedCalled); + client.closeBlocking(); + server.stop(); + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue256Test.java b/src/test/java/org/java_websocket/issues/Issue256Test.java new file mode 100644 index 000000000..30d461459 --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue256Test.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.issues; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.java_websocket.util.ThreadCheck; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.fail; + +@ExtendWith(ThreadCheck.class) +public class Issue256Test { + + private static final int NUMBER_OF_TESTS = 10; + private static WebSocketServer ws; + + private static int port; + static CountDownLatch countServerDownLatch = new CountDownLatch(1); + + @BeforeAll + public static void startServer() throws Exception { + port = SocketUtil.getAvailablePort(); + ws = new WebSocketServer(new InetSocketAddress(port), 16) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + + } + + @Override + public void onMessage(WebSocket conn, String message) { + conn.send(message); + } + + @Override + public void onError(WebSocket conn, Exception ex) { + fail("There should be no exception!"); + } + + @Override + public void onStart() { + countServerDownLatch.countDown(); + } + }; + ws.setConnectionLostTimeout(0); + ws.start(); + countServerDownLatch.await(); + } + + private void runTestScenarioReconnect(boolean closeBlocking) throws Exception { + final CountDownLatch countDownLatch0 = new CountDownLatch(1); + final CountDownLatch countDownLatch1 = new CountDownLatch(2); + WebSocketClient clt = new WebSocketClient(new URI("ws://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + countDownLatch1.countDown(); + } + + @Override + public void onMessage(String message) { + + } + + @Override + public void onClose(int code, String reason, boolean remote) { + countDownLatch0.countDown(); + } + + @Override + public void onError(Exception ex) { + fail("There should be no exception!"); + } + }; + clt.connectBlocking(); + if (closeBlocking) { + clt.closeBlocking(); + } else { + clt.getSocket().close(); + } + countDownLatch0.await(); + clt.reconnectBlocking(); + clt.closeBlocking(); + } + + @AfterAll + public static void successTests() throws InterruptedException, IOException { + ws.stop(); + } + + public static Collection data() { + List ret = new ArrayList(NUMBER_OF_TESTS); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + ret.add(new Integer[]{i}); + } + return ret; + } + + @ParameterizedTest + @Timeout(5000) + @MethodSource("data") + public void runReconnectSocketClose() throws Exception { + runTestScenarioReconnect(false); + } + + @ParameterizedTest + @Timeout(5000) + @MethodSource("data") + public void runReconnectCloseBlocking() throws Exception { + runTestScenarioReconnect(true); + } + +} + diff --git a/src/test/java/org/java_websocket/issues/Issue580Test.java b/src/test/java/org/java_websocket/issues/Issue580Test.java new file mode 100644 index 000000000..e50e5f839 --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue580Test.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.issues; + +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.java_websocket.util.ThreadCheck; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +@ExtendWith(ThreadCheck.class) +public class Issue580Test { + + private static final int NUMBER_OF_TESTS = 10; + + + private void runTestScenario(boolean closeBlocking) throws Exception { + final CountDownLatch countServerDownLatch = new CountDownLatch(1); + int port = SocketUtil.getAvailablePort(); + WebSocketServer ws = new WebSocketServer(new InetSocketAddress(port)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + + } + + @Override + public void onMessage(WebSocket conn, String message) { + + } + + @Override + public void onError(WebSocket conn, Exception ex) { + + } + + @Override + public void onStart() { + countServerDownLatch.countDown(); + } + }; + ws.start(); + countServerDownLatch.await(); + WebSocketClient clt = new WebSocketClient(new URI("ws://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + + } + + @Override + public void onMessage(String message) { + + } + + @Override + public void onClose(int code, String reason, boolean remote) { + + } + + @Override + public void onError(Exception ex) { + + } + }; + clt.connectBlocking(); + clt.send("test"); + if (closeBlocking) { + clt.closeBlocking(); + } + ws.stop(); + Thread.sleep(100); + } + + public static Collection data() { + List ret = new ArrayList(NUMBER_OF_TESTS); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + ret.add(new Integer[]{i}); + } + return ret; + } + + + @ParameterizedTest + @MethodSource("data") + public void runNoCloseBlockingTestScenario() throws Exception { + runTestScenario(false); + } + + @ParameterizedTest + @MethodSource("data") + public void runCloseBlockingTestScenario() throws Exception { + runTestScenario(true); + } +} + diff --git a/src/test/java/org/java_websocket/issues/Issue598Test.java b/src/test/java/org/java_websocket/issues/Issue598Test.java new file mode 100644 index 000000000..26c12f56a --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue598Test.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.issues; + +import java.net.InetSocketAddress; +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.enums.Opcode; +import org.java_websocket.extensions.IExtension; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.protocols.IProtocol; +import org.java_websocket.protocols.Protocol; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +public class Issue598Test { + + private static List protocolList = Collections.singletonList( + new Protocol("")); + private static List extensionList = Collections.emptyList(); + + private static void runTestScenario(int testCase) throws Exception { + final CountDownLatch countServerDownLatch = new CountDownLatch(1); + final CountDownLatch countReceiveDownLatch = new CountDownLatch(1); + final CountDownLatch countCloseDownLatch = new CountDownLatch(1); + int port = SocketUtil.getAvailablePort(); + Draft draft = null; + switch (testCase) { + case 0: + case 1: + case 2: + case 3: + draft = new Draft_6455(extensionList, protocolList, 10); + break; + case 4: + case 5: + case 6: + case 7: + draft = new Draft_6455(extensionList, protocolList, 9); + break; + } + WebSocketClient webSocket = new WebSocketClient(new URI("ws://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + + } + + @Override + public void onMessage(String message) { + } + + + @Override + public void onClose(int code, String reason, boolean remote) { + + } + + @Override + public void onError(Exception ex) { + + } + }; + WebSocketServer server = new WebSocketServer(new InetSocketAddress(port), + Collections.singletonList(draft)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + if (code == CloseFrame.TOOBIG) { + countCloseDownLatch.countDown(); + } + } + + @Override + public void onMessage(WebSocket conn, String message) { + countReceiveDownLatch.countDown(); + } + + @Override + public void onMessage(WebSocket conn, ByteBuffer message) { + countReceiveDownLatch.countDown(); + } + + @Override + public void onError(WebSocket conn, Exception ex) { + + } + + @Override + public void onStart() { + countServerDownLatch.countDown(); + } + }; + server.start(); + countServerDownLatch.await(); + webSocket.connectBlocking(); + switch (testCase) { + case 0: + case 4: + byte[] bArray = new byte[10]; + for (byte i = 0; i < 10; i++) { + bArray[i] = i; + } + webSocket.send(ByteBuffer.wrap(bArray)); + if (testCase == 0) { + countReceiveDownLatch.await(); + } + if (testCase == 4) { + countCloseDownLatch.await(); + } + break; + case 2: + case 6: + bArray = "0123456789".getBytes(); + webSocket.send(ByteBuffer.wrap(bArray)); + if (testCase == 2) { + countReceiveDownLatch.await(); + } + if (testCase == 6) { + countCloseDownLatch.await(); + } + break; + case 1: + case 5: + bArray = new byte[2]; + for (byte i = 0; i < 10; i++) { + bArray[i % 2] = i; + if (i % 2 == 1) { + webSocket.sendFragmentedFrame(Opcode.BINARY, ByteBuffer.wrap(bArray), i == 9); + } + } + if (testCase == 1) { + countReceiveDownLatch.await(); + } + if (testCase == 5) { + countCloseDownLatch.await(); + } + break; + case 3: + case 7: + for (byte i = 0; i < 10; i++) { + webSocket + .sendFragmentedFrame(Opcode.TEXT, ByteBuffer.wrap((Integer.toString(i)).getBytes()), + i == 9); + } + if (testCase == 3) { + countReceiveDownLatch.await(); + } + if (testCase == 7) { + countCloseDownLatch.await(); + } + break; + } + server.stop(); + } + + @Test + @Timeout(2000) + public void runBelowLimitBytebuffer() throws Exception { + runTestScenario(0); + } + + @Test + @Timeout(2000) + public void runBelowSplitLimitBytebuffer() throws Exception { + runTestScenario(1); + } + + @Test + @Timeout(2000) + public void runBelowLimitString() throws Exception { + runTestScenario(2); + } + + @Test + @Timeout(2000) + public void runBelowSplitLimitString() throws Exception { + runTestScenario(3); + } + + @Test + @Timeout(2000) + public void runAboveLimitBytebuffer() throws Exception { + runTestScenario(4); + } + + @Test + @Timeout(2000) + public void runAboveSplitLimitBytebuffer() throws Exception { + runTestScenario(5); + } + + @Test + @Timeout(2000) + public void runAboveLimitString() throws Exception { + runTestScenario(6); + } + + @Test + @Timeout(2000) + public void runAboveSplitLimitString() throws Exception { + runTestScenario(7); + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue609Test.java b/src/test/java/org/java_websocket/issues/Issue609Test.java new file mode 100644 index 000000000..4c89b784a --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue609Test.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.issues; + +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.concurrent.CountDownLatch; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class Issue609Test { + + CountDownLatch countDownLatch = new CountDownLatch(1); + CountDownLatch countServerDownLatch = new CountDownLatch(1); + + boolean wasOpenClient; + boolean wasOpenServer; + + @Test + public void testIssue() throws Exception { + int port = SocketUtil.getAvailablePort(); + WebSocketClient webSocket = new WebSocketClient(new URI("ws://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + + } + + @Override + public void onMessage(String message) { + + } + + @Override + public void onClose(int code, String reason, boolean remote) { + wasOpenClient = isOpen(); + countDownLatch.countDown(); + } + + @Override + public void onError(Exception ex) { + + } + }; + WebSocketServer server = new WebSocketServer(new InetSocketAddress(port)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + wasOpenServer = conn.isOpen(); + } + + @Override + public void onMessage(WebSocket conn, String message) { + + } + + @Override + public void onError(WebSocket conn, Exception ex) { + + } + + @Override + public void onStart() { + countServerDownLatch.countDown(); + } + }; + server.start(); + countServerDownLatch.await(); + webSocket.connectBlocking(); + assertTrue(webSocket.isOpen(), "webSocket.isOpen()"); + webSocket.getSocket().close(); + countDownLatch.await(); + assertFalse(webSocket.isOpen(), "!webSocket.isOpen()"); + assertFalse(wasOpenClient, "!wasOpenClient"); + assertFalse(wasOpenServer, "!wasOpenServer"); + server.stop(); + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue621Test.java b/src/test/java/org/java_websocket/issues/Issue621Test.java new file mode 100644 index 000000000..f21c40071 --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue621Test.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.issues; + +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.concurrent.CountDownLatch; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class Issue621Test { + + CountDownLatch countDownLatch = new CountDownLatch(1); + CountDownLatch countServerDownLatch = new CountDownLatch(1); + + boolean wasError = false; + + class TestPrintStream extends PrintStream { + + public TestPrintStream(OutputStream out) { + super(out); + } + + @Override + public void println(Object o) { + wasError = true; + super.println(o); + } + } + + @Test + public void testIssue() throws Exception { + System.setErr(new TestPrintStream(System.err)); + int port = SocketUtil.getAvailablePort(); + WebSocketClient webSocket = new WebSocketClient(new URI("ws://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + + } + + @Override + public void onMessage(String message) { + + } + + @Override + public void onClose(int code, String reason, boolean remote) { + countDownLatch.countDown(); + } + + @Override + public void onError(Exception ex) { + + } + }; + WebSocketServer server = new WebSocketServer(new InetSocketAddress(port)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + conn.close(); + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, String message) { + + } + + @Override + public void onError(WebSocket conn, Exception ex) { + + } + + @Override + public void onStart() { + countServerDownLatch.countDown(); + } + }; + server.start(); + countServerDownLatch.await(); + webSocket.connectBlocking(); + countDownLatch.await(); + assertFalse(wasError, "There was an error using System.err"); + server.stop(); + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue661Test.java b/src/test/java/org/java_websocket/issues/Issue661Test.java new file mode 100644 index 000000000..31cded8fc --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue661Test.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.issues; + + +import java.net.BindException; +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; +import org.java_websocket.WebSocket; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.java_websocket.util.ThreadCheck; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(ThreadCheck.class) +public class Issue661Test { + + private CountDownLatch countServerDownLatch = new CountDownLatch(1); + + private boolean wasError = false; + private boolean wasBindException = false; + + @Test + @Timeout(2000) + public void testIssue() throws Exception { + int port = SocketUtil.getAvailablePort(); + WebSocketServer server0 = new WebSocketServer(new InetSocketAddress(port)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + fail("There should be no onOpen"); + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + fail("There should be no onClose"); + } + + @Override + public void onMessage(WebSocket conn, String message) { + fail("There should be no onMessage"); + } + + @Override + public void onError(WebSocket conn, Exception ex) { + fail("There should be no onError!"); + } + + @Override + public void onStart() { + countServerDownLatch.countDown(); + } + }; + server0.start(); + try { + countServerDownLatch.await(); + } catch (InterruptedException e) { + // + } + WebSocketServer server1 = new WebSocketServer(new InetSocketAddress(port)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + fail("There should be no onOpen"); + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + fail("There should be no onClose"); + } + + @Override + public void onMessage(WebSocket conn, String message) { + fail("There should be no onMessage"); + } + + @Override + public void onError(WebSocket conn, Exception ex) { + if (ex instanceof BindException) { + wasBindException = true; + } else { + wasError = true; + } + } + + @Override + public void onStart() { + fail("There should be no onStart!"); + } + }; + server1.start(); + Thread.sleep(1000); + server1.stop(); + server0.stop(); + Thread.sleep(100); + assertFalse(wasError, "There was an unexpected exception!"); + assertTrue(wasBindException, "There was no bind exception!"); + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue666Test.java b/src/test/java/org/java_websocket/issues/Issue666Test.java new file mode 100644 index 000000000..163dd2366 --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue666Test.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.issues; + +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.java_websocket.util.ThreadCheck; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class Issue666Test { + + private CountDownLatch countServerDownLatch = new CountDownLatch(1); + + @Test + public void testServer() throws Exception { + Map mapBefore = ThreadCheck.getThreadMap(); + int port = SocketUtil.getAvailablePort(); + WebSocketServer server = new WebSocketServer(new InetSocketAddress(port)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + + } + + @Override + public void onMessage(WebSocket conn, String message) { + + } + + @Override + public void onError(WebSocket conn, Exception ex) { + + } + + @Override + public void onStart() { + countServerDownLatch.countDown(); + } + }; + server.start(); + countServerDownLatch.await(); + Map mapAfter = ThreadCheck.getThreadMap(); + for (long key : mapBefore.keySet()) { + mapAfter.remove(key); + } + for (Thread thread : mapAfter.values()) { + String name = thread.getName(); + if (!name.startsWith("WebSocketSelector-") && !name.startsWith("WebSocketWorker-") && !name + .startsWith("WebSocketConnectionLostChecker-") + && !name.startsWith("WebSocketConnectReadThread-")) { + fail("Thread not correctly named! Is: " + name); + } + } + server.stop(); + } + + @Test + public void testClient() throws Exception { + int port = SocketUtil.getAvailablePort(); + WebSocketClient client = new WebSocketClient(new URI("ws://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + + } + + @Override + public void onMessage(String message) { + + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onError(Exception ex) { + + } + }; + WebSocketServer server = new WebSocketServer(new InetSocketAddress(port)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + + } + + @Override + public void onMessage(WebSocket conn, String message) { + + } + + @Override + public void onError(WebSocket conn, Exception ex) { + + } + + @Override + public void onStart() { + countServerDownLatch.countDown(); + } + }; + server.start(); + countServerDownLatch.await(); + assertTrue(SocketUtil.waitForServerToStart(port), "Server Start Status"); + Map mapBefore = ThreadCheck.getThreadMap(); + client.connectBlocking(); + Map mapAfter = ThreadCheck.getThreadMap(); + for (long key : mapBefore.keySet()) { + mapAfter.remove(key); + } + for (Thread thread : mapAfter.values()) { + String name = thread.getName(); + if (!name.startsWith("WebSocketConnectionLostChecker-") && !name.startsWith("WebSocketWriteThread-") + && !name.startsWith("WebSocketConnectReadThread-") + && !name.startsWith("WebSocketWorker-")) { + fail("Thread not correctly named! Is: " + name); + } + } + client.close(); + server.stop(); + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue677Test.java b/src/test/java/org/java_websocket/issues/Issue677Test.java new file mode 100644 index 000000000..bf660d789 --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue677Test.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.issues; + + +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.concurrent.CountDownLatch; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class Issue677Test { + + CountDownLatch countDownLatch0 = new CountDownLatch(1); + CountDownLatch countServerDownLatch = new CountDownLatch(1); + + @Test + public void testIssue() throws Exception { + int port = SocketUtil.getAvailablePort(); + WebSocketClient webSocket0 = new WebSocketClient(new URI("ws://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + + } + + @Override + public void onMessage(String message) { + + } + + @Override + public void onClose(int code, String reason, boolean remote) { + countDownLatch0.countDown(); + } + + @Override + public void onError(Exception ex) { + + } + }; + WebSocketClient webSocket1 = new WebSocketClient(new URI("ws://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + } + + @Override + public void onMessage(String message) { + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onError(Exception ex) { + + } + }; + WebSocketServer server = new WebSocketServer(new InetSocketAddress(port)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, String message) { + + } + + @Override + public void onError(WebSocket conn, Exception ex) { + + } + + @Override + public void onStart() { + countServerDownLatch.countDown(); + } + }; + server.start(); + countServerDownLatch.await(); + webSocket0.connectBlocking(); + assertTrue(webSocket0.isOpen(), "webSocket.isOpen()"); + webSocket0.close(); + countDownLatch0.await(); + // Add some delay is since the latch will be decreased in the onClose before the state is reset + for (int i = 0; i < 5; i++) { + if (webSocket0.isClosed()) { + break; + } + Thread.sleep(5); + } + assertTrue(webSocket0.isClosed(), "webSocket.isClosed()"); + webSocket1.connectBlocking(); + assertTrue(webSocket1.isOpen(), "webSocket.isOpen()"); + webSocket1.closeConnection(CloseFrame.ABNORMAL_CLOSE, "Abnormal close!"); + assertTrue(webSocket1.isClosed(), "webSocket.isClosed()"); + server.stop(); + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue713Test.java b/src/test/java/org/java_websocket/issues/Issue713Test.java new file mode 100644 index 000000000..769f08f94 --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue713Test.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.issues; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import static org.junit.jupiter.api.Assertions.fail; + +public class Issue713Test { + + CountDownLatch countDownLatchString = new CountDownLatch(10); + CountDownLatch countDownLatchConnect = new CountDownLatch(10); + CountDownLatch countDownLatchBytebuffer = new CountDownLatch(10); + + @Test + public void testIllegalArgument() throws InterruptedException { + WebSocketServer server = new WebSocketServer( + new InetSocketAddress(SocketUtil.getAvailablePort())) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + + } + + @Override + public void onMessage(WebSocket conn, String message) { + + } + + @Override + public void onError(WebSocket conn, Exception ex) { + + } + + @Override + public void onStart() { + + } + }; + try { + server.broadcast((byte[]) null, null); + fail("IllegalArgumentException should be thrown"); + } catch (Exception e) { + // OK + } + try { + server.broadcast((String) null, null); + fail("IllegalArgumentException should be thrown"); + } catch (Exception e) { + // OK + } + } + + @Test + @Timeout(2000) + public void testIssue() throws Exception { + final int port = SocketUtil.getAvailablePort(); + WebSocketServer server = new WebSocketServer(new InetSocketAddress(port)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, String message) { + + } + + @Override + public void onError(WebSocket conn, Exception ex) { + + } + + @Override + public void onStart() { + try { + for (int i = 0; i < 10; i++) { + TestWebSocket tw = new TestWebSocket(port); + tw.connect(); + } + } catch (Exception e) { + fail("Exception during connect!"); + } + } + }; + server.start(); + countDownLatchConnect.await(); + server.broadcast("Hello world!"); + countDownLatchString.await(); + server.broadcast("Hello world".getBytes()); + countDownLatchBytebuffer.await(); + countDownLatchBytebuffer = new CountDownLatch(10); + server.broadcast(ByteBuffer.wrap("Hello world".getBytes())); + countDownLatchBytebuffer.await(); + + countDownLatchString = new CountDownLatch(5); + ArrayList specialList = new ArrayList(server.getConnections()); + specialList.remove(8); + specialList.remove(6); + specialList.remove(4); + specialList.remove(2); + specialList.remove(0); + server.broadcast("Hello world", specialList); + countDownLatchString.await(); + + countDownLatchBytebuffer = new CountDownLatch(5); + server.broadcast("Hello world".getBytes()); + countDownLatchBytebuffer.await(); + } + + + class TestWebSocket extends WebSocketClient { + + TestWebSocket(int port) throws URISyntaxException { + super(new URI("ws://localhost:" + port)); + } + + @Override + public void onOpen(ServerHandshake handshakedata) { + countDownLatchConnect.countDown(); + } + + @Override + public void onMessage(String message) { + countDownLatchString.countDown(); + } + + @Override + public void onMessage(ByteBuffer message) { + countDownLatchBytebuffer.countDown(); + } + + @Override + public void onClose(int code, String reason, boolean remote) { + + } + + @Override + public void onError(Exception ex) { + + } + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue732Test.java b/src/test/java/org/java_websocket/issues/Issue732Test.java new file mode 100644 index 000000000..d9e734bbe --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue732Test.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.issues; + + +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.concurrent.CountDownLatch; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.java_websocket.util.ThreadCheck; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.junit.jupiter.api.Assertions.fail; + +@ExtendWith(ThreadCheck.class) +public class Issue732Test { + + + private CountDownLatch countServerDownLatch = new CountDownLatch(1); + + @Test + @Timeout(2000) + public void testIssue() throws Exception { + int port = SocketUtil.getAvailablePort(); + final WebSocketClient webSocket = new WebSocketClient(new URI("ws://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + try { + this.reconnect(); + fail("Exception should be thrown"); + } catch (IllegalStateException e) { + // + } + } + + @Override + public void onMessage(String message) { + try { + this.reconnect(); + fail("Exception should be thrown"); + } catch (IllegalStateException e) { + send("hi"); + } + } + + @Override + public void onClose(int code, String reason, boolean remote) { + try { + this.reconnect(); + fail("Exception should be thrown"); + } catch (IllegalStateException e) { + // + } + } + + @Override + public void onError(Exception ex) { + try { + this.reconnect(); + fail("Exception should be thrown"); + } catch (IllegalStateException e) { + // + } + } + }; + WebSocketServer server = new WebSocketServer(new InetSocketAddress(port)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + conn.send("hi"); + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + countServerDownLatch.countDown(); + } + + @Override + public void onMessage(WebSocket conn, String message) { + conn.close(); + } + + @Override + public void onError(WebSocket conn, Exception ex) { + fail("There should be no onError!"); + } + + @Override + public void onStart() { + webSocket.connect(); + } + }; + server.start(); + countServerDownLatch.await(); + server.stop(); + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue764Test.java b/src/test/java/org/java_websocket/issues/Issue764Test.java new file mode 100644 index 000000000..870f79ad9 --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue764Test.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + * + */ + +package org.java_websocket.issues; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.concurrent.CountDownLatch; +import javax.net.ssl.SSLContext; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.DefaultSSLWebSocketServerFactory; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SSLContextUtil; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +public class Issue764Test { + + private CountDownLatch countClientDownLatch = new CountDownLatch(2); + private CountDownLatch countServerDownLatch = new CountDownLatch(1); + + @Test + @Timeout(2000) + public void testIssue() + throws IOException, URISyntaxException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException, UnrecoverableKeyException, CertificateException, InterruptedException { + int port = SocketUtil.getAvailablePort(); + final WebSocketClient webSocket = new WebSocketClient(new URI("wss://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + countClientDownLatch.countDown(); + countServerDownLatch.countDown(); + } + + @Override + public void onMessage(String message) { + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onError(Exception ex) { + } + }; + WebSocketServer server = new MyWebSocketServer(port, webSocket, countServerDownLatch); + + SSLContext sslContext = SSLContextUtil.getContext(); + + server.setWebSocketFactory(new DefaultSSLWebSocketServerFactory(sslContext)); + webSocket.setSocketFactory(sslContext.getSocketFactory()); + server.start(); + countServerDownLatch.await(); + webSocket.connectBlocking(); + webSocket.reconnectBlocking(); + countClientDownLatch.await(); + } + + + private static class MyWebSocketServer extends WebSocketServer { + + private final WebSocketClient webSocket; + private final CountDownLatch countServerDownLatch; + + + public MyWebSocketServer(int port, WebSocketClient webSocket, + CountDownLatch countServerDownLatch) { + super(new InetSocketAddress(port)); + this.webSocket = webSocket; + this.countServerDownLatch = countServerDownLatch; + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, String message) { + + } + + @Override + public void onError(WebSocket conn, Exception ex) { + ex.printStackTrace(); + } + + @Override + public void onStart() { + countServerDownLatch.countDown(); + } + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue765Test.java b/src/test/java/org/java_websocket/issues/Issue765Test.java new file mode 100644 index 000000000..5eff49468 --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue765Test.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + * + */ + +package org.java_websocket.issues; + +import java.io.IOException; +import java.nio.channels.ByteChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.List; +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.WebSocketServerFactory; +import org.java_websocket.drafts.Draft; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class Issue765Test { + + boolean isClosedCalled = false; + + @Test + public void testIssue() { + WebSocketServer webSocketServer = new MyWebSocketServer(); + webSocketServer.setWebSocketFactory(new LocalWebSocketFactory()); + assertFalse(isClosedCalled, "Close should not have been called yet"); + webSocketServer.setWebSocketFactory(new LocalWebSocketFactory()); + assertTrue(isClosedCalled, "Close has been called"); + } + + private static class MyWebSocketServer extends WebSocketServer { + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + + } + + @Override + public void onMessage(WebSocket conn, String message) { + + } + + @Override + public void onError(WebSocket conn, Exception ex) { + + } + + @Override + public void onStart() { + + } + } + + private class LocalWebSocketFactory implements WebSocketServerFactory { + + @Override + public WebSocketImpl createWebSocket(WebSocketAdapter a, Draft d) { + return null; + } + + @Override + public WebSocketImpl createWebSocket(WebSocketAdapter a, List drafts) { + return null; + } + + @Override + public ByteChannel wrapChannel(SocketChannel channel, SelectionKey key) throws IOException { + return null; + } + + @Override + public void close() { + isClosedCalled = true; + } + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue811Test.java b/src/test/java/org/java_websocket/issues/Issue811Test.java new file mode 100644 index 000000000..e2faf7ed2 --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue811Test.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + * + */ + +package org.java_websocket.issues; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; +import org.java_websocket.WebSocket; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +public class Issue811Test { + + private CountDownLatch countServerDownLatch = new CountDownLatch(1); + + @Test + @Timeout(2000) + public void testSetConnectionLostTimeout() throws IOException, InterruptedException { + final MyWebSocketServer server = new MyWebSocketServer( + new InetSocketAddress(SocketUtil.getAvailablePort())); + server.start(); + new Thread(new Runnable() { + @Override + public void run() { + while (server.getConnectionLostTimeout() == 60) { + // do nothing + } + // will never reach this statement + countServerDownLatch.countDown(); + } + }).start(); + Thread.sleep(1000); + server.setConnectionLostTimeout(20); + countServerDownLatch.await(); + } + + private static class MyWebSocketServer extends WebSocketServer { + + public MyWebSocketServer(InetSocketAddress inetSocketAddress) { + super(inetSocketAddress); + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + + } + + @Override + public void onMessage(WebSocket conn, String message) { + + } + + @Override + public void onError(WebSocket conn, Exception ex) { + + } + + @Override + public void onStart() { + + } + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue825Test.java b/src/test/java/org/java_websocket/issues/Issue825Test.java new file mode 100644 index 000000000..f846f9571 --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue825Test.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + * + */ + +package org.java_websocket.issues; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.concurrent.CountDownLatch; +import javax.net.ssl.SSLContext; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.DefaultSSLWebSocketServerFactory; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SSLContextUtil; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +public class Issue825Test { + + + @Test + @Timeout(15000) + public void testIssue() + throws IOException, URISyntaxException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException, UnrecoverableKeyException, CertificateException, InterruptedException { + final CountDownLatch countClientOpenLatch = new CountDownLatch(3); + final CountDownLatch countClientMessageLatch = new CountDownLatch(3); + final CountDownLatch countServerDownLatch = new CountDownLatch(1); + int port = SocketUtil.getAvailablePort(); + final WebSocketClient webSocket = new WebSocketClient(new URI("wss://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + countClientOpenLatch.countDown(); + } + + @Override + public void onMessage(String message) { + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onError(Exception ex) { + } + }; + WebSocketServer server = new MyWebSocketServer(port, countServerDownLatch, + countClientMessageLatch); + + // load up the key store + SSLContext sslContext = SSLContextUtil.getContext(); + + server.setWebSocketFactory(new DefaultSSLWebSocketServerFactory(sslContext)); + webSocket.setSocketFactory(sslContext.getSocketFactory()); + server.start(); + countServerDownLatch.await(); + webSocket.connectBlocking(); + webSocket.send("hi"); + Thread.sleep(10); + webSocket.closeBlocking(); + + //Disconnect manually and reconnect blocking + webSocket.reconnectBlocking(); + webSocket.send("it's"); + Thread.sleep(10000); + webSocket.closeBlocking(); + //Disconnect manually and reconnect + webSocket.reconnect(); + countClientOpenLatch.await(); + webSocket.send("me"); + Thread.sleep(100); + webSocket.closeBlocking(); + countClientMessageLatch.await(); + } + + + private static class MyWebSocketServer extends WebSocketServer { + + private final CountDownLatch countServerLatch; + private final CountDownLatch countClientMessageLatch; + + + public MyWebSocketServer(int port, CountDownLatch serverDownLatch, + CountDownLatch countClientMessageLatch) { + super(new InetSocketAddress(port)); + this.countServerLatch = serverDownLatch; + this.countClientMessageLatch = countClientMessageLatch; + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, String message) { + countClientMessageLatch.countDown(); + } + + @Override + public void onError(WebSocket conn, Exception ex) { + ex.printStackTrace(); + } + + @Override + public void onStart() { + countServerLatch.countDown(); + } + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue834Test.java b/src/test/java/org/java_websocket/issues/Issue834Test.java new file mode 100644 index 000000000..abd121179 --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue834Test.java @@ -0,0 +1,53 @@ +package org.java_websocket.issues; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Set; +import org.java_websocket.WebSocket; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +public class Issue834Test { + + @Test + @Timeout(1000) + public void testNoNewThreads() throws InterruptedException { + + Set threadSet1 = Thread.getAllStackTraces().keySet(); + + new WebSocketServer(new InetSocketAddress(SocketUtil.getAvailablePort())) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, String message) { + } + + @Override + public void onError(WebSocket conn, Exception ex) { + } + + @Override + public void onStart() { + } + }; + + Set threadSet2 = Thread.getAllStackTraces().keySet(); + + //checks that no threads are started in the constructor + assertEquals(threadSet1, threadSet2); + + } + +} diff --git a/src/test/java/org/java_websocket/issues/Issue847Test.java b/src/test/java/org/java_websocket/issues/Issue847Test.java new file mode 100644 index 000000000..870e18b07 --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue847Test.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + * + */ + +package org.java_websocket.issues; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Scanner; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.framing.BinaryFrame; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.util.Charsetfunctions; +import org.java_websocket.util.KeyUtils; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.fail; + +public class Issue847Test { + + private static Thread thread; + private static ServerSocket serverSocket; + + private static int port; + private static final int NUMBER_OF_TESTS = 20; + + public static Collection data() { + List ret = new ArrayList(NUMBER_OF_TESTS); + for (int i = 1; i <= NUMBER_OF_TESTS + 1; i++) { + ret.add(new Integer[]{(int) Math.round(Math.pow(2, i))}); + } + return ret; + } + + @BeforeAll + public static void startServer() throws Exception { + port = SocketUtil.getAvailablePort(); + thread = new Thread( + new Runnable() { + public void run() { + try { + serverSocket = new ServerSocket(port); + serverSocket.setReuseAddress(true); + while (true) { + Socket client = null; + try { + client = serverSocket.accept(); + Scanner in = new Scanner(client.getInputStream()); + String input; + String seckey = ""; + String testCase; + boolean useMask = false; + int size = 0; + OutputStream os = client.getOutputStream(); + while (in.hasNext()) { + input = in.nextLine(); + if (input.startsWith("Sec-WebSocket-Key: ")) { + seckey = input.split(" ")[1]; + } + //Last + if (input.startsWith("Upgrade")) { + os.write(Charsetfunctions.asciiBytes( + "HTTP/1.1 101 Websocket Connection Upgrade\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n" + + KeyUtils.getSecKey(seckey) + "\r\n")); + os.flush(); + Thread.sleep(10); + Draft_6455 draft_6455 = new Draft_6455(); + BinaryFrame binaryFrame = new BinaryFrame(); + binaryFrame.setPayload(ByteBuffer.allocate(size)); + binaryFrame.setTransferemasked(useMask); + ByteBuffer byteBuffer = draft_6455.createBinaryFrame(binaryFrame); + byte[] bytes = byteBuffer.array(); + int first = size / 2; + os.write(bytes, 0, first); + os.flush(); + Thread.sleep(5); + os.write(bytes, first, bytes.length - first); + os.flush(); + break; + } + if (input.startsWith("GET ")) { + testCase = input.split(" ")[1]; + String[] strings = testCase.split("/"); + useMask = Boolean.valueOf(strings[1]); + size = Integer.valueOf(strings[2]); + } + } + } catch (IOException e) { + // + } + } + } catch (Exception e) { + e.printStackTrace(); + fail("There should be no exception"); + } + } + }); + thread.start(); + } + + @AfterAll + public static void successTests() throws IOException { + serverSocket.close(); + thread.interrupt(); + } + + @ParameterizedTest() + @Timeout(5000) + @MethodSource("data") + public void testIncrementalFrameUnmasked(int size) throws Exception { + testIncrementalFrame(false, size); + } + + @ParameterizedTest() + @Timeout(5000) + @MethodSource("data") + public void testIncrementalFrameMsked(int size) throws Exception { + testIncrementalFrame(true, size); + } + + + private void testIncrementalFrame(boolean useMask, int size) throws Exception { + final boolean[] threadReturned = {false}; + final WebSocketClient webSocketClient = new WebSocketClient( + new URI("ws://localhost:" + port + "/" + useMask + "/" + size)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + } + + @Override + public void onMessage(String message) { + fail("There should not be a message!"); + } + + public void onMessage(ByteBuffer message) { + threadReturned[0] = true; + close(); + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onError(Exception ex) { + ex.printStackTrace(); + } + }; + Thread finalThread = new Thread(webSocketClient); + finalThread.start(); + finalThread.join(); + if (!threadReturned[0]) { + fail("Error"); + } + } +} + diff --git a/src/test/java/org/java_websocket/issues/Issue855Test.java b/src/test/java/org/java_websocket/issues/Issue855Test.java new file mode 100644 index 000000000..f094f067e --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue855Test.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + * + */ + +package org.java_websocket.issues; + +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.concurrent.CountDownLatch; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +public class Issue855Test { + + CountDownLatch countServerDownLatch = new CountDownLatch(1); + CountDownLatch countDownLatch = new CountDownLatch(1); + + @Test + @Timeout(2000) + public void testIssue() throws Exception { + int port = SocketUtil.getAvailablePort(); + WebSocketClient webSocket = new WebSocketClient(new URI("ws://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + countDownLatch.countDown(); + } + + @Override + public void onMessage(String message) { + + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onError(Exception ex) { + + } + }; + WebSocketServer server = new WebSocketServer(new InetSocketAddress(port)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + conn.close(); + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, String message) { + + } + + @Override + public void onError(WebSocket conn, Exception ex) { + + } + + @Override + public void onStart() { + countServerDownLatch.countDown(); + } + }; + new Thread(server).start(); + countServerDownLatch.await(); + webSocket.connectBlocking(); + server.stop(); + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue879Test.java b/src/test/java/org/java_websocket/issues/Issue879Test.java new file mode 100644 index 000000000..504bec922 --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue879Test.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + * + */ + +package org.java_websocket.issues; + +import java.io.IOException; +import java.net.BindException; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class Issue879Test { + + private static final int NUMBER_OF_TESTS = 20; + + @Timeout(10000) + @ParameterizedTest() + @MethodSource("data") + public void QuickStopTest(int numberOfConnections) throws InterruptedException, URISyntaxException { + final boolean[] wasBindException = {false}; + final boolean[] wasConcurrentException = new boolean[1]; + final CountDownLatch countDownLatch = new CountDownLatch(1); + + class SimpleServer extends WebSocketServer { + + public SimpleServer(InetSocketAddress address) { + super(address); + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + broadcast("new connection: " + handshake + .getResourceDescriptor()); //This method sends a message to all clients connected + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, String message) { + } + + @Override + public void onMessage(WebSocket conn, ByteBuffer message) { + } + + @Override + public void onError(WebSocket conn, Exception ex) { + if (ex instanceof BindException) { + wasBindException[0] = true; + } + if (ex instanceof ConcurrentModificationException) { + wasConcurrentException[0] = true; + } + } + + @Override + public void onStart() { + countDownLatch.countDown(); + } + } + int port = SocketUtil.getAvailablePort(); + SimpleServer serverA = new SimpleServer(new InetSocketAddress(port)); + SimpleServer serverB = new SimpleServer(new InetSocketAddress(port)); + serverA.start(); + countDownLatch.await(); + List clients = startNewConnections(numberOfConnections, port); + Thread.sleep(100); + int numberOfConnected = 0; + for (WebSocketClient client : clients) { + if (client.isOpen()) { + numberOfConnected++; + } + } + // Number will differ since we use connect instead of connectBlocking + // System.out.println(numberOfConnected + " " + numberOfConnections); + + serverA.stop(); + serverB.start(); + clients.clear(); + assertFalse(wasBindException[0], "There was a BindException"); + assertFalse(wasConcurrentException[0], "There was a ConcurrentModificationException"); + } + + public static Collection data() { + List ret = new ArrayList(NUMBER_OF_TESTS); + for (int i = 0; i < NUMBER_OF_TESTS; i++) { + ret.add(new Integer[]{25 + i * 25}); + } + return ret; + } + + private List startNewConnections(int numberOfConnections, int port) + throws URISyntaxException, InterruptedException { + List clients = new ArrayList(numberOfConnections); + for (int i = 0; i < numberOfConnections; i++) { + WebSocketClient client = new SimpleClient(new URI("ws://localhost:" + port)); + client.connect(); + Thread.sleep(1); + clients.add(client); + } + return clients; + } + + class SimpleClient extends WebSocketClient { + + public SimpleClient(URI serverUri) { + super(serverUri); + } + + @Override + public void onOpen(ServerHandshake handshakedata) { + + } + + @Override + public void onMessage(String message) { + + } + + @Override + public void onClose(int code, String reason, boolean remote) { + + } + + @Override + public void onError(Exception ex) { + + } + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue890Test.java b/src/test/java/org/java_websocket/issues/Issue890Test.java new file mode 100644 index 000000000..82c991bab --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue890Test.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + * + */ + +package org.java_websocket.issues; + + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.concurrent.CountDownLatch; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.DefaultSSLWebSocketServerFactory; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SSLContextUtil; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import static org.junit.jupiter.api.Assertions.*; + +public class Issue890Test { + + + @Test + @Timeout(4000) + public void testWithSSLSession() + throws IOException, URISyntaxException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException, UnrecoverableKeyException, CertificateException, InterruptedException { + int port = SocketUtil.getAvailablePort(); + final CountDownLatch countServerDownLatch = new CountDownLatch(1); + final WebSocketClient webSocket = new WebSocketClient(new URI("wss://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + countServerDownLatch.countDown(); + } + + @Override + public void onMessage(String message) { + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onError(Exception ex) { + } + }; + TestResult testResult = new TestResult(); + WebSocketServer server = new MyWebSocketServer(port, testResult, countServerDownLatch); + SSLContext sslContext = SSLContextUtil.getContext(); + + server.setWebSocketFactory(new DefaultSSLWebSocketServerFactory(sslContext)); + webSocket.setSocketFactory(sslContext.getSocketFactory()); + server.start(); + countServerDownLatch.await(); + webSocket.connectBlocking(); + assertTrue(testResult.hasSSLSupport); + assertNotNull(testResult.sslSession); + } + + @Test + @Timeout(4000) + public void testWithOutSSLSession() + throws IOException, URISyntaxException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException, UnrecoverableKeyException, CertificateException, InterruptedException { + int port = SocketUtil.getAvailablePort(); + final CountDownLatch countServerDownLatch = new CountDownLatch(1); + final WebSocketClient webSocket = new WebSocketClient(new URI("ws://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + countServerDownLatch.countDown(); + } + + @Override + public void onMessage(String message) { + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onError(Exception ex) { + } + }; + TestResult testResult = new TestResult(); + WebSocketServer server = new MyWebSocketServer(port, testResult, countServerDownLatch); + server.start(); + countServerDownLatch.await(); + webSocket.connectBlocking(); + assertFalse(testResult.hasSSLSupport); + assertNull(testResult.sslSession); + } + + + private static class MyWebSocketServer extends WebSocketServer { + + private final TestResult testResult; + private final CountDownLatch countServerDownLatch; + + public MyWebSocketServer(int port, TestResult testResult, CountDownLatch countServerDownLatch) { + super(new InetSocketAddress(port)); + this.testResult = testResult; + this.countServerDownLatch = countServerDownLatch; + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + testResult.hasSSLSupport = conn.hasSSLSupport(); + try { + testResult.sslSession = conn.getSSLSession(); + } catch (IllegalArgumentException e) { + // Ignore + } + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, String message) { + + } + + @Override + public void onError(WebSocket conn, Exception ex) { + ex.printStackTrace(); + } + + @Override + public void onStart() { + countServerDownLatch.countDown(); + } + } + + private class TestResult { + + public SSLSession sslSession = null; + + public boolean hasSSLSupport = false; + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue900Test.java b/src/test/java/org/java_websocket/issues/Issue900Test.java new file mode 100644 index 000000000..edc966e9f --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue900Test.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + * + */ + +package org.java_websocket.issues; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.WrappedByteChannel; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +public class Issue900Test { + + CountDownLatch serverStartLatch = new CountDownLatch(1); + CountDownLatch closeCalledLatch = new CountDownLatch(1); + + @Test + @Timeout(2000) + public void testIssue() throws Exception { + int port = SocketUtil.getAvailablePort(); + final WebSocketClient client = new WebSocketClient(new URI("ws://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + + } + + @Override + public void onMessage(String message) { + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onError(Exception ex) { + + } + }; + WebSocketServer server = new WebSocketServer(new InetSocketAddress(port)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + closeCalledLatch.countDown(); + } + + @Override + public void onMessage(WebSocket conn, String message) { + + } + + @Override + public void onError(WebSocket conn, Exception ex) { + + } + + @Override + public void onStart() { + serverStartLatch.countDown(); + } + }; + new Thread(server).start(); + serverStartLatch.await(); + client.connectBlocking(); + WebSocketImpl websocketImpl = (WebSocketImpl) new ArrayList(server.getConnections()) + .get(0); + websocketImpl.setChannel(new ExceptionThrowingByteChannel()); + server.broadcast("test"); + closeCalledLatch.await(); + } + + class ExceptionThrowingByteChannel implements WrappedByteChannel { + + @Override + public boolean isNeedWrite() { + return true; + } + + @Override + public void writeMore() throws IOException { + throw new IOException(); + } + + @Override + public boolean isNeedRead() { + return true; + } + + @Override + public int readMore(ByteBuffer dst) throws IOException { + throw new IOException(); + } + + @Override + public boolean isBlocking() { + return false; + } + + @Override + public int read(ByteBuffer dst) throws IOException { + throw new IOException(); + } + + @Override + public int write(ByteBuffer src) throws IOException { + throw new IOException(); + } + + @Override + public boolean isOpen() { + return false; + } + + @Override + public void close() throws IOException { + throw new IOException(); + } + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue941Test.java b/src/test/java/org/java_websocket/issues/Issue941Test.java new file mode 100644 index 000000000..eaf50fab3 --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue941Test.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.issues; + + +import java.net.InetSocketAddress; +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.PingFrame; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +public class Issue941Test { + + private CountDownLatch pingLatch = new CountDownLatch(1); + private CountDownLatch pongLatch = new CountDownLatch(1); + private byte[] pingBuffer, receivedPingBuffer, pongBuffer; + + @Test + public void testIssue() throws Exception { + int port = SocketUtil.getAvailablePort(); + WebSocketClient client = new WebSocketClient(new URI("ws://localhost:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + } + + @Override + public void onMessage(String message) { + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onError(Exception ex) { + } + + @Override + public PingFrame onPreparePing(WebSocket conn) { + PingFrame frame = new PingFrame(); + pingBuffer = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + frame.setPayload(ByteBuffer.wrap(pingBuffer)); + return frame; + } + + @Override + public void onWebsocketPong(WebSocket conn, Framedata f) { + pongBuffer = f.getPayloadData().array(); + pongLatch.countDown(); + } + }; + + WebSocketServer server = new WebSocketServer(new InetSocketAddress(port)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, String message) { + } + + @Override + public void onError(WebSocket conn, Exception ex) { + } + + @Override + public void onStart() { + } + + @Override + public void onWebsocketPing(WebSocket conn, Framedata f) { + receivedPingBuffer = f.getPayloadData().array(); + super.onWebsocketPing(conn, f); + pingLatch.countDown(); + } + }; + + server.start(); + client.connectBlocking(); + client.setConnectionLostTimeout(1); + pingLatch.await(); + assertArrayEquals(pingBuffer, receivedPingBuffer); + pongLatch.await(); + assertArrayEquals(pingBuffer, pongBuffer); + } +} diff --git a/src/test/java/org/java_websocket/issues/Issue962Test.java b/src/test/java/org/java_websocket/issues/Issue962Test.java new file mode 100644 index 000000000..56baa6d34 --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue962Test.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2010-2020 Olivier Ayache + * + * 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. + */ +package org.java_websocket.issues; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.util.concurrent.CountDownLatch; +import javax.net.SocketFactory; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import static org.junit.jupiter.api.Assertions.*; + +public class Issue962Test { + + private static class TestSocketFactory extends SocketFactory { + + private final SocketFactory socketFactory = SocketFactory.getDefault(); + private final String bindingAddress; + + public TestSocketFactory(String bindingAddress) { + this.bindingAddress = bindingAddress; + } + + @Override + public Socket createSocket() throws IOException { + Socket socket = socketFactory.createSocket(); + socket.bind(new InetSocketAddress(bindingAddress, 0)); + return socket; + } + + @Override + public Socket createSocket(String string, int i) throws IOException, UnknownHostException { + Socket socket = socketFactory.createSocket(string, i); + socket.bind(new InetSocketAddress(bindingAddress, 0)); + return socket; + } + + @Override + public Socket createSocket(String string, int i, InetAddress ia, int i1) + throws IOException, UnknownHostException { + throw new UnsupportedOperationException(); + } + + @Override + public Socket createSocket(InetAddress ia, int i) throws IOException { + Socket socket = socketFactory.createSocket(ia, i); + socket.bind(new InetSocketAddress(bindingAddress, 0)); + return socket; + } + + @Override + public Socket createSocket(InetAddress ia, int i, InetAddress ia1, int i1) throws IOException { + throw new UnsupportedOperationException(); + } + + } + + @Test + @Timeout(2000) + public void testIssue() throws IOException, URISyntaxException, InterruptedException { + int port = SocketUtil.getAvailablePort(); + WebSocketClient client = new WebSocketClient(new URI("ws://127.0.0.1:" + port)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + } + + @Override + public void onMessage(String message) { + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onError(Exception ex) { + fail(ex.toString() + " should not occur"); + } + }; + + String bindingAddress = "127.0.0.1"; + CountDownLatch serverStartedLatch = new CountDownLatch(1); + + client.setSocketFactory(new TestSocketFactory(bindingAddress)); + + WebSocketServer server = new WebSocketServer(new InetSocketAddress(port)) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, String message) { + } + + @Override + public void onError(WebSocket conn, Exception ex) { + } + + @Override + public void onStart() { + serverStartedLatch.countDown(); + } + }; + + server.start(); + serverStartedLatch.await(); + client.connectBlocking(); + assertEquals(bindingAddress, client.getSocket().getLocalAddress().getHostAddress()); + assertNotEquals(0, client.getSocket().getLocalPort()); + assertTrue(client.getSocket().isConnected()); + } + +} diff --git a/src/test/java/org/java_websocket/issues/Issue997Test.java b/src/test/java/org/java_websocket/issues/Issue997Test.java new file mode 100644 index 000000000..e72a338c2 --- /dev/null +++ b/src/test/java/org/java_websocket/issues/Issue997Test.java @@ -0,0 +1,220 @@ +package org.java_websocket.issues; + +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + * + */ + + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLParameters; + +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.DefaultSSLWebSocketServerFactory; +import org.java_websocket.server.WebSocketServer; +import org.java_websocket.util.SSLContextUtil; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class Issue997Test { + + @Test + @Timeout(2000) + public void test_localServer_ServerLocalhost_Client127_CheckActive() + throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException, KeyStoreException, IOException, URISyntaxException, InterruptedException { + SSLWebSocketClient client = testIssueWithLocalServer("127.0.0.1", SocketUtil.getAvailablePort(), + SSLContextUtil.getLocalhostOnlyContext(), SSLContextUtil.getLocalhostOnlyContext(), + "HTTPS"); + assertFalse(client.onOpen, "client is not open"); + assertTrue(client.onSSLError, "client has caught a SSLHandshakeException"); + } + + @Test + @Timeout(2000) + public void test_localServer_ServerLocalhost_Client127_CheckInactive() + throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException, KeyStoreException, IOException, URISyntaxException, InterruptedException { + SSLWebSocketClient client = testIssueWithLocalServer("127.0.0.1", SocketUtil.getAvailablePort(), + SSLContextUtil.getLocalhostOnlyContext(), SSLContextUtil.getLocalhostOnlyContext(), ""); + assertTrue(client.onOpen, "client is open"); + assertFalse(client.onSSLError, "client has not caught a SSLHandshakeException"); + } + + @Test + @Timeout(2000) + public void test_localServer_ServerLocalhost_Client127_CheckDefault() + throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException, KeyStoreException, IOException, URISyntaxException, InterruptedException { + SSLWebSocketClient client = testIssueWithLocalServer("127.0.0.1", SocketUtil.getAvailablePort(), + SSLContextUtil.getLocalhostOnlyContext(), SSLContextUtil.getLocalhostOnlyContext(), null); + assertFalse(client.onOpen, "client is not open"); + assertTrue(client.onSSLError, "client has caught a SSLHandshakeException"); + } + + @Test + @Timeout(2000) + public void test_localServer_ServerLocalhost_ClientLocalhost_CheckActive() + throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException, KeyStoreException, IOException, URISyntaxException, InterruptedException { + SSLWebSocketClient client = testIssueWithLocalServer("localhost", SocketUtil.getAvailablePort(), + SSLContextUtil.getLocalhostOnlyContext(), SSLContextUtil.getLocalhostOnlyContext(), + "HTTPS"); + assertTrue(client.onOpen, "client is open"); + assertFalse(client.onSSLError, "client has not caught a SSLHandshakeException"); + } + + @Test + @Timeout(2000) + public void test_localServer_ServerLocalhost_ClientLocalhost_CheckInactive() + throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException, KeyStoreException, IOException, URISyntaxException, InterruptedException { + SSLWebSocketClient client = testIssueWithLocalServer("localhost", SocketUtil.getAvailablePort(), + SSLContextUtil.getLocalhostOnlyContext(), SSLContextUtil.getLocalhostOnlyContext(), ""); + assertTrue(client.onOpen, "client is open"); + assertFalse(client.onSSLError, "client has not caught a SSLHandshakeException"); + } + + @Test + @Timeout(2000) + public void test_localServer_ServerLocalhost_ClientLocalhost_CheckDefault() + throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException, KeyStoreException, IOException, URISyntaxException, InterruptedException { + SSLWebSocketClient client = testIssueWithLocalServer("localhost", SocketUtil.getAvailablePort(), + SSLContextUtil.getLocalhostOnlyContext(), SSLContextUtil.getLocalhostOnlyContext(), null); + assertTrue(client.onOpen, "client is open"); + assertFalse(client.onSSLError, "client has not caught a SSLHandshakeException"); + } + + + public SSLWebSocketClient testIssueWithLocalServer(String address, int port, + SSLContext serverContext, SSLContext clientContext, String endpointIdentificationAlgorithm) + throws IOException, URISyntaxException, InterruptedException { + CountDownLatch countServerDownLatch = new CountDownLatch(1); + SSLWebSocketClient client = new SSLWebSocketClient(address, port, + endpointIdentificationAlgorithm); + WebSocketServer server = new SSLWebSocketServer(port, countServerDownLatch); + + server.setWebSocketFactory(new DefaultSSLWebSocketServerFactory(serverContext)); + if (clientContext != null) { + client.setSocketFactory(clientContext.getSocketFactory()); + } + server.start(); + countServerDownLatch.await(); + client.connectBlocking(1, TimeUnit.SECONDS); + return client; + } + + + private static class SSLWebSocketClient extends WebSocketClient { + + private final String endpointIdentificationAlgorithm; + public boolean onSSLError = false; + public boolean onOpen = false; + + public SSLWebSocketClient(String address, int port, String endpointIdentificationAlgorithm) + throws URISyntaxException { + super(new URI("wss://" + address + ':' + port)); + this.endpointIdentificationAlgorithm = endpointIdentificationAlgorithm; + } + + @Override + public void onOpen(ServerHandshake handshakedata) { + this.onOpen = true; + } + + @Override + public void onMessage(String message) { + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onError(Exception ex) { + if (ex instanceof SSLHandshakeException) { + this.onSSLError = true; + } + } + + @Override + protected void onSetSSLParameters(SSLParameters sslParameters) { + // Always call super to ensure hostname validation is active by default + super.onSetSSLParameters(sslParameters); + if (endpointIdentificationAlgorithm != null) { + sslParameters.setEndpointIdentificationAlgorithm(endpointIdentificationAlgorithm); + } + } + + } + + + private static class SSLWebSocketServer extends WebSocketServer { + + private final CountDownLatch countServerDownLatch; + + + public SSLWebSocketServer(int port, CountDownLatch countServerDownLatch) { + super(new InetSocketAddress(port)); + this.countServerDownLatch = countServerDownLatch; + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, String message) { + + } + + @Override + public void onError(WebSocket conn, Exception ex) { + ex.printStackTrace(); + } + + @Override + public void onStart() { + countServerDownLatch.countDown(); + } + } +} diff --git a/src/test/java/org/java_websocket/keystore.jks b/src/test/java/org/java_websocket/keystore.jks new file mode 100644 index 000000000..2ff34420d Binary files /dev/null and b/src/test/java/org/java_websocket/keystore.jks differ diff --git a/src/test/java/org/java_websocket/keystore_localhost_only.jks b/src/test/java/org/java_websocket/keystore_localhost_only.jks new file mode 100644 index 000000000..c18383b0b Binary files /dev/null and b/src/test/java/org/java_websocket/keystore_localhost_only.jks differ diff --git a/src/test/java/org/java_websocket/misc/OpeningHandshakeRejectionTest.java b/src/test/java/org/java_websocket/misc/OpeningHandshakeRejectionTest.java new file mode 100644 index 000000000..8290d5244 --- /dev/null +++ b/src/test/java/org/java_websocket/misc/OpeningHandshakeRejectionTest.java @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.misc; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URI; +import java.util.Scanner; +import java.util.concurrent.CountDownLatch; + +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.util.Charsetfunctions; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class OpeningHandshakeRejectionTest { + + private int port = -1; + private Thread thread; + private ServerSocket serverSocket; + + private static final String additionalHandshake = "Upgrade: websocket\r\nConnection: Upgrade\r\n\r\n"; + + public void startServer() throws InterruptedException { + this.port = SocketUtil.getAvailablePort(); + this.thread = new Thread( + () -> { + try { + serverSocket = new ServerSocket(port); + serverSocket.setReuseAddress(true); + while (true) { + Socket client = null; + try { + client = serverSocket.accept(); + Scanner in = new Scanner(client.getInputStream()); + if (!in.hasNextLine()) { + continue; + } + String input = in.nextLine(); + String testCase = input.split(" ")[1]; + OutputStream os = client.getOutputStream(); + if ("/0".equals(testCase)) { + os.write(Charsetfunctions + .asciiBytes("HTTP/1.1 100 Switching Protocols\r\n" + additionalHandshake)); + os.flush(); + } + if ("/1".equals(testCase)) { + os.write(Charsetfunctions + .asciiBytes("HTTP/1.0 100 Switching Protocols\r\n" + additionalHandshake)); + os.flush(); + } + if ("/2".equals(testCase)) { + os.write(Charsetfunctions + .asciiBytes("HTTP 100 Switching Protocols\r\n" + additionalHandshake)); + os.flush(); + } + if ("/3".equals(testCase)) { + os.write(Charsetfunctions + .asciiBytes("HTTP/1.1 200 Switching Protocols\r\n" + additionalHandshake)); + os.flush(); + } + if ("/4".equals(testCase)) { + os.write(Charsetfunctions + .asciiBytes("HTTP 101 Switching Protocols\r\n" + additionalHandshake)); + os.flush(); + } + if ("/5".equals(testCase)) { + os.write(Charsetfunctions + .asciiBytes("HTTP/1.1 404 Switching Protocols\r\n" + additionalHandshake)); + os.flush(); + } + if ("/6".equals(testCase)) { + os.write(Charsetfunctions + .asciiBytes("HTTP/2.0 404 Switching Protocols\r\n" + additionalHandshake)); + os.flush(); + } + if ("/7".equals(testCase)) { + os.write(Charsetfunctions + .asciiBytes("HTTP/1.1 500 Switching Protocols\r\n" + additionalHandshake)); + os.flush(); + } + if ("/8".equals(testCase)) { + os.write(Charsetfunctions + .asciiBytes("GET 302 Switching Protocols\r\n" + additionalHandshake)); + os.flush(); + } + if ("/9".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes( + "GET HTTP/1.1 101 Switching Protocols\r\n" + additionalHandshake)); + os.flush(); + } + if ("/10".equals(testCase)) { + os.write(Charsetfunctions + .asciiBytes("HTTP/1.1 101 Switching Protocols\r\n" + additionalHandshake)); + os.flush(); + } + if ("/11".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes( + "HTTP/1.1 101 Websocket Connection Upgrade\r\n" + additionalHandshake)); + os.flush(); + } + } catch (IOException e) { + // + } + } + } catch (Exception e) { + fail("There should not be an exception: " + e.getMessage() + " Port: " + port); + } + }); + this.thread.start(); + } + + @AfterEach + public void cleanUp() throws IOException { + if (serverSocket != null) { + serverSocket.close(); + } + if (thread != null) { + thread.interrupt(); + } + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase0() throws Exception { + testHandshakeRejection(0); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase1() throws Exception { + testHandshakeRejection(1); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase2() throws Exception { + testHandshakeRejection(2); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase3() throws Exception { + testHandshakeRejection(3); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase4() throws Exception { + testHandshakeRejection(4); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase5() throws Exception { + testHandshakeRejection(5); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase6() throws Exception { + testHandshakeRejection(6); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase7() throws Exception { + testHandshakeRejection(7); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase8() throws Exception { + testHandshakeRejection(8); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase9() throws Exception { + testHandshakeRejection(9); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase10() throws Exception { + testHandshakeRejection(10); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase11() throws Exception { + testHandshakeRejection(11); + } + + private void testHandshakeRejection(int i) throws Exception { + startServer(); + assertTrue(SocketUtil.waitForServerToStart(this.port), "Server Start Status"); + final int finalI = i; + final CountDownLatch countDownLatch = new CountDownLatch(1); + WebSocketClient webSocketClient = new WebSocketClient( + new URI("ws://localhost:" + this.port + "/" + finalI)) { + @Override + public void onOpen(ServerHandshake handshakedata) { + fail("There should not be a connection!"); + } + + @Override + public void onMessage(String message) { + fail("There should not be a message!"); + } + + @Override + public void onClose(int code, String reason, boolean remote) { + if (finalI != 10 && finalI != 11) { + if (code != CloseFrame.PROTOCOL_ERROR) { + fail("There should be a protocol error!"); + } else if (reason.startsWith("Invalid status code received:") || reason + .startsWith("Invalid status line received:")) { + countDownLatch.countDown(); + } else { + fail("The reason should be included!"); + } + } else { + //Since we do not include a correct Sec-WebSocket-Accept, onClose will be called with reason 'Draft refuses handshake' + if (!reason.endsWith("refuses handshake")) { + fail("onClose should not be called!"); + } else { + countDownLatch.countDown(); + } + } + } + + @Override + public void onError(Exception ex) { + fail("There should not be an exception: " + ex.getMessage() + " Port: " + port); + } + }; + final AssertionError[] exc = new AssertionError[1]; + exc[0] = null; + Thread finalThread = new Thread(new Runnable() { + @Override + public void run() { + try { + webSocketClient.run(); + } catch (AssertionError e) { + exc[0] = e; + countDownLatch.countDown(); + } + } + + }); + finalThread.start(); + finalThread.join(); + if (exc[0] != null) { + throw exc[0]; + } + countDownLatch.await(); + } +} diff --git a/src/test/java/org/java_websocket/protocols/ProtocolHandshakeRejectionTest.java b/src/test/java/org/java_websocket/protocols/ProtocolHandshakeRejectionTest.java new file mode 100644 index 000000000..c8c1d69e3 --- /dev/null +++ b/src/test/java/org/java_websocket/protocols/ProtocolHandshakeRejectionTest.java @@ -0,0 +1,665 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.protocols; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URI; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Scanner; +import java.util.concurrent.CountDownLatch; + +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.extensions.IExtension; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.util.Base64; +import org.java_websocket.util.Charsetfunctions; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +public class ProtocolHandshakeRejectionTest { + + private static final String additionalHandshake = "HTTP/1.1 101 Websocket Connection Upgrade\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n"; + private Thread thread; + private ServerSocket serverSocket; + + private int port = -1; + + public void startServer() throws InterruptedException { + port = SocketUtil.getAvailablePort(); + thread = new Thread( + () -> { + try { + serverSocket = new ServerSocket(port); + serverSocket.setReuseAddress(true); + while (true) { + Socket client = null; + try { + client = serverSocket.accept(); + Scanner in = new Scanner(client.getInputStream()); + if (!in.hasNextLine()) { + continue; + } + String input = in.nextLine(); + String testCase = input.split(" ")[1]; + String seckey = ""; + String secproc = ""; + while (in.hasNext()) { + input = in.nextLine(); + if (input.startsWith("Sec-WebSocket-Key: ")) { + seckey = input.split(" ")[1]; + } + if (input.startsWith("Sec-WebSocket-Protocol: ")) { + secproc = input.split(" ")[1]; + } + //Last + if (input.startsWith("Upgrade")) { + break; + } + } + OutputStream os = client.getOutputStream(); + if ("/0".equals(testCase)) { + os.write(Charsetfunctions + .asciiBytes(additionalHandshake + getSecKey(seckey) + "\r\n")); + os.flush(); + } + if ("/1".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes( + additionalHandshake + getSecKey(seckey) + "Sec-WebSocket-Protocol: chat" + + "\r\n\r\n")); + os.flush(); + } + if ("/2".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + getSecKey(seckey) + + "Sec-WebSocket-Protocol: chat, chat2" + "\r\n\r\n")); + os.flush(); + } + if ("/3".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + getSecKey(seckey) + + "Sec-WebSocket-Protocol: chat,chat2,chat3" + "\r\n\r\n")); + os.flush(); + } + if ("/4".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + getSecKey(seckey) + + "Sec-WebSocket-Protocol: chat\r\nSec-WebSocket-Protocol: chat2,chat3" + + "\r\n\r\n")); + os.flush(); + } + if ("/5".equals(testCase)) { + os.write(Charsetfunctions + .asciiBytes(additionalHandshake + getSecKey(seckey) + "\r\n")); + os.flush(); + } + if ("/6".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes( + additionalHandshake + getSecKey(seckey) + "Sec-WebSocket-Protocol: chat" + + "\r\n\r\n")); + os.flush(); + } + if ("/7".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + getSecKey(seckey) + + "Sec-WebSocket-Protocol: chat, chat2" + "\r\n\r\n")); + os.flush(); + } + if ("/8".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + getSecKey(seckey) + + "Sec-WebSocket-Protocol: chat,chat2,chat3" + "\r\n\r\n")); + os.flush(); + } + if ("/9".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + getSecKey(seckey) + + "Sec-WebSocket-Protocol: chat\r\nSec-WebSocket-Protocol: chat2,chat3" + + "\r\n\r\n")); + os.flush(); + } + if ("/10".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + getSecKey(seckey) + + "Sec-WebSocket-Protocol: chat2,chat3" + "\r\n\r\n")); + os.flush(); + } + if ("/11".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + getSecKey(seckey) + + "Sec-WebSocket-Protocol: chat2, chat3" + "\r\n\r\n")); + os.flush(); + } + if ("/12".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + getSecKey(seckey) + + "Sec-WebSocket-Protocol: chat2\r\nSec-WebSocket-Protocol: chat3" + + "\r\n\r\n")); + os.flush(); + } + if ("/13".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + getSecKey(seckey) + + "Sec-WebSocket-Protocol: chat2\r\nSec-WebSocket-Protocol: chat" + + "\r\n\r\n")); + os.flush(); + } + if ("/14".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + getSecKey(seckey) + + "Sec-WebSocket-Protocol: chat2,chat" + "\r\n\r\n")); + os.flush(); + } + if ("/15".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes( + additionalHandshake + getSecKey(seckey) + "Sec-WebSocket-Protocol: chat3" + + "\r\n\r\n")); + os.flush(); + } + if ("/16".equals(testCase)) { + os.write(Charsetfunctions + .asciiBytes(additionalHandshake + getSecKey(seckey) + "\r\n")); + os.flush(); + } + if ("/17".equals(testCase)) { + os.write(Charsetfunctions + .asciiBytes(additionalHandshake + getSecKey(seckey) + "\r\n")); + os.flush(); + } + if ("/18".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes( + additionalHandshake + "Sec-WebSocket-Accept: abc\r\n" + "\r\n")); + os.flush(); + } + if ("/19".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + "\r\n")); + os.flush(); + } + // Order check + if ("/20".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + getSecKey(seckey) + + "Sec-WebSocket-Protocol: chat1,chat2,chat3" + "\r\n\r\n")); + os.flush(); + } + if ("/21".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + getSecKey(seckey) + + "Sec-WebSocket-Protocol: chat1,chat2,chat3" + "\r\n\r\n")); + os.flush(); + } + if ("/22".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + getSecKey(seckey) + + "Sec-WebSocket-Protocol: chat1,chat2,chat3" + "\r\n\r\n")); + os.flush(); + } + if ("/23".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + getSecKey(seckey) + + "Sec-WebSocket-Protocol: chat1,chat2,chat3" + "\r\n\r\n")); + os.flush(); + } + if ("/24".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + getSecKey(seckey) + + "Sec-WebSocket-Protocol: chat1,chat2,chat3" + "\r\n\r\n")); + os.flush(); + } + if ("/25".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes( + additionalHandshake + getSecKey(seckey) + "Sec-WebSocket-Protocol: abc" + + "\r\n\r\n")); + os.flush(); + } + if ("/26".equals(testCase)) { + os.write(Charsetfunctions + .asciiBytes(additionalHandshake + getSecKey(seckey) + "\r\n\r\n")); + os.flush(); + } + if ("/27".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes(additionalHandshake + getSecKey(seckey) + + "Sec-WebSocket-Protocol: chat1,chat2,chat3" + "\r\n\r\n")); + os.flush(); + } + if ("/28".equals(testCase)) { + os.write(Charsetfunctions.asciiBytes( + additionalHandshake + getSecKey(seckey) + "Sec-WebSocket-Protocol: abc" + + "\r\n\r\n")); + os.flush(); + } + if ("/29".equals(testCase)) { + os.write(Charsetfunctions + .asciiBytes(additionalHandshake + getSecKey(seckey) + "\r\n\r\n")); + os.flush(); + } + } catch (IOException e) { + // + } + } + } catch (Exception e) { + fail("There should be no exception", e); + } + }); + thread.start(); + } + + private static String getSecKey(String seckey) { + return "Sec-WebSocket-Accept: " + generateFinalKey(seckey) + "\r\n"; + } + + @AfterEach + public void successTests() throws IOException { + if (serverSocket != null) { + serverSocket.close(); + } + if (thread != null) { + thread.interrupt(); + } + } + + @Test + @Timeout(5000) + public void testProtocolRejectionTestCase0() throws Exception { + testProtocolRejection(0, new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("")))); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase1() throws Exception { + testProtocolRejection(1, new Draft_6455()); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase2() throws Exception { + testProtocolRejection(2, new Draft_6455()); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase3() throws Exception { + testProtocolRejection(3, new Draft_6455()); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase4() throws Exception { + testProtocolRejection(4, new Draft_6455()); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase5() throws Exception { + testProtocolRejection(5, new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat")))); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase6() throws Exception { + testProtocolRejection(6, new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat")))); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase7() throws Exception { + testProtocolRejection(7, new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat")))); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase8() throws Exception { + testProtocolRejection(8, new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat")))); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase9() throws Exception { + testProtocolRejection(9, new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat")))); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase10() throws Exception { + testProtocolRejection(10, new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat")))); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase11() throws Exception { + testProtocolRejection(11, new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat")))); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase12() throws Exception { + testProtocolRejection(12, new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat")))); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase13() throws Exception { + testProtocolRejection(13, new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("chat")))); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase14() throws Exception { + ArrayList protocols = new ArrayList<>(); + protocols.add(new Protocol("chat")); + protocols.add(new Protocol("chat2")); + testProtocolRejection(14, new Draft_6455(Collections.emptyList(), protocols)); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase15() throws Exception { + ArrayList protocols = new ArrayList<>(); + protocols.add(new Protocol("chat")); + protocols.add(new Protocol("chat2")); + testProtocolRejection(15, new Draft_6455(Collections.emptyList(), protocols)); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase16() throws Exception { + ArrayList protocols = new ArrayList<>(); + protocols.add(new Protocol("chat")); + protocols.add(new Protocol("chat2")); + testProtocolRejection(16, new Draft_6455(Collections.emptyList(), protocols)); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase17() throws Exception { + ArrayList protocols = new ArrayList<>(); + protocols.add(new Protocol("chat")); + protocols.add(new Protocol("")); + testProtocolRejection(17, new Draft_6455(Collections.emptyList(), protocols)); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase18() throws Exception { + testProtocolRejection(18, new Draft_6455()); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase19() throws Exception { + testProtocolRejection(19, new Draft_6455()); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase20() throws Exception { + testProtocolRejection(20, new Draft_6455()); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase21() throws Exception { + ArrayList protocols = new ArrayList<>(); + protocols.add(new Protocol("chat1")); + protocols.add(new Protocol("chat2")); + protocols.add(new Protocol("chat3")); + testProtocolRejection(21, new Draft_6455(Collections.emptyList(), protocols)); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase22() throws Exception { + ArrayList protocols = new ArrayList<>(); + protocols.add(new Protocol("chat2")); + protocols.add(new Protocol("chat3")); + protocols.add(new Protocol("chat1")); + testProtocolRejection(22, new Draft_6455(Collections.emptyList(), protocols)); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase23() throws Exception { + ArrayList protocols = new ArrayList<>(); + protocols.add(new Protocol("chat3")); + protocols.add(new Protocol("chat2")); + protocols.add(new Protocol("chat1")); + testProtocolRejection(23, new Draft_6455(Collections.emptyList(), protocols)); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase24() throws Exception { + testProtocolRejection(24, new Draft_6455()); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase25() throws Exception { + testProtocolRejection(25, new Draft_6455()); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase26() throws Exception { + testProtocolRejection(26, new Draft_6455()); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase27() throws Exception { + testProtocolRejection(27, new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("opc")))); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase28() throws Exception { + testProtocolRejection(28, new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("opc")))); + } + + @Test + @Timeout(5000) + public void testHandshakeRejectionTestCase29() throws Exception { + testProtocolRejection(29, new Draft_6455(Collections.emptyList(), + Collections.singletonList(new Protocol("opc")))); + } + + private void testProtocolRejection(int i, Draft_6455 draft) throws Exception { + startServer(); + assertTrue(SocketUtil.waitForServerToStart(this.port), "Server Start Status"); + final int finalI = i; + final CountDownLatch countDownLatch = new CountDownLatch(1); + final WebSocketClient webSocketClient = new WebSocketClient( + new URI("ws://localhost:" + port + "/" + finalI), draft) { + @Override + public void onOpen(ServerHandshake handshakedata) { + switch (finalI) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 13: + case 14: + case 17: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + countDownLatch.countDown(); + closeConnection(CloseFrame.ABNORMAL_CLOSE, "Bye"); + break; + default: + fail("There should not be a connection!"); + } + } + + @Override + public void onMessage(String message) { + fail("There should not be a message!"); + } + + @Override + public void onClose(int code, String reason, boolean remote) { + switch (finalI) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 17: + case 20: + case 24: + case 25: + case 26: + assertEquals("", getProtocol().getProvidedProtocol()); + break; + case 5: + case 9: + case 10: + case 11: + case 12: + case 13: + case 15: + case 16: + case 18: + case 19: + case 27: + case 28: + case 29: + assertNull(getProtocol()); + break; + case 6: + case 7: + case 8: + case 14: + assertEquals("chat", getProtocol().getProvidedProtocol()); + break; + case 22: + assertEquals("chat2", getProtocol().getProvidedProtocol()); + break; + case 21: + assertEquals("chat1", getProtocol().getProvidedProtocol()); + break; + case 23: + assertEquals("chat3", getProtocol().getProvidedProtocol()); + break; + default: + fail(); + } + if (code == CloseFrame.ABNORMAL_CLOSE) { + switch (finalI) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 13: + case 14: + case 17: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + return; + } + } + if (code != CloseFrame.PROTOCOL_ERROR) { + fail("There should be a protocol error! " + finalI + " " + code); + } else if (reason.endsWith("refuses handshake")) { + countDownLatch.countDown(); + } else { + fail("The reason should be included!"); + } + } + + @Override + public void onError(Exception ex) { + fail("There should not be an exception: " + ex.getMessage() + " Port: " + port); + } + }; + final AssertionError[] exc = new AssertionError[1]; + exc[0] = null; + Thread finalThread = new Thread(new Runnable() { + @Override + public void run() { + try { + webSocketClient.run(); + } catch (AssertionError e) { + exc[0] = e; + countDownLatch.countDown(); + } + } + + }); + finalThread.start(); + finalThread.join(); + if (exc[0] != null) { + throw exc[0]; + } + + countDownLatch.await(); + + } + + /** + * Generate a final key from a input string + * + * @param in the input string + * @return a final key + */ + private static String generateFinalKey(String in) { + String seckey = in.trim(); + String acc = seckey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + MessageDigest sh1; + try { + sh1 = MessageDigest.getInstance("SHA1"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + return Base64.encodeBytes(sh1.digest(acc.getBytes())); + } +} diff --git a/src/test/java/org/java_websocket/protocols/ProtocolTest.java b/src/test/java/org/java_websocket/protocols/ProtocolTest.java new file mode 100644 index 000000000..895b5a4f1 --- /dev/null +++ b/src/test/java/org/java_websocket/protocols/ProtocolTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.protocols; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ProtocolTest { + + @Test + public void testConstructor() throws Exception { + Protocol protocol0 = new Protocol(""); + try { + Protocol protocol1 = new Protocol(null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException e) { + //Fine + } + } + + @Test + public void testAcceptProvidedProtocol() throws Exception { + Protocol protocol0 = new Protocol(""); + assertTrue(protocol0.acceptProvidedProtocol("")); + assertTrue(protocol0.acceptProvidedProtocol("chat")); + assertTrue(protocol0.acceptProvidedProtocol("chat, test")); + assertTrue(protocol0.acceptProvidedProtocol("chat, test,")); + Protocol protocol1 = new Protocol("chat"); + assertTrue(protocol1.acceptProvidedProtocol("chat")); + assertFalse(protocol1.acceptProvidedProtocol("test")); + assertTrue(protocol1.acceptProvidedProtocol("chat, test")); + assertTrue(protocol1.acceptProvidedProtocol("test, chat")); + assertTrue(protocol1.acceptProvidedProtocol("test,chat")); + assertTrue(protocol1.acceptProvidedProtocol("chat,test")); + assertTrue(protocol1.acceptProvidedProtocol("asdchattest,test, chat")); + } + + @Test + public void testGetProvidedProtocol() throws Exception { + Protocol protocol0 = new Protocol(""); + assertEquals("", protocol0.getProvidedProtocol()); + Protocol protocol1 = new Protocol("protocol"); + assertEquals("protocol", protocol1.getProvidedProtocol()); + } + + @Test + public void testCopyInstance() throws Exception { + IProtocol protocol0 = new Protocol(""); + IProtocol protoocl1 = protocol0.copyInstance(); + assertEquals(protocol0, protoocl1); + IProtocol protocol2 = new Protocol("protocol"); + IProtocol protocol3 = protocol2.copyInstance(); + assertEquals(protocol2, protocol3); + } + + @Test + public void testToString() throws Exception { + Protocol protocol0 = new Protocol(""); + assertEquals("", protocol0.getProvidedProtocol()); + Protocol protocol1 = new Protocol("protocol"); + assertEquals("protocol", protocol1.getProvidedProtocol()); + } + + @Test + public void testEquals() throws Exception { + Protocol protocol0 = new Protocol(""); + Protocol protocol1 = new Protocol("protocol"); + Protocol protocol2 = new Protocol("protocol"); + assertNotEquals(protocol0, protocol1); + assertNotEquals(protocol0, protocol2); + assertEquals(protocol1, protocol2); + assertNotEquals(null, protocol1); + assertNotEquals(new Object(), protocol1); + } + + @Test + public void testHashCode() throws Exception { + Protocol protocol0 = new Protocol(""); + Protocol protocol1 = new Protocol("protocol"); + Protocol protocol2 = new Protocol("protocol"); + assertNotEquals(protocol0, protocol1); + assertNotEquals(protocol0, protocol2); + assertEquals(protocol1, protocol2); + } + +} \ No newline at end of file diff --git a/src/test/java/org/java_websocket/server/CustomSSLWebSocketServerFactoryTest.java b/src/test/java/org/java_websocket/server/CustomSSLWebSocketServerFactoryTest.java new file mode 100644 index 000000000..a83d8de07 --- /dev/null +++ b/src/test/java/org/java_websocket/server/CustomSSLWebSocketServerFactoryTest.java @@ -0,0 +1,172 @@ +package org.java_websocket.server; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.NotYetConnectedException; +import java.nio.channels.SocketChannel; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.concurrent.Executors; +import javax.net.ssl.SSLContext; +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.handshake.Handshakedata; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +public class CustomSSLWebSocketServerFactoryTest { + + final String[] emptyArray = new String[0]; + + @Test + public void testConstructor() throws NoSuchAlgorithmException { + try { + new CustomSSLWebSocketServerFactory(null, null, null); + fail("IllegalArgumentException should be thrown"); + } catch (IllegalArgumentException e) { + // Good + } + try { + new CustomSSLWebSocketServerFactory(null, null, null, null); + fail("IllegalArgumentException should be thrown"); + } catch (IllegalArgumentException e) { + // Good + } + try { + new CustomSSLWebSocketServerFactory(SSLContext.getDefault(), null, null, null); + fail("IllegalArgumentException should be thrown"); + } catch (IllegalArgumentException e) { + } + try { + new CustomSSLWebSocketServerFactory(SSLContext.getDefault(), null, null); + } catch (IllegalArgumentException e) { + fail("IllegalArgumentException should not be thrown"); + } + try { + new CustomSSLWebSocketServerFactory(SSLContext.getDefault(), Executors.newCachedThreadPool(), + null, null); + } catch (IllegalArgumentException e) { + fail("IllegalArgumentException should not be thrown"); + } + try { + new CustomSSLWebSocketServerFactory(SSLContext.getDefault(), Executors.newCachedThreadPool(), + emptyArray, null); + } catch (IllegalArgumentException e) { + fail("IllegalArgumentException should not be thrown"); + } + try { + new CustomSSLWebSocketServerFactory(SSLContext.getDefault(), Executors.newCachedThreadPool(), + null, emptyArray); + } catch (IllegalArgumentException e) { + fail("IllegalArgumentException should not be thrown"); + } + try { + new CustomSSLWebSocketServerFactory(SSLContext.getDefault(), Executors.newCachedThreadPool(), + emptyArray, emptyArray); + } catch (IllegalArgumentException e) { + fail("IllegalArgumentException should not be thrown"); + } + } + + @Test + public void testCreateWebSocket() throws NoSuchAlgorithmException { + CustomSSLWebSocketServerFactory webSocketServerFactory = new CustomSSLWebSocketServerFactory( + SSLContext.getDefault(), null, null); + CustomWebSocketAdapter webSocketAdapter = new CustomWebSocketAdapter(); + WebSocketImpl webSocketImpl = webSocketServerFactory + .createWebSocket(webSocketAdapter, new Draft_6455()); + assertNotNull(webSocketImpl, "webSocketImpl != null"); + webSocketImpl = webSocketServerFactory + .createWebSocket(webSocketAdapter, Collections.singletonList(new Draft_6455())); + assertNotNull(webSocketImpl, "webSocketImpl != null"); + } + + @Test + public void testWrapChannel() throws IOException, NoSuchAlgorithmException { + CustomSSLWebSocketServerFactory webSocketServerFactory = new CustomSSLWebSocketServerFactory( + SSLContext.getDefault(), null, null); + SocketChannel channel = SocketChannel.open(); + try { + ByteChannel result = webSocketServerFactory.wrapChannel(channel, null); + } catch (NotYetConnectedException e) { + //We do not really connect + } + channel.close(); + webSocketServerFactory = new CustomSSLWebSocketServerFactory(SSLContext.getDefault(), + new String[]{"TLSv1.2"}, + new String[]{"TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA"}); + channel = SocketChannel.open(); + try { + ByteChannel result = webSocketServerFactory.wrapChannel(channel, null); + } catch (NotYetConnectedException e) { + //We do not really connect + } + channel.close(); + } + + @Test + public void testClose() { + DefaultWebSocketServerFactory webSocketServerFactory = new DefaultWebSocketServerFactory(); + webSocketServerFactory.close(); + } + + private static class CustomWebSocketAdapter extends WebSocketAdapter { + + @Override + public void onWebsocketMessage(WebSocket conn, String message) { + + } + + @Override + public void onWebsocketMessage(WebSocket conn, ByteBuffer blob) { + + } + + @Override + public void onWebsocketOpen(WebSocket conn, Handshakedata d) { + + } + + @Override + public void onWebsocketClose(WebSocket ws, int code, String reason, boolean remote) { + + } + + @Override + public void onWebsocketClosing(WebSocket ws, int code, String reason, boolean remote) { + + } + + @Override + public void onWebsocketCloseInitiated(WebSocket ws, int code, String reason) { + + } + + @Override + public void onWebsocketError(WebSocket conn, Exception ex) { + + } + + @Override + public void onWriteDemand(WebSocket conn) { + + } + + @Override + public InetSocketAddress getLocalSocketAddress(WebSocket conn) { + return null; + } + + @Override + public InetSocketAddress getRemoteSocketAddress(WebSocket conn) { + return null; + } + } +} diff --git a/src/test/java/org/java_websocket/server/DaemonThreadTest.java b/src/test/java/org/java_websocket/server/DaemonThreadTest.java new file mode 100644 index 000000000..9047a97da --- /dev/null +++ b/src/test/java/org/java_websocket/server/DaemonThreadTest.java @@ -0,0 +1,79 @@ +package org.java_websocket.server; + +import java.net.*; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import org.java_websocket.WebSocket; +import org.java_websocket.handshake.*; +import org.java_websocket.client.*; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DaemonThreadTest { + + @Test + @Timeout(1000) + public void test_AllCreatedThreadsAreDaemon() throws InterruptedException { + + Set threadSet1 = Thread.getAllStackTraces().keySet(); + final CountDownLatch ready = new CountDownLatch(1); + final CountDownLatch serverStarted = new CountDownLatch(1); + + WebSocketServer server = new WebSocketServer(new InetSocketAddress(SocketUtil.getAvailablePort())) { + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) {} + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) {} + @Override + public void onMessage(WebSocket conn, String message) {} + @Override + public void onError(WebSocket conn, Exception ex) {} + @Override + public void onStart() {serverStarted.countDown();} + }; + server.setDaemon(true); + server.setDaemon(false); + server.setDaemon(true); + server.start(); + serverStarted.await(); + + WebSocketClient client = new WebSocketClient(URI.create("ws://localhost:" + server.getPort())) { + @Override + public void onOpen(ServerHandshake handshake) { + ready.countDown(); + } + @Override + public void onClose(int code, String reason, boolean remote) {} + @Override + public void onMessage(String message) {} + @Override + public void onError(Exception ex) {} + }; + client.setDaemon(false); + client.setDaemon(true); + client.connect(); + + ready.await(); + Set threadSet2 = Thread.getAllStackTraces().keySet(); + threadSet2.removeAll(threadSet1); + + assertFalse(threadSet2.isEmpty(), "new threads created (no new threads indicates issue in test)"); + + for (Thread t : threadSet2) + assertTrue(t.isDaemon(), t.getName()); + + boolean exception = false; + try { + server.setDaemon(false); + } catch(IllegalStateException e) { + exception = true; + } + assertTrue(exception, "exception was thrown when calling setDaemon on a running server"); + + server.stop(); + } +} diff --git a/src/test/java/org/java_websocket/server/DefaultSSLWebSocketServerFactoryTest.java b/src/test/java/org/java_websocket/server/DefaultSSLWebSocketServerFactoryTest.java new file mode 100644 index 000000000..c8330a551 --- /dev/null +++ b/src/test/java/org/java_websocket/server/DefaultSSLWebSocketServerFactoryTest.java @@ -0,0 +1,142 @@ +package org.java_websocket.server; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.NotYetConnectedException; +import java.nio.channels.SocketChannel; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.concurrent.Executors; +import javax.net.ssl.SSLContext; +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.handshake.Handshakedata; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +public class DefaultSSLWebSocketServerFactoryTest { + + @Test + public void testConstructor() throws NoSuchAlgorithmException { + try { + new DefaultSSLWebSocketServerFactory(null); + fail("IllegalArgumentException should be thrown"); + } catch (IllegalArgumentException e) { + // Good + } + try { + new DefaultSSLWebSocketServerFactory(null, null); + fail("IllegalArgumentException should be thrown"); + } catch (IllegalArgumentException e) { + // Good + } + try { + new DefaultSSLWebSocketServerFactory(SSLContext.getDefault(), null); + fail("IllegalArgumentException should be thrown"); + } catch (IllegalArgumentException e) { + } + try { + new DefaultSSLWebSocketServerFactory(SSLContext.getDefault()); + } catch (IllegalArgumentException e) { + fail("IllegalArgumentException should not be thrown"); + } + try { + new DefaultSSLWebSocketServerFactory(SSLContext.getDefault(), + Executors.newCachedThreadPool()); + } catch (IllegalArgumentException e) { + fail("IllegalArgumentException should not be thrown"); + } + } + + @Test + public void testCreateWebSocket() throws NoSuchAlgorithmException { + DefaultSSLWebSocketServerFactory webSocketServerFactory = new DefaultSSLWebSocketServerFactory( + SSLContext.getDefault()); + CustomWebSocketAdapter webSocketAdapter = new CustomWebSocketAdapter(); + WebSocketImpl webSocketImpl = webSocketServerFactory + .createWebSocket(webSocketAdapter, new Draft_6455()); + assertNotNull(webSocketImpl,"webSocketImpl != null"); + webSocketImpl = webSocketServerFactory + .createWebSocket(webSocketAdapter, Collections.singletonList(new Draft_6455())); + assertNotNull( webSocketImpl, "webSocketImpl != null"); + } + + @Test + public void testWrapChannel() throws IOException, NoSuchAlgorithmException { + DefaultSSLWebSocketServerFactory webSocketServerFactory = new DefaultSSLWebSocketServerFactory( + SSLContext.getDefault()); + SocketChannel channel = SocketChannel.open(); + try { + ByteChannel result = webSocketServerFactory.wrapChannel(channel, null); + } catch (NotYetConnectedException e) { + //We do not really connect + } + channel.close(); + } + + @Test + public void testClose() { + DefaultWebSocketServerFactory webSocketServerFactory = new DefaultWebSocketServerFactory(); + webSocketServerFactory.close(); + } + + private static class CustomWebSocketAdapter extends WebSocketAdapter { + + @Override + public void onWebsocketMessage(WebSocket conn, String message) { + + } + + @Override + public void onWebsocketMessage(WebSocket conn, ByteBuffer blob) { + + } + + @Override + public void onWebsocketOpen(WebSocket conn, Handshakedata d) { + + } + + @Override + public void onWebsocketClose(WebSocket ws, int code, String reason, boolean remote) { + + } + + @Override + public void onWebsocketClosing(WebSocket ws, int code, String reason, boolean remote) { + + } + + @Override + public void onWebsocketCloseInitiated(WebSocket ws, int code, String reason) { + + } + + @Override + public void onWebsocketError(WebSocket conn, Exception ex) { + + } + + @Override + public void onWriteDemand(WebSocket conn) { + + } + + @Override + public InetSocketAddress getLocalSocketAddress(WebSocket conn) { + return null; + } + + @Override + public InetSocketAddress getRemoteSocketAddress(WebSocket conn) { + return null; + } + } +} diff --git a/src/test/java/org/java_websocket/server/DefaultWebSocketServerFactoryTest.java b/src/test/java/org/java_websocket/server/DefaultWebSocketServerFactoryTest.java new file mode 100644 index 000000000..77dafa56f --- /dev/null +++ b/src/test/java/org/java_websocket/server/DefaultWebSocketServerFactoryTest.java @@ -0,0 +1,100 @@ +package org.java_websocket.server; + + +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.util.Collections; +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.handshake.Handshakedata; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +public class DefaultWebSocketServerFactoryTest { + + @Test + public void testCreateWebSocket() { + DefaultWebSocketServerFactory webSocketServerFactory = new DefaultWebSocketServerFactory(); + CustomWebSocketAdapter webSocketAdapter = new CustomWebSocketAdapter(); + WebSocketImpl webSocketImpl = webSocketServerFactory + .createWebSocket(webSocketAdapter, new Draft_6455()); + assertNotNull(webSocketImpl, "webSocketImpl != null"); + webSocketImpl = webSocketServerFactory + .createWebSocket(webSocketAdapter, Collections.singletonList(new Draft_6455())); + assertNotNull(webSocketImpl, "webSocketImpl != null"); + } + + @Test + public void testWrapChannel() { + DefaultWebSocketServerFactory webSocketServerFactory = new DefaultWebSocketServerFactory(); + SocketChannel channel = (new Socket()).getChannel(); + SocketChannel result = webSocketServerFactory.wrapChannel(channel, null); + assertSame(channel, result, "channel == result"); + } + + @Test + public void testClose() { + DefaultWebSocketServerFactory webSocketServerFactory = new DefaultWebSocketServerFactory(); + webSocketServerFactory.close(); + } + + private static class CustomWebSocketAdapter extends WebSocketAdapter { + + @Override + public void onWebsocketMessage(WebSocket conn, String message) { + + } + + @Override + public void onWebsocketMessage(WebSocket conn, ByteBuffer blob) { + + } + + @Override + public void onWebsocketOpen(WebSocket conn, Handshakedata d) { + + } + + @Override + public void onWebsocketClose(WebSocket ws, int code, String reason, boolean remote) { + + } + + @Override + public void onWebsocketClosing(WebSocket ws, int code, String reason, boolean remote) { + + } + + @Override + public void onWebsocketCloseInitiated(WebSocket ws, int code, String reason) { + + } + + @Override + public void onWebsocketError(WebSocket conn, Exception ex) { + + } + + @Override + public void onWriteDemand(WebSocket conn) { + + } + + @Override + public InetSocketAddress getLocalSocketAddress(WebSocket conn) { + return null; + } + + @Override + public InetSocketAddress getRemoteSocketAddress(WebSocket conn) { + return null; + } + } +} diff --git a/src/test/java/org/java_websocket/server/SSLParametersWebSocketServerFactoryTest.java b/src/test/java/org/java_websocket/server/SSLParametersWebSocketServerFactoryTest.java new file mode 100644 index 000000000..00715d231 --- /dev/null +++ b/src/test/java/org/java_websocket/server/SSLParametersWebSocketServerFactoryTest.java @@ -0,0 +1,137 @@ +package org.java_websocket.server; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.NotYetConnectedException; +import java.nio.channels.SocketChannel; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.concurrent.Executors; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.handshake.Handshakedata; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +public class SSLParametersWebSocketServerFactoryTest { + + @Test + public void testConstructor() throws NoSuchAlgorithmException { + try { + new SSLParametersWebSocketServerFactory(null, null); + fail("IllegalArgumentException should be thrown"); + } catch (IllegalArgumentException e) { + // Good + } + try { + new SSLParametersWebSocketServerFactory(SSLContext.getDefault(), null); + fail("IllegalArgumentException should be thrown"); + } catch (IllegalArgumentException e) { + } + try { + new SSLParametersWebSocketServerFactory(SSLContext.getDefault(), new SSLParameters()); + } catch (IllegalArgumentException e) { + fail("IllegalArgumentException should not be thrown"); + } + try { + new SSLParametersWebSocketServerFactory(SSLContext.getDefault(), + Executors.newCachedThreadPool(), new SSLParameters()); + } catch (IllegalArgumentException e) { + fail("IllegalArgumentException should not be thrown"); + } + } + + @Test + public void testCreateWebSocket() throws NoSuchAlgorithmException { + SSLParametersWebSocketServerFactory webSocketServerFactory = new SSLParametersWebSocketServerFactory( + SSLContext.getDefault(), new SSLParameters()); + CustomWebSocketAdapter webSocketAdapter = new CustomWebSocketAdapter(); + WebSocketImpl webSocketImpl = webSocketServerFactory + .createWebSocket(webSocketAdapter, new Draft_6455()); + assertNotNull(webSocketImpl, "webSocketImpl != null"); + webSocketImpl = webSocketServerFactory + .createWebSocket(webSocketAdapter, Collections.singletonList(new Draft_6455())); + assertNotNull(webSocketImpl, "webSocketImpl != null"); + } + + @Test + public void testWrapChannel() throws IOException, NoSuchAlgorithmException { + SSLParametersWebSocketServerFactory webSocketServerFactory = new SSLParametersWebSocketServerFactory( + SSLContext.getDefault(), new SSLParameters()); + SocketChannel channel = SocketChannel.open(); + try { + ByteChannel result = webSocketServerFactory.wrapChannel(channel, null); + } catch (NotYetConnectedException e) { + //We do not really connect + } + channel.close(); + } + + @Test + public void testClose() { + DefaultWebSocketServerFactory webSocketServerFactory = new DefaultWebSocketServerFactory(); + webSocketServerFactory.close(); + } + + private static class CustomWebSocketAdapter extends WebSocketAdapter { + + @Override + public void onWebsocketMessage(WebSocket conn, String message) { + + } + + @Override + public void onWebsocketMessage(WebSocket conn, ByteBuffer blob) { + + } + + @Override + public void onWebsocketOpen(WebSocket conn, Handshakedata d) { + + } + + @Override + public void onWebsocketClose(WebSocket ws, int code, String reason, boolean remote) { + + } + + @Override + public void onWebsocketClosing(WebSocket ws, int code, String reason, boolean remote) { + + } + + @Override + public void onWebsocketCloseInitiated(WebSocket ws, int code, String reason) { + + } + + @Override + public void onWebsocketError(WebSocket conn, Exception ex) { + + } + + @Override + public void onWriteDemand(WebSocket conn) { + + } + + @Override + public InetSocketAddress getLocalSocketAddress(WebSocket conn) { + return null; + } + + @Override + public InetSocketAddress getRemoteSocketAddress(WebSocket conn) { + return null; + } + } +} diff --git a/src/test/java/org/java_websocket/server/WebSocketServerTest.java b/src/test/java/org/java_websocket/server/WebSocketServerTest.java new file mode 100644 index 000000000..9af9d10e8 --- /dev/null +++ b/src/test/java/org/java_websocket/server/WebSocketServerTest.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + * + */ + +package org.java_websocket.server; + + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import org.java_websocket.WebSocket; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.util.SocketUtil; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class WebSocketServerTest { + + @Test + public void testConstructor() { + List draftCollection = Collections.singletonList(new Draft_6455()); + Collection webSocketCollection = new HashSet(); + InetSocketAddress inetAddress = new InetSocketAddress(1337); + + try { + WebSocketServer server = new MyWebSocketServer(null, 1, draftCollection, webSocketCollection); + fail("Should fail"); + } catch (IllegalArgumentException e) { + //OK + } + try { + WebSocketServer server = new MyWebSocketServer(inetAddress, 0, draftCollection, + webSocketCollection); + fail("Should fail"); + } catch (IllegalArgumentException e) { + //OK + } + try { + WebSocketServer server = new MyWebSocketServer(inetAddress, -1, draftCollection, + webSocketCollection); + fail("Should fail"); + } catch (IllegalArgumentException e) { + //OK + } + try { + WebSocketServer server = new MyWebSocketServer(inetAddress, Integer.MIN_VALUE, + draftCollection, webSocketCollection); + fail("Should fail"); + } catch (IllegalArgumentException e) { + //OK + } + try { + WebSocketServer server = new MyWebSocketServer(inetAddress, Integer.MIN_VALUE, + draftCollection, webSocketCollection); + fail("Should fail"); + } catch (IllegalArgumentException e) { + //OK + } + try { + WebSocketServer server = new MyWebSocketServer(inetAddress, 1, draftCollection, null); + fail("Should fail"); + } catch (IllegalArgumentException e) { + //OK + } + + try { + WebSocketServer server = new MyWebSocketServer(inetAddress, 1, draftCollection, + webSocketCollection); + // OK + } catch (IllegalArgumentException e) { + fail("Should not fail"); + } + try { + WebSocketServer server = new MyWebSocketServer(inetAddress, 1, null, webSocketCollection); + // OK + } catch (IllegalArgumentException e) { + fail("Should not fail"); + } + } + + + @Test + public void testGetAddress() throws InterruptedException { + int port = SocketUtil.getAvailablePort(); + InetSocketAddress inetSocketAddress = new InetSocketAddress(port); + MyWebSocketServer server = new MyWebSocketServer(port); + assertEquals(inetSocketAddress, server.getAddress()); + } + + @Test + public void testGetDrafts() { + List draftCollection = Collections.singletonList(new Draft_6455()); + Collection webSocketCollection = new HashSet(); + InetSocketAddress inetAddress = new InetSocketAddress(1337); + MyWebSocketServer server = new MyWebSocketServer(inetAddress, 1, draftCollection, + webSocketCollection); + assertEquals(1, server.getDraft().size()); + assertEquals(draftCollection.get(0), server.getDraft().get(0)); + } + + @Test + public void testGetPort() throws IOException, InterruptedException { + int port = SocketUtil.getAvailablePort(); + CountDownLatch countServerDownLatch = new CountDownLatch(1); + MyWebSocketServer server = new MyWebSocketServer(port); + assertEquals(port, server.getPort()); + server = new MyWebSocketServer(0, countServerDownLatch); + assertEquals(0, server.getPort()); + server.start(); + countServerDownLatch.await(); + assertNotEquals(0, server.getPort()); + } + + @Test + public void testMaxPendingConnections() { + MyWebSocketServer server = new MyWebSocketServer(1337); + assertEquals(-1, server.getMaxPendingConnections()); + server.setMaxPendingConnections(10); + assertEquals(10, server.getMaxPendingConnections()); + } + + @Test + public void testBroadcast() { + MyWebSocketServer server = new MyWebSocketServer(1337); + try { + server.broadcast((byte[]) null, Collections.emptyList()); + fail("Should fail"); + } catch (IllegalArgumentException e) { + // OK + } + try { + server.broadcast((ByteBuffer) null, Collections.emptyList()); + fail("Should fail"); + } catch (IllegalArgumentException e) { + // OK + } + try { + server.broadcast((String) null, Collections.emptyList()); + fail("Should fail"); + } catch (IllegalArgumentException e) { + // OK + } + try { + server.broadcast(new byte[]{(byte) 0xD0}, null); + fail("Should fail"); + } catch (IllegalArgumentException e) { + // OK + } + try { + server.broadcast(ByteBuffer.wrap(new byte[]{(byte) 0xD0}), null); + fail("Should fail"); + } catch (IllegalArgumentException e) { + // OK + } + try { + server.broadcast("", null); + fail("Should fail"); + } catch (IllegalArgumentException e) { + // OK + } + try { + server.broadcast("", Collections.emptyList()); + // OK + } catch (IllegalArgumentException e) { + fail("Should not fail"); + } + } + + private static class MyWebSocketServer extends WebSocketServer { + + private CountDownLatch serverLatch = null; + + public MyWebSocketServer(InetSocketAddress address, int decodercount, List drafts, + Collection connectionscontainer) { + super(address, decodercount, drafts, connectionscontainer); + } + + public MyWebSocketServer(int port, CountDownLatch serverLatch) { + super(new InetSocketAddress(port)); + this.serverLatch = serverLatch; + } + + public MyWebSocketServer(int port) { + this(port, null); + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + } + + @Override + public void onMessage(WebSocket conn, String message) { + + } + + @Override + public void onError(WebSocket conn, Exception ex) { + ex.printStackTrace(); + } + + @Override + public void onStart() { + if (serverLatch != null) { + serverLatch.countDown(); + } + } + } +} + diff --git a/src/test/java/org/java_websocket/util/Base64Test.java b/src/test/java/org/java_websocket/util/Base64Test.java new file mode 100644 index 000000000..0d546db50 --- /dev/null +++ b/src/test/java/org/java_websocket/util/Base64Test.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.util; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +public class Base64Test { + + @Test + public void testEncodeBytes() throws IOException { + assertEquals("", Base64.encodeBytes(new byte[0])); + assertEquals("QHE=", + Base64.encodeBytes(new byte[]{49, 121, 64, 113, -63, 43, -24, 62, 4, 48}, 2, 2, 0)); + assertGzipEncodedBytes("H4sIAAAAAAAA", "MEALfv3IMBAAAA", + Base64.encodeBytes(new byte[]{49, 121, 64, 113, -63, 43, -24, 62, 4, 48}, 0, 1, 6)); + assertGzipEncodedBytes("H4sIAAAAAAAA", "MoBABQHKKWAgAAAA==", + Base64.encodeBytes(new byte[]{49, 121, 64, 113, -63, 43, -24, 62, 4, 48}, 2, 2, 18)); + assertEquals("F63=", + Base64.encodeBytes(new byte[]{49, 121, 64, 113, 63, 43, -24, 62, 4, 48}, 2, 2, 32)); + assertGzipEncodedBytes("6sg7--------", "Bc0-0F699L-V----==", + Base64.encodeBytes(new byte[]{49, 121, 64, 113, 63, 43, -24, 62, 4, 48}, 2, 2, 34)); + } + + // see https://bugs.openjdk.org/browse/JDK-8253142 + private void assertGzipEncodedBytes(String expectedPrefix, String expectedSuffix, String actual) { + assertTrue(actual.startsWith(expectedPrefix)); + assertTrue(actual.endsWith(expectedSuffix)); + } + + @Test + public void testEncodeBytes2() { + assertThrows(IllegalArgumentException.class, () -> + Base64.encodeBytes(new byte[0], -2, -2, -56)); + } + + @Test + public void testEncodeBytes3() { + assertThrows(IllegalArgumentException.class, () -> + Base64.encodeBytes(new byte[]{64, -128, 32, 18, 16, 16, 0, 18, 16}, + 2064072977, -2064007440, 10)); + } + + @Test + public void testEncodeBytes4() { + assertThrows(NullPointerException.class, () -> Base64.encodeBytes(null)); + } + + @Test + public void testEncodeBytes5() { + assertThrows(IllegalArgumentException.class, () -> + Base64.encodeBytes(null, 32766, 0, 8)); + } + + @Test + public void testEncodeBytesToBytes1() throws IOException { + assertArrayEquals(new byte[]{95, 68, 111, 78, 55, 45, 61, 61}, + Base64.encodeBytesToBytes(new byte[]{-108, -19, 24, 32}, 0, 4, 32)); + assertArrayEquals(new byte[]{95, 68, 111, 78, 55, 67, 111, 61}, + Base64.encodeBytesToBytes(new byte[]{-108, -19, 24, 32, -35}, 0, 5, 40)); + assertArrayEquals(new byte[]{95, 68, 111, 78, 55, 67, 111, 61}, + Base64.encodeBytesToBytes(new byte[]{-108, -19, 24, 32, -35}, 0, 5, 32)); + assertArrayEquals(new byte[]{87, 50, 77, 61}, + Base64.encodeBytesToBytes(new byte[]{115, 42, 123, 99, 10, -33, 75, 30, 91, 99}, 8, 2, 48)); + assertArrayEquals(new byte[]{87, 50, 77, 61}, + Base64.encodeBytesToBytes(new byte[]{115, 42, 123, 99, 10, -33, 75, 30, 91, 99}, 8, 2, 56)); + assertArrayEquals(new byte[]{76, 53, 66, 61}, + Base64.encodeBytesToBytes(new byte[]{113, 42, 123, 99, 10, -33, 75, 30, 88, 99}, 8, 2, 36)); + assertArrayEquals(new byte[]{87, 71, 77, 61}, + Base64.encodeBytesToBytes(new byte[]{113, 42, 123, 99, 10, -33, 75, 30, 88, 99}, 8, 2, 4)); + } + + @Test + public void testEncodeBytesToBytes2() { + assertThrows(IllegalArgumentException.class, () -> + Base64.encodeBytesToBytes(new byte[]{83, 10, 91, 67, 42, -1, 107, 62, 91, 67}, 8, 6, 26)); + } + + @Test + public void testEncodeBytesToBytes3() throws IOException { + byte[] src = new byte[]{ + 113, 42, 123, 99, 10, -33, 75, 30, 88, 99, + 113, 42, 123, 99, 10, -33, 75, 31, 88, 99, + 113, 42, 123, 99, 10, -33, 75, 32, 88, 99, + 113, 42, 123, 99, 10, -33, 75, 33, 88, 99, + 113, 42, 123, 99, 10, -33, 75, 34, 88, 99, + 113, 42, 123, 99, 10, -33, 75, 35, 88, 99, + 55, 60 + }; + byte[] excepted = new byte[]{ + 99, 83, 112, 55, 89, 119, 114, 102, 83, 120, + 53, 89, 89, 51, 69, 113, 101, 50, 77, 75, 51, + 48, 115, 102, 87, 71, 78, 120, 75, 110, 116, 106, + 67, 116, 57, 76, 73, 70, 104, 106, 99, 83, 112, + 55, 89, 119, 114, 102, 83, 121, 70, 89, 89, + 51, 69, 113, 101, 50, 77, 75, 51, 48, 115, + 105, 87, 71, 78, 120, 75, 110, 116, 106, 67, + 116, 57, 76, 10, 73, 49, 104, 106, 78, 122, + 119, 61 + }; + + assertArrayEquals(excepted, Base64.encodeBytesToBytes(src, 0, 62, 8)); + } +} diff --git a/src/test/java/org/java_websocket/util/ByteBufferUtilsTest.java b/src/test/java/org/java_websocket/util/ByteBufferUtilsTest.java new file mode 100644 index 000000000..a694bac31 --- /dev/null +++ b/src/test/java/org/java_websocket/util/ByteBufferUtilsTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.util; + +import org.junit.jupiter.api.Test; + +import java.nio.ByteBuffer; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * JUnit Test for the new ByteBufferUtils class + */ +public class ByteBufferUtilsTest { + + /** + * A small byte array with some data + */ + private static byte[] smallArray = {0, -1, -2, -3, -4}; + + /** + * A big byte array with some data + */ + private static byte[] bigArray = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + + @Test + public void testEmptyByteBufferCapacity() { + ByteBuffer byteBuffer = ByteBufferUtils.getEmptyByteBuffer(); + assertEquals( 0, byteBuffer.capacity(), "capacity must be 0"); + } + + @Test + public void testEmptyByteBufferNewObject() { + ByteBuffer byteBuffer0 = ByteBufferUtils.getEmptyByteBuffer(); + ByteBuffer byteBuffer1 = ByteBufferUtils.getEmptyByteBuffer(); + assertNotSame(byteBuffer0, byteBuffer1, "Allocated new object"); + } + + @Test + public void testTransferByteBufferSmallToEmpty() { + ByteBuffer small = ByteBuffer.wrap(smallArray); + ByteBuffer empty = ByteBufferUtils.getEmptyByteBuffer(); + ByteBufferUtils.transferByteBuffer(small, empty); + assertArrayEquals( smallArray, small.array(), "Small bytebuffer should not change"); + assertEquals( 0, empty.capacity(), "Capacity of the empty bytebuffer should still be 0"); + } + + @Test + public void testTransferByteBufferSmallToBig() { + ByteBuffer small = ByteBuffer.wrap(smallArray); + ByteBuffer big = ByteBuffer.wrap(bigArray); + ByteBufferUtils.transferByteBuffer(small, big); + assertArrayEquals( smallArray, small.array(), "Small bytebuffer should not change"); + assertEquals( smallArray[0], big.get(0), "Big bytebuffer not same to source 0"); + assertEquals( smallArray[1], big.get(1), "Big bytebuffer not same to source 1"); + assertEquals( smallArray[2], big.get(2), "Big bytebuffer not same to source 2"); + assertEquals( smallArray[3], big.get(3), "Big bytebuffer not same to source 3"); + assertEquals( smallArray[4], big.get(4), "Big bytebuffer not same to source 4"); + assertEquals( bigArray[5], big.get(5), "Big bytebuffer not same to source 5"); + assertEquals( bigArray[6], big.get(6), "Big bytebuffer not same to source 6"); + assertEquals( bigArray[7], big.get(7), "Big bytebuffer not same to source 7"); + assertEquals( bigArray[8], big.get(8), "Big bytebuffer not same to source 8"); + } + + @Test + public void testTransferByteBufferBigToSmall() { + ByteBuffer small = ByteBuffer.wrap(smallArray); + ByteBuffer big = ByteBuffer.wrap(bigArray); + ByteBufferUtils.transferByteBuffer(big, small); + assertArrayEquals( bigArray, big.array(), "Big bytebuffer should not change"); + assertEquals( bigArray[0], small.get(0), "Small bytebuffer not same to source 0"); + assertEquals( bigArray[1], small.get(1), "Small bytebuffer not same to source 1"); + assertEquals( bigArray[2], small.get(2), "Small bytebuffer not same to source 2"); + assertEquals( bigArray[3], small.get(3), "Small bytebuffer not same to source 3"); + assertEquals( bigArray[4], small.get(4), "Small bytebuffer not same to source 4"); + } + + @Test + public void testTransferByteBufferCheckNullDest() { + ByteBuffer source = ByteBuffer.wrap(smallArray); + try { + ByteBufferUtils.transferByteBuffer(source, null); + fail("IllegalArgumentException should be thrown"); + } catch (IllegalArgumentException e) { + //Fine + } + } + + @Test + public void testTransferByteBufferCheckNullSource() { + ByteBuffer dest = ByteBuffer.wrap(smallArray); + try { + ByteBufferUtils.transferByteBuffer(null, dest); + fail("IllegalArgumentException should be thrown"); + } catch (IllegalArgumentException e) { + //Fine + } + } + + @Test + public void testTransferByteBufferCheckNullBoth() { + try { + ByteBufferUtils.transferByteBuffer(null, null); + fail("IllegalArgumentException should be thrown"); + } catch (IllegalArgumentException e) { + //Fine + } + } +} diff --git a/src/test/java/org/java_websocket/util/CharsetfunctionsTest.java b/src/test/java/org/java_websocket/util/CharsetfunctionsTest.java new file mode 100644 index 000000000..1a7ed29e1 --- /dev/null +++ b/src/test/java/org/java_websocket/util/CharsetfunctionsTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.util; + +import java.nio.ByteBuffer; +import org.java_websocket.exceptions.InvalidDataException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class CharsetfunctionsTest { + + @Test + public void testAsciiBytes() { + assertArrayEquals(new byte[]{102, 111, 111}, Charsetfunctions.asciiBytes("foo")); + } + + @Test + public void testStringUtf8ByteBuffer() throws InvalidDataException { + assertEquals("foo", + Charsetfunctions.stringUtf8(ByteBuffer.wrap(new byte[]{102, 111, 111}))); + } + + + @Test + public void testIsValidUTF8off() { + assertFalse(Charsetfunctions.isValidUTF8(ByteBuffer.wrap(new byte[]{100}), 2)); + assertFalse(Charsetfunctions.isValidUTF8(ByteBuffer.wrap(new byte[]{(byte) 128}), 0)); + + assertTrue(Charsetfunctions.isValidUTF8(ByteBuffer.wrap(new byte[]{100}), 0)); + } + + @Test + public void testIsValidUTF8() { + assertFalse(Charsetfunctions.isValidUTF8(ByteBuffer.wrap(new byte[]{(byte) 128}))); + + assertTrue(Charsetfunctions.isValidUTF8(ByteBuffer.wrap(new byte[]{100}))); + } + + @Test + public void testStringAscii1() { + assertEquals("oBar", + Charsetfunctions.stringAscii(new byte[]{102, 111, 111, 66, 97, 114}, 2, 4)); + + } + + @Test + public void testStringAscii2() { + assertEquals("foo", Charsetfunctions.stringAscii(new byte[]{102, 111, 111})); + } + + @Test + public void testUtf8Bytes() { + assertArrayEquals(new byte[]{102, 111, 111, 66, 97, 114}, + Charsetfunctions.utf8Bytes("fooBar")); + } +} diff --git a/src/test/java/org/java_websocket/util/KeyUtils.java b/src/test/java/org/java_websocket/util/KeyUtils.java new file mode 100644 index 000000000..ca5dbff14 --- /dev/null +++ b/src/test/java/org/java_websocket/util/KeyUtils.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + * + */ + +package org.java_websocket.util; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class KeyUtils { + + /** + * Generate a final key from a input string + * + * @param in the input string + * @return a final key + */ + public static String generateFinalKey(String in) { + String seckey = in.trim(); + String acc = seckey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + MessageDigest sh1; + try { + sh1 = MessageDigest.getInstance("SHA1"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + return Base64.encodeBytes(sh1.digest(acc.getBytes())); + } + + public static String getSecKey(String seckey) { + return "Sec-WebSocket-Accept: " + KeyUtils.generateFinalKey(seckey) + "\r\n"; + } + +} diff --git a/src/test/java/org/java_websocket/util/SSLContextUtil.java b/src/test/java/org/java_websocket/util/SSLContextUtil.java new file mode 100644 index 000000000..87b20ecb1 --- /dev/null +++ b/src/test/java/org/java_websocket/util/SSLContextUtil.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + * + */ + +package org.java_websocket.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Paths; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + +public class SSLContextUtil { + + public static SSLContext getContext() + throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, IOException, CertificateException, UnrecoverableKeyException { + // load up the key store + String STORETYPE = "JKS"; + String KEYSTORE = Paths.get("src", "test", "java", "org", "java_websocket", "keystore.jks") + .toString(); + String STOREPASSWORD = "storepassword"; + String KEYPASSWORD = "keypassword"; + + KeyStore ks = KeyStore.getInstance(STORETYPE); + File kf = new File(KEYSTORE); + ks.load(new FileInputStream(kf), STOREPASSWORD.toCharArray()); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, KEYPASSWORD.toCharArray()); + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(ks); + + SSLContext sslContext = null; + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + return sslContext; + } + + public static SSLContext getLocalhostOnlyContext() + throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, IOException, CertificateException, UnrecoverableKeyException { + // load up the key store + String STORETYPE = "JKS"; + String KEYSTORE = Paths + .get("src", "test", "java", "org", "java_websocket", "keystore_localhost_only.jks") + .toString(); + String STOREPASSWORD = "storepassword"; + String KEYPASSWORD = "keypassword"; + + KeyStore ks = KeyStore.getInstance(STORETYPE); + File kf = new File(KEYSTORE); + ks.load(new FileInputStream(kf), STOREPASSWORD.toCharArray()); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, KEYPASSWORD.toCharArray()); + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(ks); + + SSLContext sslContext = null; + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + return sslContext; + } +} diff --git a/src/test/java/org/java_websocket/util/SocketUtil.java b/src/test/java/org/java_websocket/util/SocketUtil.java new file mode 100644 index 000000000..dd8d82a16 --- /dev/null +++ b/src/test/java/org/java_websocket/util/SocketUtil.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.util; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; + +public class SocketUtil { + + public static int getAvailablePort() throws InterruptedException { + while (true) { + try (ServerSocket srv = new ServerSocket(0)) { + return srv.getLocalPort(); + } catch (IOException e) { + // Retry + } + Thread.sleep(5); + } + } + public static boolean waitForServerToStart(int port) throws InterruptedException { + Socket socket = null; + for (int i = 0; i < 50; i++) { + try { + socket = new Socket("localhost", port); + if (socket.isConnected()) { + return true; + } + } catch (IOException ignore) { + // Ignore + } finally { + if (socket != null) { + try { + socket.close(); + } catch (IOException ignore) { + } + } + } + Thread.sleep(10); + } + return false; + } +} diff --git a/src/test/java/org/java_websocket/util/ThreadCheck.java b/src/test/java/org/java_websocket/util/ThreadCheck.java new file mode 100644 index 000000000..208f8edcf --- /dev/null +++ b/src/test/java/org/java_websocket/util/ThreadCheck.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2010-2020 Nathan Rajlich + * + * 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. + */ + +package org.java_websocket.util; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.LockSupport; + +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Makes test fail if new threads are still alive after tear-down. + */ +public class ThreadCheck implements AfterEachCallback, BeforeEachCallback { + + private Map map = new HashMap(); + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + map = getThreadMap(); + } + + @Override + public void afterEach(ExtensionContext context) throws Exception { + long time = System.currentTimeMillis(); + do { + LockSupport.parkNanos(10000000); + } while (checkZombies(true) && System.currentTimeMillis() - time < 1000); + + checkZombies(false); + } + + private boolean checkZombies(boolean testOnly) { + Map newMap = getThreadMap(); + + int zombies = 0; + for (Thread t : newMap.values()) { + Thread prev = map.get(t.getId()); + if (prev == null) { + zombies++; + if (testOnly) { + return true; + } + + StringBuilder b = new StringBuilder(4096); + appendStack(t, b.append("\n").append(t.getName())); + System.err.println(b); + } + } + if (zombies > 0 && !testOnly) { + fail("Found " + zombies + " zombie thread(s) "); + } + + return zombies > 0; + } + + public static Map getThreadMap() { + Map map = new HashMap(); + Thread[] threads = new Thread[Thread.activeCount() * 2]; + int actualNb = Thread.enumerate(threads); + for (int i = 0; i < actualNb; i++) { + if (threads[i].getName().contains("WebSocket")) { + map.put(threads[i].getId(), threads[i]); + } + } + return map; + } + + private static void appendStack(Thread th, StringBuilder s) { + StackTraceElement[] st = th.getStackTrace(); + for (int i = 0; i < st.length; i++) { + s.append("\n at "); + s.append(st[i]); + } + } + + +} diff --git a/src/test/resources/org/java_websocket/AutobahnClient.feature b/src/test/resources/org/java_websocket/AutobahnClient.feature index 737961775..6bc273a20 100644 --- a/src/test/resources/org/java_websocket/AutobahnClient.feature +++ b/src/test/resources/org/java_websocket/AutobahnClient.feature @@ -1,25 +1,26 @@ Feature: Client connects using Draft 17 - As an autobahn client - I want to connect to a websocket server using draft 17 - So that I can send and receive messages + As an autobahn client + I want to connect to a websocket server using draft 17 + So that I can send and receive messages - Scenario Outline: Client connection parameters are valid - Given the Autobahn Server is running using Draft_ on port - And protocol is - And the host is - And the port is - And the query string is - And the draft is Draft_ - When the client connects to the server - Then the server response should contain - And the response's query should contain - And the response's http version should contain - And the response's handshake should contain - And the response's host should contain - And the response's websocket key should contain - And the response's websocket version should contain - And the response's upgraded protocol should contain + Scenario Outline: Client connection parameters are valid + Given the Autobahn Server is running using Draft_ on port + And protocol is + And the host is + And the port is + And the path is + And the query string is + And the draft is Draft_ + When the client connects to the server + Then the server response should contain + And the response's query should contain + And the response's http version should contain + And the response's handshake should contain + And the response's host should contain + And the response's websocket key should contain + And the response's websocket version should contain + And the response's upgraded protocol should contain - Examples: - | protocol | host | port | query | draft | method | http_version | handshake | websocket_key | websocket_version | upgraded_protocol | - | ws | localhost | 9003 | case=1&agent=tootallnate/websocket | 17 | GET | HTTP/1.1 | Upgrade | Sec-WebSocket-Key | Sec-WebSocket-Version | websocket | + Examples: + | protocol | host | port | path | query | draft | method | http_version | handshake | websocket_key | websocket_version | upgraded_protocol | + | ws | localhost | 9003 | /websocket | case=1&agent=tootallnate/websocket | 17 | GET | HTTP/1.1 | Upgrade | Sec-WebSocket-Key | Sec-WebSocket-Version | websocket |