├── .classpath ├── .gitignore ├── .project ├── CHANGES.md ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── com │ └── neovisionaries │ └── ws │ └── client │ ├── Address.java │ ├── Base64.java │ ├── ByteArray.java │ ├── ConnectThread.java │ ├── Connectable.java │ ├── CounterPayloadGenerator.java │ ├── DeflateCompressor.java │ ├── DeflateDecompressor.java │ ├── DeflateUtil.java │ ├── DistinguishedNameParser.java │ ├── DualStackMode.java │ ├── FinishThread.java │ ├── FixedDistanceHuffman.java │ ├── FixedLiteralLengthHuffman.java │ ├── FormatException.java │ ├── HandshakeBuilder.java │ ├── HandshakeReader.java │ ├── HostnameUnverifiedException.java │ ├── Huffman.java │ ├── InsufficientDataException.java │ ├── ListenerManager.java │ ├── Misc.java │ ├── NoMoreFrameException.java │ ├── OkHostnameVerifier.java │ ├── OpeningHandshakeException.java │ ├── PayloadGenerator.java │ ├── PerMessageCompressionExtension.java │ ├── PerMessageDeflateExtension.java │ ├── PeriodicalFrameSender.java │ ├── PingSender.java │ ├── PongSender.java │ ├── ProxyHandshaker.java │ ├── ProxySettings.java │ ├── ReadingThread.java │ ├── SNIHelper.java │ ├── SocketConnector.java │ ├── SocketFactorySettings.java │ ├── SocketInitiator.java │ ├── StateManager.java │ ├── StatusLine.java │ ├── ThreadType.java │ ├── Token.java │ ├── WebSocket.java │ ├── WebSocketAdapter.java │ ├── WebSocketCloseCode.java │ ├── WebSocketError.java │ ├── WebSocketException.java │ ├── WebSocketExtension.java │ ├── WebSocketFactory.java │ ├── WebSocketFrame.java │ ├── WebSocketInputStream.java │ ├── WebSocketListener.java │ ├── WebSocketOpcode.java │ ├── WebSocketOutputStream.java │ ├── WebSocketState.java │ ├── WebSocketThread.java │ ├── WritingThread.java │ └── package-info.java └── test └── java └── com └── neovisionaries └── ws └── client ├── MiscTest.java ├── PerMessageDeflateExtensionTest.java ├── TokenTest.java ├── WebSocketExtensionTest.java └── WebSocketFrameTest.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .settings/ 2 | target/ 3 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | nv-websocket-client 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.wst.common.project.facet.core.builder 10 | 11 | 12 | 13 | 14 | org.eclipse.jdt.core.javabuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.m2e.core.maven2Builder 20 | 21 | 22 | 23 | 24 | org.eclipse.wst.validation.validationbuilder 25 | 26 | 27 | 28 | 29 | 30 | org.eclipse.jem.workbench.JavaEMFNature 31 | org.eclipse.wst.common.modulecore.ModuleCoreNature 32 | org.eclipse.jdt.core.javanature 33 | org.eclipse.m2e.core.maven2Nature 34 | org.eclipse.wst.common.project.facet.core.nature 35 | 36 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.neovisionaries 6 | nv-websocket-client 7 | 2.15-SNAPSHOT 8 | bundle 9 | ${project.groupId}:${project.artifactId} 10 | WebSocket client implementation in Java. 11 | https://github.com/TakahikoKawasaki/nv-websocket-client 12 | 13 | 14 | 15 | The Apache Software License, Version 2.0 16 | http://www.apache.org/licenses/LICENSE-2.0.txt 17 | 18 | 19 | 20 | 21 | 22 | Takahiko Kawasaki 23 | 24 | 25 | 26 | 27 | scm:git:git@github.com:TakahikoKawasaki/nv-websocket-client.git 28 | scm:git:git@github.com:TakahikoKawasaki/nv-websocket-client.git 29 | git@github.com:TakahikoKawasaki/nv-websocket-client.git 30 | HEAD 31 | 32 | 33 | 34 | UTF-8 35 | 36 | 37 | 38 | 39 | darutk 40 | https://oss.sonatype.org/content/repositories/snapshots 41 | 42 | 43 | 44 | darutk 45 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 46 | 47 | 48 | 49 | 50 | 51 | junit 52 | junit 53 | 4.13.1 54 | test 55 | 56 | 57 | 58 | 59 | 60 | doclint 61 | 62 | [1.8,) 63 | 64 | 65 | 66 | 67 | org.apache.maven.plugins 68 | maven-javadoc-plugin 69 | 70 | -Xdoclint:-html 71 | 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-site-plugin 76 | 77 | 78 | 79 | org.apache.maven.plugins 80 | maven-javadoc-plugin 81 | 82 | -Xdoclint:-html 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | org.apache.maven.plugins 97 | maven-compiler-plugin 98 | 3.2 99 | 100 | 1.5 101 | 1.5 102 | true 103 | 104 | 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-source-plugin 109 | 2.3 110 | 111 | 112 | attach-sources 113 | 114 | jar-no-fork 115 | 116 | 117 | 118 | 119 | 120 | 121 | org.apache.maven.plugins 122 | maven-javadoc-plugin 123 | 2.10.1 124 | 125 | -J-Duser.language=en 126 | 127 | 128 | 129 | attach-javadocs 130 | 131 | jar 132 | 133 | 134 | 135 | 136 | 137 | 138 | org.apache.maven.plugins 139 | maven-jar-plugin 140 | 2.5 141 | 142 | 143 | 144 | Neo Visionaries Inc. 145 | 146 | 147 | 148 | 149 | 150 | 151 | org.sonatype.plugins 152 | nexus-staging-maven-plugin 153 | 1.6.4 154 | true 155 | 156 | darutk 157 | https://oss.sonatype.org/ 158 | true 159 | 160 | 161 | 162 | 163 | org.apache.maven.plugins 164 | maven-release-plugin 165 | 2.5.1 166 | 167 | true 168 | false 169 | release 170 | deploy 171 | 172 | 173 | 174 | 175 | org.apache.maven.plugins 176 | maven-gpg-plugin 177 | 1.5 178 | 179 | 180 | sign-artifacts 181 | verify 182 | 183 | sign 184 | 185 | 186 | E3F58E5C 187 | 188 | 189 | 190 | 191 | 192 | 193 | org.apache.felix 194 | maven-bundle-plugin 195 | 2.5.3 196 | true 197 | 198 | 199 | ${project.groupId}.ws.client 200 | 201 | 202 | 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/Address.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import java.net.InetSocketAddress; 20 | 21 | 22 | class Address 23 | { 24 | private final String mHost; 25 | private final int mPort; 26 | private transient String mString; 27 | 28 | 29 | Address(String host, int port) 30 | { 31 | mHost = host; 32 | mPort = port; 33 | } 34 | 35 | 36 | String getHostname() 37 | { 38 | return mHost; 39 | } 40 | 41 | int getPort() 42 | { 43 | return mPort; 44 | } 45 | 46 | 47 | @Override 48 | public String toString() 49 | { 50 | if (mString == null) 51 | { 52 | mString = String.format("%s:%d", mHost, mPort); 53 | } 54 | 55 | return mString; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/Base64.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | class Base64 20 | { 21 | private static final byte[] INDEX_TABLE = { 22 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 23 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 24 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 25 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' 26 | }; 27 | 28 | 29 | public static String encode(String data) 30 | { 31 | if (data == null) 32 | { 33 | return null; 34 | } 35 | 36 | return encode(Misc.getBytesUTF8(data)); 37 | } 38 | 39 | 40 | public static String encode(byte[] data) 41 | { 42 | if (data == null) 43 | { 44 | return null; 45 | } 46 | 47 | int capacity = (((((data.length * 8) + 5) / 6) + 3) / 4) * 4; 48 | 49 | StringBuilder builder = new StringBuilder(capacity); 50 | 51 | for (int bitIndex = 0; ; bitIndex += 6) 52 | { 53 | int bits = extractBits(data, bitIndex); 54 | 55 | if (bits < 0) 56 | { 57 | break; 58 | } 59 | 60 | builder.append((char)INDEX_TABLE[bits]); 61 | } 62 | 63 | for (int i = builder.length(); i < capacity; ++i) 64 | { 65 | builder.append('='); 66 | } 67 | 68 | return builder.toString(); 69 | } 70 | 71 | 72 | private static int extractBits(byte[] data, int bitIndex) 73 | { 74 | int byteIndex = bitIndex / 8; 75 | byte nextByte; 76 | 77 | if (data.length <= byteIndex) 78 | { 79 | return -1; 80 | } 81 | else if (data.length - 1 == byteIndex) 82 | { 83 | nextByte = 0; 84 | } 85 | else 86 | { 87 | nextByte = data[byteIndex + 1]; 88 | } 89 | 90 | switch ((bitIndex % 24) / 6) 91 | { 92 | case 0: 93 | return ((data[byteIndex] >> 2) & 0x3F); 94 | 95 | case 1: 96 | return (((data[byteIndex] << 4) & 0x30) | ((nextByte >> 4) & 0x0F)); 97 | 98 | case 2: 99 | return (((data[byteIndex] << 2) & 0x3C) | ((nextByte >> 6) & 0x03)); 100 | 101 | case 3: 102 | return (data[byteIndex] & 0x3F); 103 | 104 | default: 105 | // Never reach here. 106 | return 0; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/ByteArray.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import java.nio.ByteBuffer; 20 | 21 | 22 | /** 23 | * Expandable byte array with byte-basis and bit-basis operations. 24 | */ 25 | class ByteArray 26 | { 27 | private static final int ADDITIONAL_BUFFER_SIZE = 1024; 28 | 29 | // The buffer. 30 | private ByteBuffer mBuffer; 31 | 32 | // The current length. 33 | private int mLength; 34 | 35 | 36 | /** 37 | * Constructor with initial capacity. 38 | * 39 | * @param capacity 40 | * Initial capacity for the internal buffer. 41 | */ 42 | public ByteArray(int capacity) 43 | { 44 | mBuffer = ByteBuffer.allocate(capacity); 45 | mLength = 0; 46 | } 47 | 48 | 49 | /** 50 | * Constructor with initial data. The length of the data is used 51 | * as the initial capacity of the internal buffer. 52 | * 53 | * @param data 54 | * Initial data. 55 | */ 56 | public ByteArray(byte[] data) 57 | { 58 | mBuffer = ByteBuffer.wrap(data); 59 | mLength = data.length; 60 | } 61 | 62 | 63 | /** 64 | * The length of the data. 65 | */ 66 | public int length() 67 | { 68 | return mLength; 69 | } 70 | 71 | 72 | /** 73 | * Get a byte at the index. 74 | */ 75 | public byte get(int index) throws IndexOutOfBoundsException 76 | { 77 | if (index < 0 || mLength <= index) 78 | { 79 | // Bad index. 80 | throw new IndexOutOfBoundsException( 81 | String.format("Bad index: index=%d, length=%d", index, mLength)); 82 | } 83 | 84 | return mBuffer.get(index); 85 | } 86 | 87 | 88 | /** 89 | * Expand the size of the internal buffer. 90 | */ 91 | private void expandBuffer(int newBufferSize) 92 | { 93 | // Allocate a new buffer. 94 | ByteBuffer newBuffer = ByteBuffer.allocate(newBufferSize); 95 | 96 | // Copy the content of the current buffer to the new buffer. 97 | int oldPosition = mBuffer.position(); 98 | mBuffer.position(0); 99 | newBuffer.put(mBuffer); 100 | newBuffer.position(oldPosition); 101 | 102 | // Replace the buffers. 103 | mBuffer = newBuffer; 104 | } 105 | 106 | 107 | /** 108 | * Add a byte at the current position. 109 | */ 110 | public void put(int data) 111 | { 112 | // If the buffer is small. 113 | if (mBuffer.capacity() < (mLength + 1)) 114 | { 115 | expandBuffer(mLength + ADDITIONAL_BUFFER_SIZE); 116 | } 117 | 118 | mBuffer.put((byte)data); 119 | ++mLength; 120 | } 121 | 122 | 123 | /** 124 | * Add data at the current position. 125 | * 126 | * @param source 127 | * Source data. 128 | */ 129 | public void put(byte[] source) 130 | { 131 | // If the buffer is small. 132 | if (mBuffer.capacity() < (mLength + source.length)) 133 | { 134 | expandBuffer(mLength + source.length + ADDITIONAL_BUFFER_SIZE); 135 | } 136 | 137 | mBuffer.put(source); 138 | mLength += source.length; 139 | } 140 | 141 | 142 | /** 143 | * Add data at the current position. 144 | * 145 | * @param source 146 | * Source data. 147 | * 148 | * @param index 149 | * The index in the source data. Data from the index is copied. 150 | * 151 | * @param length 152 | * The length of data to copy. 153 | */ 154 | public void put(byte[] source, int index, int length) 155 | { 156 | // If the buffer is small. 157 | if (mBuffer.capacity() < (mLength + length)) 158 | { 159 | expandBuffer(mLength + length + ADDITIONAL_BUFFER_SIZE); 160 | } 161 | 162 | mBuffer.put(source, index, length); 163 | mLength += length; 164 | } 165 | 166 | 167 | /** 168 | * Add data at the current position. 169 | * 170 | * @param source 171 | * Source data. 172 | * 173 | * @param index 174 | * The index in the source data. Data from the index is copied. 175 | * 176 | * @param length 177 | * The length of data to copy. 178 | */ 179 | public void put(ByteArray source, int index, int length) 180 | { 181 | put(source.mBuffer.array(), index, length); 182 | } 183 | 184 | 185 | /** 186 | * Convert to a byte array (byte[]). 187 | */ 188 | public byte[] toBytes() 189 | { 190 | return toBytes(0); 191 | } 192 | 193 | 194 | public byte[] toBytes(int beginIndex) 195 | { 196 | return toBytes(beginIndex, length()); 197 | } 198 | 199 | 200 | public byte[] toBytes(int beginIndex, int endIndex) 201 | { 202 | int len = endIndex - beginIndex; 203 | 204 | if (len < 0 || beginIndex < 0 || mLength < endIndex) 205 | { 206 | throw new IllegalArgumentException( 207 | String.format("Bad range: beginIndex=%d, endIndex=%d, length=%d", 208 | beginIndex, endIndex, mLength)); 209 | } 210 | 211 | byte[] bytes = new byte[len]; 212 | 213 | if (len != 0) 214 | { 215 | System.arraycopy(mBuffer.array(), beginIndex, bytes, 0, len); 216 | } 217 | 218 | return bytes; 219 | } 220 | 221 | 222 | public void clear() 223 | { 224 | mBuffer.clear(); 225 | mBuffer.position(0); 226 | mLength = 0; 227 | } 228 | 229 | 230 | public void shrink(int size) 231 | { 232 | if (mBuffer.capacity() <= size) 233 | { 234 | return; 235 | } 236 | 237 | int endIndex = mLength; 238 | int beginIndex = mLength - size; 239 | 240 | byte[] bytes = toBytes(beginIndex, endIndex); 241 | 242 | mBuffer = ByteBuffer.wrap(bytes); 243 | mBuffer.position(bytes.length); 244 | mLength = bytes.length; 245 | } 246 | 247 | 248 | public boolean getBit(int bitIndex) 249 | { 250 | int index = bitIndex / 8; 251 | int shift = bitIndex % 8; 252 | int value = get(index); 253 | 254 | // Return true if the bit pointed to by bitIndex is set. 255 | return ((value & (1 << shift)) != 0); 256 | } 257 | 258 | 259 | public int getBits(int bitIndex, int nBits) 260 | { 261 | int number = 0; 262 | int weight = 1; 263 | 264 | // Convert consecutive bits into a number. 265 | for (int i = 0; i < nBits; ++i, weight *= 2) 266 | { 267 | // getBit() returns true if the bit is set. 268 | if (getBit(bitIndex + i)) 269 | { 270 | number += weight; 271 | } 272 | } 273 | 274 | return number; 275 | } 276 | 277 | 278 | public int getHuffmanBits(int bitIndex, int nBits) 279 | { 280 | int number = 0; 281 | int weight = 1; 282 | 283 | // Convert consecutive bits into a number. 284 | // 285 | // Note that 'i' is initialized by 'nBits - 1', not by 1. 286 | // This is because "3.1.1. Packing into bytes" in RFC 1951 287 | // says as follows: 288 | // 289 | // Huffman codes are packed starting with the most 290 | // significant bit of the code. 291 | // 292 | for (int i = nBits - 1; 0 <= i; --i, weight *= 2) 293 | { 294 | // getBit() returns true if the bit is set. 295 | if (getBit(bitIndex + i)) 296 | { 297 | number += weight; 298 | } 299 | } 300 | 301 | return number; 302 | } 303 | 304 | 305 | public boolean readBit(int[] bitIndex) 306 | { 307 | boolean result = getBit(bitIndex[0]); 308 | 309 | ++bitIndex[0]; 310 | 311 | return result; 312 | } 313 | 314 | 315 | public int readBits(int[] bitIndex, int nBits) 316 | { 317 | int number = getBits(bitIndex[0], nBits); 318 | 319 | bitIndex[0] += nBits; 320 | 321 | return number; 322 | } 323 | 324 | 325 | public void setBit(int bitIndex, boolean bit) 326 | { 327 | int index = bitIndex / 8; 328 | int shift = bitIndex % 8; 329 | int value = get(index); 330 | 331 | if (bit) 332 | { 333 | value |= (1 << shift); 334 | } 335 | else 336 | { 337 | value &= ~(1 << shift); 338 | } 339 | 340 | mBuffer.put(index, (byte)value); 341 | } 342 | 343 | 344 | public void clearBit(int bitIndex) 345 | { 346 | setBit(bitIndex, false); 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/ConnectThread.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2017 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | class ConnectThread extends WebSocketThread 20 | { 21 | public ConnectThread(WebSocket ws) 22 | { 23 | super("ConnectThread", ws, ThreadType.CONNECT_THREAD); 24 | } 25 | 26 | 27 | @Override 28 | public void runMain() 29 | { 30 | try 31 | { 32 | mWebSocket.connect(); 33 | } 34 | catch (WebSocketException e) 35 | { 36 | handleError(e); 37 | } 38 | } 39 | 40 | 41 | private void handleError(WebSocketException cause) 42 | { 43 | ListenerManager manager = mWebSocket.getListenerManager(); 44 | 45 | manager.callOnError(cause); 46 | manager.callOnConnectError(cause); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/Connectable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import java.util.concurrent.Callable; 20 | 21 | 22 | /** 23 | * An implementation of {@link Callable} interface that calls 24 | * {@link WebSocket#connect()}. 25 | * 26 | * @since 1.7 27 | */ 28 | class Connectable implements Callable 29 | { 30 | private final WebSocket mWebSocket; 31 | 32 | 33 | public Connectable(WebSocket ws) 34 | { 35 | mWebSocket = ws; 36 | } 37 | 38 | 39 | @Override 40 | public WebSocket call() throws WebSocketException 41 | { 42 | return mWebSocket.connect(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/CounterPayloadGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | class CounterPayloadGenerator implements PayloadGenerator 20 | { 21 | private long mCount; 22 | 23 | 24 | @Override 25 | public byte[] generate() 26 | { 27 | return Misc.getBytesUTF8(String.valueOf(increment())); 28 | } 29 | 30 | 31 | private long increment() 32 | { 33 | // Increment the counter. 34 | mCount = Math.max(mCount + 1, 1); 35 | 36 | return mCount; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/DeflateCompressor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import java.io.ByteArrayOutputStream; 20 | import java.io.IOException; 21 | import java.util.zip.Deflater; 22 | import java.util.zip.DeflaterOutputStream; 23 | 24 | 25 | /** 26 | * DEFLATE (RFC 1951) 27 | * compressor implementation. 28 | */ 29 | class DeflateCompressor 30 | { 31 | public static byte[] compress(byte[] input) throws IOException 32 | { 33 | // Destination where compressed data will be stored. 34 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 35 | 36 | // Create a compressor. 37 | Deflater deflater = createDeflater(); 38 | DeflaterOutputStream dos = new DeflaterOutputStream(baos, deflater); 39 | 40 | // Compress the data. 41 | // 42 | // Some other implementations such as Jetty and Tyrus use 43 | // Deflater.deflate(byte[], int, int, int) with Deflate.SYNC_FLUSH, 44 | // but this implementation does not do it intentionally because the 45 | // method and the constant value are not available before Java 7. 46 | dos.write(input, 0, input.length); 47 | dos.close(); 48 | 49 | // Release the resources held by the compressor. 50 | deflater.end(); 51 | 52 | // Retrieve the compressed data. 53 | return baos.toByteArray(); 54 | } 55 | 56 | 57 | private static Deflater createDeflater() 58 | { 59 | // The second argument (nowrap) is true to get only DEFLATE 60 | // blocks without the ZLIB header and checksum fields. 61 | return new Deflater(Deflater.DEFAULT_COMPRESSION, true); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/DeflateDecompressor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | /** 20 | * DEFLATE (RFC 1951) 21 | * decompressor implementation from scratch. 22 | */ 23 | class DeflateDecompressor 24 | { 25 | public static void decompress(ByteArray input, ByteArray output) throws FormatException 26 | { 27 | decompress(input, 0, output); 28 | } 29 | 30 | 31 | private static void decompress(ByteArray input, int index, ByteArray output) throws FormatException 32 | { 33 | // The data is compressed on a bit basis, so use a bit index. 34 | int[] bitIndex = new int[1]; 35 | bitIndex[0] = index * 8; 36 | 37 | // Process all blocks one by one until the end. 38 | // inflateBlock() returns false if no more block exists. 39 | while (inflateBlock(input, bitIndex, output)) {} 40 | } 41 | 42 | 43 | private static boolean inflateBlock( 44 | ByteArray input, int[] bitIndex, ByteArray output) throws FormatException 45 | { 46 | // Each block has a block header which consists of 3 bits. 47 | // See 3.2.3. of RFC 1951. 48 | 49 | // The first bit indicates whether the block is the last one or not. 50 | boolean last = input.readBit(bitIndex); 51 | 52 | // The combination of the second and the third bits indicate the 53 | // compression type of the block. Compression types are as follows: 54 | // 55 | // 00: No compression. 56 | // 01: Compressed with fixed Huffman codes 57 | // 10: Compressed with dynamic Huffman codes 58 | // 11: Reserved (error) 59 | // 60 | int type = input.readBits(bitIndex, 2); 61 | 62 | switch (type) 63 | { 64 | // No compression 65 | case 0: 66 | inflatePlainBlock(input, bitIndex, output); 67 | break; 68 | 69 | // Compressed with fixed Huffman codes 70 | case 1: 71 | inflateFixedBlock(input, bitIndex, output); 72 | break; 73 | 74 | // Compressed with dynamic Huffman codes 75 | case 2: 76 | inflateDynamicBlock(input, bitIndex, output); 77 | break; 78 | 79 | // Bad format 80 | default: 81 | // Bad compression type at the bit index. 82 | String message = String.format( 83 | "[%s] Bad compression type '11' at the bit index '%d'.", 84 | DeflateDecompressor.class.getSimpleName(), bitIndex[0]); 85 | 86 | throw new FormatException(message); 87 | } 88 | 89 | // If no more data are available. 90 | if (input.length() <= (bitIndex[0] / 8)) 91 | { 92 | // Last even if BFINAL bit is false. 93 | last = true; 94 | } 95 | 96 | // Return true if this block is not the last one. 97 | return !last; 98 | } 99 | 100 | 101 | private static void inflatePlainBlock(ByteArray input, int[] bitIndex, ByteArray output) 102 | { 103 | // 3.2.4 Non-compressed blocks (BTYPE=00) 104 | 105 | // Skip any remaining bits in current partially processed byte. 106 | int bi = (bitIndex[0] + 7) & ~7; 107 | 108 | // Data copy is performed on a byte basis, so convert the bit index 109 | // to a byte index. 110 | int index = bi / 8; 111 | 112 | // LEN: 2 bytes. The data length. 113 | int len = (input.get(index) & 0xFF) + (input.get(index + 1) & 0xFF) * 256; 114 | 115 | // NLEN: 2 bytes. The one's complement of LEN. 116 | 117 | // Skip LEN and NLEN. 118 | index += 4; 119 | 120 | // Copy the data to the output. 121 | output.put(input, index, len); 122 | 123 | // Make the bitIndex point to the bit next to 124 | // the end of the copied data. 125 | bitIndex[0] = (index + len) * 8; 126 | } 127 | 128 | 129 | private static void inflateFixedBlock( 130 | ByteArray input, int[] bitIndex, ByteArray output) throws FormatException 131 | { 132 | // 3.2.6 Compression with fixed Huffman codes (BTYPE=01) 133 | 134 | // Inflate the compressed data using the pre-defined 135 | // conversion tables. The specification says in 3.2.2 136 | // as follows. 137 | // 138 | // The only differences between the two compressed 139 | // cases is how the Huffman codes for the literal/ 140 | // length and distance alphabets are defined. 141 | // 142 | // The "two compressed cases" in the above sentence are 143 | // "fixed Huffman codes" and "dynamic Huffman codes". 144 | inflateData(input, bitIndex, output, 145 | FixedLiteralLengthHuffman.getInstance(), 146 | FixedDistanceHuffman.getInstance()); 147 | } 148 | 149 | 150 | private static void inflateDynamicBlock( 151 | ByteArray input, int[] bitIndex, ByteArray output) throws FormatException 152 | { 153 | // 3.2.7 Compression with dynamic Huffman codes (BTYPE=10) 154 | 155 | // Read 2 tables. One is a table to convert "code value of literal/length 156 | // alphabet" into "literal/length symbol". The other is a table to convert 157 | // "code value of distance alphabet" into "distance symbol". 158 | Huffman[] tables = new Huffman[2]; 159 | DeflateUtil.readDynamicTables(input, bitIndex, tables); 160 | 161 | // The actual compressed data of this block. The data are encoded using 162 | // the literal/length and distance Huffman codes that were parsed above. 163 | inflateData(input, bitIndex, output, tables[0], tables[1]); 164 | } 165 | 166 | 167 | private static void inflateData( 168 | ByteArray input, int[] bitIndex, ByteArray output, 169 | Huffman literalLengthHuffman, Huffman distanceHuffman) throws FormatException 170 | { 171 | // 3.2.5 Compressed blocks (length and distance codes) 172 | 173 | while (true) 174 | { 175 | // Read a literal/length symbol from the input. 176 | int literalLength = literalLengthHuffman.readSym(input, bitIndex); 177 | 178 | // Symbol value '256' indicates the end. 179 | if (literalLength == 256) 180 | { 181 | // End of this data. 182 | break; 183 | } 184 | 185 | // Symbol values from 0 to 255 represent literal values. 186 | if (0 <= literalLength && literalLength <= 255) 187 | { 188 | // Output as is. 189 | output.put(literalLength); 190 | continue; 191 | } 192 | 193 | // Symbol values from 257 to 285 represent pairs. 194 | // Depending on symbol values, some extra bits in the input may be 195 | // consumed to compute the length. 196 | int length = DeflateUtil.readLength(input, bitIndex, literalLength); 197 | 198 | // Read the distance from the input. 199 | int distance = DeflateUtil.readDistance(input, bitIndex, distanceHuffman); 200 | 201 | // Extract some data from the output buffer and copy them. 202 | duplicate(length, distance, output); 203 | } 204 | } 205 | 206 | 207 | private static void duplicate(int length, int distance, ByteArray output) 208 | { 209 | // Get the number of bytes written so far. 210 | int sourceLength = output.length(); 211 | 212 | // An array to finally append to the output. 213 | byte[] target = new byte[length]; 214 | 215 | // The position from which to start copying data. 216 | int initialPosition = sourceLength - distance; 217 | int sourceIndex = initialPosition; 218 | 219 | for (int targetIndex = 0; targetIndex < length; ++targetIndex, ++sourceIndex) 220 | { 221 | if (sourceLength <= sourceIndex) 222 | { 223 | // Reached the end of the current output buffer. 224 | // The specification says as follows in 3.2.3. 225 | // 226 | // Note also that the referenced string may 227 | // overlap the current position; for example, 228 | // if the last 2 bytes decoded have values X 229 | // and Y, a string reference with adds X,Y,X,Y,X to the output 231 | // stream. 232 | 233 | // repeat. 234 | sourceIndex = initialPosition; 235 | } 236 | 237 | target[targetIndex] = output.get(sourceIndex); 238 | } 239 | 240 | // Append the duplicated bytes to the output. 241 | output.put(target); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/DeflateUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | /** 20 | * Utility methods for DEFLATE (RFC 1951). 21 | */ 22 | class DeflateUtil 23 | { 24 | private static int[] INDICES_FROM_CODE_LENGTH_ORDER = 25 | { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; 26 | 27 | 28 | public static void readDynamicTables( 29 | ByteArray input, int[] bitIndex, Huffman[] tables) throws FormatException 30 | { 31 | // 3.2.7 Compression with dynamic Huffman codes (BTYPE=10) 32 | 33 | // 5 Bits: HLIT, The number of Literal/Length codes - 257 (257 - 286) 34 | int hlit = input.readBits(bitIndex, 5) + 257; 35 | 36 | // 5 Bits: HDIST, The number of Distance codes - 1 (1 - 32) 37 | int hdist = input.readBits(bitIndex, 5) + 1; 38 | 39 | // 4 Bits: HCLEN, The number of Code Length codes - 4 (4 - 19) 40 | int hclen = input.readBits(bitIndex, 4) + 4; 41 | 42 | // (hclen * 3) bits: code lengths of "values of code length". 43 | // 44 | // Note that "values of code lengths" (which ranges from 0 to 18) 45 | // themselves are compressed using Huffman code. In addition, 46 | // the order here is strange. 47 | int[] codeLengthsFromCodeLengthValue = new int[19]; 48 | for (int i = 0; i < hclen; ++i) 49 | { 50 | byte codeLengthOfCodeLengthValue = (byte)input.readBits(bitIndex, 3); 51 | 52 | // The strange order is converted into a normal index here. 53 | int index = codeLengthOrderToIndex(i); 54 | 55 | codeLengthsFromCodeLengthValue[index] = codeLengthOfCodeLengthValue; 56 | } 57 | 58 | // Create a table to convert "code value of code length value" into 59 | // "code length value". 60 | Huffman codeLengthHuffman = new Huffman(codeLengthsFromCodeLengthValue); 61 | 62 | // hlit code lengths for literal/length alphabet. The code lengths are 63 | // encoded using the code length Huffman code that was parsed above. 64 | int[] codeLengthsFromLiteralLengthCode = new int[hlit]; 65 | readCodeLengths(input, bitIndex, codeLengthsFromLiteralLengthCode, codeLengthHuffman); 66 | 67 | // Create a table to convert "code value of literal/length alphabet" 68 | // into "literal/length symbol". 69 | Huffman literalLengthHuffman = new Huffman(codeLengthsFromLiteralLengthCode); 70 | 71 | // hdist code lengths for the distance alphabet. The code lengths are 72 | // encoded using the code length Huffman code that was parsed above. 73 | int[] codeLengthsFromDistanceCode = new int[hdist]; 74 | readCodeLengths(input, bitIndex, codeLengthsFromDistanceCode, codeLengthHuffman); 75 | 76 | // Create a table to convert "code value of distance alphabet" into 77 | // "distance symbol". 78 | Huffman distanceHuffman = new Huffman(codeLengthsFromDistanceCode); 79 | 80 | tables[0] = literalLengthHuffman; 81 | tables[1] = distanceHuffman; 82 | } 83 | 84 | 85 | private static void readCodeLengths( 86 | ByteArray input, int bitIndex[], int[] codeLengths, 87 | Huffman codeLengthHuffman) throws FormatException 88 | { 89 | // 3.2.7 Compression with dynamic Huffman codes (BTYPE=10) 90 | 91 | for (int i = 0; i < codeLengths.length; ++i) 92 | { 93 | // Read a symbol value of code length. 94 | int codeLength = codeLengthHuffman.readSym(input, bitIndex); 95 | 96 | // Code lengths from 0 to 15 represent 0 to 15, respectively, 97 | // meaning no more extra interpretation is needed. 98 | if (0 <= codeLength && codeLength <= 15) 99 | { 100 | // As is. 101 | codeLengths[i] = codeLength; 102 | continue; 103 | } 104 | 105 | int repeatCount; 106 | 107 | switch (codeLength) 108 | { 109 | case 16: 110 | // Copy the previous code length for 3 - 6 times. 111 | // The next 2 bits (+3) indicate repeat count. 112 | codeLength = codeLengths[i - 1]; 113 | repeatCount = input.readBits(bitIndex, 2) + 3; 114 | break; 115 | 116 | case 17: 117 | // Copy a code length of 0 for 3 - 10 times. 118 | // The next 3 bits (+3) indicate repeat count. 119 | codeLength = 0; 120 | repeatCount = input.readBits(bitIndex, 3) + 3; 121 | break; 122 | 123 | case 18: 124 | // Copy a code length of 0 for 11 - 138 times. 125 | // The next 7 bits (+11) indicate repeat count. 126 | codeLength = 0; 127 | repeatCount = input.readBits(bitIndex, 7) + 11; 128 | break; 129 | 130 | default: 131 | // Bad code length. 132 | String message = String.format( 133 | "[%s] Bad code length '%d' at the bit index '%d'.", 134 | DeflateUtil.class.getSimpleName(), codeLength, bitIndex); 135 | 136 | throw new FormatException(message); 137 | } 138 | 139 | // Copy the code length as many times as specified. 140 | for (int j = 0; j < repeatCount; ++j) 141 | { 142 | codeLengths[i + j] = codeLength; 143 | } 144 | 145 | // Skip the range filled by the above copy. 146 | i += repeatCount - 1; 147 | } 148 | } 149 | 150 | 151 | private static int codeLengthOrderToIndex(int order) 152 | { 153 | // 3.2.7 Compression with dynamic Huffman codes (BTYPE=10) 154 | // 155 | // See the description about "(HCLEN + 4) x 3 bits" in the 156 | // specification. 157 | return INDICES_FROM_CODE_LENGTH_ORDER[order]; 158 | } 159 | 160 | 161 | public static int readLength( 162 | ByteArray input, int[] bitIndex, int literalLength) throws FormatException 163 | { 164 | // 3.2.5 Compressed blocks (length and distance code) 165 | 166 | int baseValue; 167 | int nBits; 168 | 169 | switch (literalLength) 170 | { 171 | case 257: 172 | case 258: 173 | case 259: 174 | case 260: 175 | case 261: 176 | case 262: 177 | case 263: 178 | case 264: 179 | return (literalLength - 254); 180 | 181 | case 265: baseValue = 11; nBits = 1; break; 182 | case 266: baseValue = 13; nBits = 1; break; 183 | case 267: baseValue = 15; nBits = 1; break; 184 | case 268: baseValue = 17; nBits = 1; break; 185 | case 269: baseValue = 19; nBits = 2; break; 186 | case 270: baseValue = 23; nBits = 2; break; 187 | case 271: baseValue = 27; nBits = 2; break; 188 | case 272: baseValue = 31; nBits = 2; break; 189 | case 273: baseValue = 35; nBits = 3; break; 190 | case 274: baseValue = 43; nBits = 3; break; 191 | case 275: baseValue = 51; nBits = 3; break; 192 | case 276: baseValue = 59; nBits = 3; break; 193 | case 277: baseValue = 67; nBits = 4; break; 194 | case 278: baseValue = 83; nBits = 4; break; 195 | case 279: baseValue = 99; nBits = 4; break; 196 | case 280: baseValue = 115; nBits = 4; break; 197 | case 281: baseValue = 131; nBits = 5; break; 198 | case 282: baseValue = 163; nBits = 5; break; 199 | case 283: baseValue = 195; nBits = 5; break; 200 | case 284: baseValue = 227; nBits = 5; break; 201 | case 285: return 258; 202 | default: 203 | // Bad literal/length code. 204 | String message = String.format( 205 | "[%s] Bad literal/length code '%d' at the bit index '%d'.", 206 | DeflateUtil.class.getSimpleName(), literalLength, bitIndex[0]); 207 | 208 | throw new FormatException(message); 209 | } 210 | 211 | // Read a value to add to the base value. 212 | int n = input.readBits(bitIndex, nBits); 213 | 214 | return baseValue + n; 215 | } 216 | 217 | 218 | public static int readDistance( 219 | ByteArray input, int[] bitIndex, Huffman distanceHuffman) throws FormatException 220 | { 221 | // 3.2.5 Compressed blocks (length and distance code) 222 | 223 | // Read a distance code from the input. 224 | // It is expected to range from 0 to 29. 225 | int code = distanceHuffman.readSym(input, bitIndex); 226 | 227 | int baseValue; 228 | int nBits; 229 | 230 | switch (code) 231 | { 232 | case 0: 233 | case 1: 234 | case 2: 235 | case 3: 236 | return code + 1; 237 | 238 | case 4: baseValue = 5; nBits = 1; break; 239 | case 5: baseValue = 7; nBits = 1; break; 240 | case 6: baseValue = 9; nBits = 2; break; 241 | case 7: baseValue = 13; nBits = 2; break; 242 | case 8: baseValue = 17; nBits = 3; break; 243 | case 9: baseValue = 25; nBits = 3; break; 244 | case 10: baseValue = 33; nBits = 4; break; 245 | case 11: baseValue = 49; nBits = 4; break; 246 | case 12: baseValue = 65; nBits = 5; break; 247 | case 13: baseValue = 97; nBits = 5; break; 248 | case 14: baseValue = 129; nBits = 6; break; 249 | case 15: baseValue = 193; nBits = 6; break; 250 | case 16: baseValue = 257; nBits = 7; break; 251 | case 17: baseValue = 385; nBits = 7; break; 252 | case 18: baseValue = 513; nBits = 8; break; 253 | case 19: baseValue = 769; nBits = 8; break; 254 | case 20: baseValue = 1025; nBits = 9; break; 255 | case 21: baseValue = 1537; nBits = 9; break; 256 | case 22: baseValue = 2049; nBits = 10; break; 257 | case 23: baseValue = 3073; nBits = 10; break; 258 | case 24: baseValue = 4097; nBits = 11; break; 259 | case 25: baseValue = 6145; nBits = 11; break; 260 | case 26: baseValue = 8193; nBits = 12; break; 261 | case 27: baseValue = 12289; nBits = 12; break; 262 | case 28: baseValue = 16385; nBits = 13; break; 263 | case 29: baseValue = 24577; nBits = 13; break; 264 | default: 265 | // Distance codes 30-31 will never actually occur 266 | // in the compressed data, the specification says. 267 | 268 | // Bad distance code. 269 | String message = String.format( 270 | "[%s] Bad distance code '%d' at the bit index '%d'.", 271 | DeflateUtil.class.getSimpleName(), code, bitIndex[0]); 272 | 273 | throw new FormatException(message); 274 | } 275 | 276 | // Read a value to add to the base value. 277 | int n = input.readBits(bitIndex, nBits); 278 | 279 | return baseValue + n; 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/DualStackMode.java: -------------------------------------------------------------------------------- 1 | package com.neovisionaries.ws.client; 2 | 3 | 4 | /** 5 | * The dual stack mode defines which IP address families will be used to 6 | * establish a connection. 7 | */ 8 | public enum DualStackMode 9 | { 10 | /** 11 | * Try both IPv4 and IPv6 to establish a connection. Used by default and 12 | * should generally be preferred. 13 | */ 14 | BOTH, 15 | 16 | 17 | /** 18 | * Only use IPv4 to establish a connection. 19 | */ 20 | IPV4_ONLY, 21 | 22 | 23 | /** 24 | * Only use IPv6 to establish a connection. 25 | */ 26 | IPV6_ONLY, 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/FinishThread.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | class FinishThread extends WebSocketThread 20 | { 21 | public FinishThread(WebSocket ws) 22 | { 23 | super("FinishThread", ws, ThreadType.FINISH_THREAD); 24 | } 25 | 26 | 27 | @Override 28 | public void runMain() 29 | { 30 | mWebSocket.finish(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/FixedDistanceHuffman.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | class FixedDistanceHuffman extends Huffman 20 | { 21 | private static final FixedDistanceHuffman INSTANCE = new FixedDistanceHuffman(); 22 | 23 | 24 | private FixedDistanceHuffman() 25 | { 26 | super(buildCodeLensFromSym()); 27 | } 28 | 29 | 30 | private static int[] buildCodeLensFromSym() 31 | { 32 | // 3.2.6. Compression with fixed Huffman codes (BTYPE=01) 33 | // 34 | // "Distance codes 0-31 are represented by (fixed-length) 35 | // 5-bit codes", the specification says. 36 | 37 | int[] codeLengths = new int[32]; 38 | 39 | for (int symbol = 0; symbol < 32; ++symbol) 40 | { 41 | codeLengths[symbol] = 5; 42 | } 43 | 44 | // Let Huffman class generate code values from code lengths. 45 | // Note that "code lengths are sufficient to generate the 46 | // actual codes". See 3.2.2. of RFC 1951. 47 | return codeLengths; 48 | } 49 | 50 | 51 | public static FixedDistanceHuffman getInstance() 52 | { 53 | return INSTANCE; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/FixedLiteralLengthHuffman.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | class FixedLiteralLengthHuffman extends Huffman 20 | { 21 | private static final FixedLiteralLengthHuffman INSTANCE = new FixedLiteralLengthHuffman(); 22 | 23 | 24 | private FixedLiteralLengthHuffman() 25 | { 26 | super(buildCodeLensFromSym()); 27 | } 28 | 29 | 30 | private static int[] buildCodeLensFromSym() 31 | { 32 | // 3.2.6. Compression with fixed Huffman codes (BTYPE=01) 33 | // 34 | // Lit Value Bits Codes 35 | // --------- ---- --------------------------- 36 | // 0 - 143 8 00110000 through 10111111 37 | // 144 - 255 9 110010000 through 111111111 38 | // 256 - 279 7 0000000 through 0010111 39 | // 280 - 287 8 11000000 through 11000111 40 | 41 | int[] codeLengths = new int[288]; 42 | 43 | int symbol; 44 | 45 | // 0 - 143 46 | for (symbol = 0; symbol < 144; ++symbol) 47 | { 48 | codeLengths[symbol] = 8; 49 | } 50 | 51 | // 144 - 255 52 | for (; symbol < 256; ++symbol) 53 | { 54 | codeLengths[symbol] = 9; 55 | } 56 | 57 | // 256 - 279 58 | for (; symbol < 280; ++symbol) 59 | { 60 | codeLengths[symbol] = 7; 61 | } 62 | 63 | // 280 - 287 64 | for (; symbol < 288; ++symbol) 65 | { 66 | codeLengths[symbol] = 8; 67 | } 68 | 69 | // Huffman class generates code values from code lengths. 70 | // Note that "code lengths are sufficient to generate the 71 | // actual codes". See 3.2.2. of RFC 1951. 72 | return codeLengths; 73 | } 74 | 75 | 76 | public static FixedLiteralLengthHuffman getInstance() 77 | { 78 | return INSTANCE; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/FormatException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | class FormatException extends Exception 20 | { 21 | private static final long serialVersionUID = 1L; 22 | 23 | 24 | public FormatException(String message) 25 | { 26 | super(message); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/HostnameUnverifiedException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import javax.net.ssl.SSLSocket; 20 | 21 | 22 | /** 23 | * The certificate of the peer does not match the expected hostname. 24 | * 25 | *

26 | * {@link #getError()} of this class returns {@link WebSocketError#HOSTNAME_UNVERIFIED 27 | * HOSTNAME_UNVERIFIED}. 28 | *

29 | * 30 | *

31 | * See Verify that certificate is valid for server hostname (#107). 33 | *

34 | * 35 | * @since 2.1 36 | */ 37 | public class HostnameUnverifiedException extends WebSocketException 38 | { 39 | private static final long serialVersionUID = 1L; 40 | 41 | 42 | private final SSLSocket mSSLSocket; 43 | private final String mHostname; 44 | 45 | 46 | /** 47 | * Constructor with the SSL socket and the expected hostname. 48 | * 49 | * @param socket 50 | * The SSL socket against which the hostname verification failed. 51 | * 52 | * @param hostname 53 | * The expected hostname. 54 | */ 55 | public HostnameUnverifiedException(SSLSocket socket, String hostname) 56 | { 57 | super(WebSocketError.HOSTNAME_UNVERIFIED, 58 | String.format("The certificate of the peer%s does not match the expected hostname (%s)", 59 | stringifyPrincipal(socket), hostname)); 60 | 61 | mSSLSocket = socket; 62 | mHostname = hostname; 63 | } 64 | 65 | 66 | private static String stringifyPrincipal(SSLSocket socket) 67 | { 68 | try 69 | { 70 | return String.format(" (%s)", socket.getSession().getPeerPrincipal().toString()); 71 | } 72 | catch (Exception e) 73 | { 74 | // Principal information is not available. 75 | return ""; 76 | } 77 | } 78 | 79 | 80 | /** 81 | * Get the SSL socket against which the hostname verification failed. 82 | * 83 | * @return 84 | * The SSL socket. 85 | */ 86 | public SSLSocket getSSLSocket() 87 | { 88 | return mSSLSocket; 89 | } 90 | 91 | 92 | /** 93 | * Get the expected hostname. 94 | * 95 | * @return 96 | * The expected hostname. 97 | */ 98 | public String getHostname() 99 | { 100 | return mHostname; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/Huffman.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | /** 20 | * Huffman coding for DEFLATE format (RFC 1951). 22 | */ 23 | class Huffman 24 | { 25 | private final int mMinCodeLen; 26 | private final int mMaxCodeLen; 27 | private final int[] mMaxCodeValsFromCodeLen; 28 | private final int[] mSymsFromCodeVal; 29 | 30 | 31 | public Huffman(int[] codeLensFromSym) 32 | { 33 | // Remember the minimum and maximum code lengths. 34 | mMinCodeLen = Math.max(Misc.min(codeLensFromSym), 1); 35 | mMaxCodeLen = Misc.max(codeLensFromSym); 36 | 37 | // Count the number of entries for each code length. 38 | int[] countsFromCodeLen = createCountsFromCodeLen(codeLensFromSym, mMaxCodeLen); 39 | 40 | // Create an array holding the maximum code values for each code length. 41 | Object[] out = new Object[2]; 42 | mMaxCodeValsFromCodeLen = createMaxCodeValsFromCodeLen(countsFromCodeLen, mMaxCodeLen, out); 43 | 44 | // Create a table to convert code values int symbols. 45 | int[] codeValsFromCodeLen = (int[])out[0]; 46 | int maxCodeVal = ((Integer)out[1]).intValue(); 47 | mSymsFromCodeVal = createSymsFromCodeVal(codeLensFromSym, codeValsFromCodeLen, maxCodeVal); 48 | } 49 | 50 | 51 | /** 52 | * Create an array whose elements have the given initial value. 53 | */ 54 | private static int[] createIntArray(int size, int initialValue) 55 | { 56 | int[] array = new int[size]; 57 | 58 | for (int i = 0; i < size; ++i) 59 | { 60 | array[i] = initialValue; 61 | } 62 | 63 | return array; 64 | } 65 | 66 | 67 | private static int[] createCountsFromCodeLen(int[] codeLensFromSym, int maxCodeLen) 68 | { 69 | int[] countsFromCodeLen = new int[maxCodeLen + 1]; 70 | 71 | // Count the number of entries for each code length. 72 | // This corresponds to the step 1 in 3.2.2. of RFC 1951. 73 | for (int symbol = 0; symbol < codeLensFromSym.length; ++symbol) 74 | { 75 | int codeLength = codeLensFromSym[symbol]; 76 | ++countsFromCodeLen[codeLength]; 77 | } 78 | 79 | return countsFromCodeLen; 80 | } 81 | 82 | 83 | private static int[] createMaxCodeValsFromCodeLen(int[] countsFromCodeLen, int maxCodeLen, Object[] out) 84 | { 85 | // Initialize an array that holds the maximum code values 86 | // for each code length. '-1' indicates that there is no 87 | // code value for the code length. 88 | int[] maxCodeValsFromCodeLen = createIntArray(maxCodeLen + 1, -1); 89 | 90 | // Compute the smallest code value for each code length. 91 | // This corresponds to the step 2 in 3.2.2. of RFC 1951. 92 | int minCodeVal = 0; 93 | int maxCodeVal = 0; 94 | countsFromCodeLen[0] = 0; 95 | int[] codeValsFromCodeLen = new int[maxCodeLen + 1]; 96 | for (int codeLen = 1; codeLen < countsFromCodeLen.length; ++codeLen) 97 | { 98 | // Compute the minimum code value for each code length. 99 | int prevCount = countsFromCodeLen[codeLen - 1]; 100 | minCodeVal = (minCodeVal + prevCount) << 1; 101 | codeValsFromCodeLen[codeLen] = minCodeVal; 102 | 103 | // Compute the maximum code value for each code length. 104 | maxCodeVal = minCodeVal + countsFromCodeLen[codeLen] - 1; 105 | maxCodeValsFromCodeLen[codeLen] = maxCodeVal; 106 | } 107 | 108 | out[0] = codeValsFromCodeLen; 109 | out[1] = Integer.valueOf(maxCodeVal); 110 | 111 | return maxCodeValsFromCodeLen; 112 | } 113 | 114 | 115 | private static int[] createSymsFromCodeVal(int[] codeLensFromSym, int[] codeValsFromCodeLen, int maxCodeVal) 116 | { 117 | int[] symsFromCodeVal = new int[maxCodeVal + 1]; 118 | 119 | // Set up a table to convert code values into symbols. 120 | // This corresponds to the step 3 in 3.2.2. of RFC 1951. 121 | 122 | for (int sym = 0; sym < codeLensFromSym.length; ++sym) 123 | { 124 | int codeLen = codeLensFromSym[sym]; 125 | 126 | if (codeLen == 0) 127 | { 128 | continue; 129 | } 130 | 131 | int codeVal = codeValsFromCodeLen[codeLen]++; 132 | symsFromCodeVal[codeVal] = sym; 133 | } 134 | 135 | return symsFromCodeVal; 136 | } 137 | 138 | 139 | public int readSym(ByteArray data, int[] bitIndex) throws FormatException 140 | { 141 | for (int codeLen = mMinCodeLen; codeLen <= mMaxCodeLen; ++codeLen) 142 | { 143 | // Get the maximum one from among the code values 144 | // whose code length is 'codeLen'. 145 | int maxCodeVal = mMaxCodeValsFromCodeLen[codeLen]; 146 | 147 | if (maxCodeVal < 0) 148 | { 149 | // There is no code value whose code length is 'codeLen'. 150 | continue; 151 | } 152 | 153 | // Read a code value from the input assuming its code length is 'codeLen'. 154 | int codeVal = data.getHuffmanBits(bitIndex[0], codeLen); 155 | 156 | if (maxCodeVal < codeVal) 157 | { 158 | // The read code value is bigger than the maximum code value 159 | // among the code values whose code length is 'codeLen'. 160 | // 161 | // Considering the latter rule of the two added for DEFLATE format 162 | // (3.2.2. Use of Huffman coding in the "deflate" format), 163 | // 164 | // * All codes of a given bit length have lexicographically 165 | // consecutive values, in the same order as the symbols 166 | // they represent; 167 | // 168 | // * Shorter codes lexicographically precede longer codes. 169 | // 170 | // We can expect that the code length of the code value we are 171 | // parsing is longer than the current 'codeLen'. 172 | continue; 173 | } 174 | 175 | // Convert the code value into a symbol value. 176 | int sym = mSymsFromCodeVal[codeVal]; 177 | 178 | // Consume the bits of the code value. 179 | bitIndex[0] += codeLen; 180 | 181 | return sym; 182 | } 183 | 184 | // Bad code at the bit index. 185 | String message = String.format( 186 | "[%s] Bad code at the bit index '%d'.", 187 | getClass().getSimpleName(), bitIndex[0]); 188 | 189 | throw new FormatException(message); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/InsufficientDataException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the License is distributed on an 12 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific 14 | * language governing permissions and limitations under the 15 | * License. 16 | */ 17 | package com.neovisionaries.ws.client; 18 | 19 | 20 | class InsufficientDataException extends WebSocketException 21 | { 22 | private static final long serialVersionUID = 1L; 23 | 24 | 25 | private final int mRequestedByteCount; 26 | private final int mReadByteCount; 27 | 28 | 29 | public InsufficientDataException(int requestedByteCount, int readByteCount) 30 | { 31 | super(WebSocketError.INSUFFICENT_DATA, "The end of the stream has been reached unexpectedly."); 32 | 33 | mRequestedByteCount = requestedByteCount; 34 | mReadByteCount = readByteCount; 35 | } 36 | 37 | 38 | public int getRequestedByteCount() 39 | { 40 | return mRequestedByteCount; 41 | } 42 | 43 | 44 | public int getReadByteCount() 45 | { 46 | return mReadByteCount; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/NoMoreFrameException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the License is distributed on an 12 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific 14 | * language governing permissions and limitations under the 15 | * License. 16 | */ 17 | package com.neovisionaries.ws.client; 18 | 19 | 20 | class NoMoreFrameException extends WebSocketException 21 | { 22 | private static final long serialVersionUID = 1L; 23 | 24 | 25 | public NoMoreFrameException() 26 | { 27 | super(WebSocketError.NO_MORE_FRAME, "No more WebSocket frame from the server."); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/OkHostnameVerifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.neovisionaries.ws.client; 19 | 20 | import java.security.cert.Certificate; 21 | import java.security.cert.CertificateParsingException; 22 | import java.security.cert.X509Certificate; 23 | import java.util.ArrayList; 24 | import java.util.Collection; 25 | import java.util.Collections; 26 | import java.util.List; 27 | import java.util.Locale; 28 | import java.util.regex.Pattern; 29 | import javax.net.ssl.HostnameVerifier; 30 | import javax.net.ssl.SSLException; 31 | import javax.net.ssl.SSLSession; 32 | import javax.security.auth.x500.X500Principal; 33 | 34 | /** 35 | * A HostnameVerifier consistent with RFC 2818. 37 | */ 38 | final class OkHostnameVerifier implements HostnameVerifier { 39 | public static final OkHostnameVerifier INSTANCE = new OkHostnameVerifier(); 40 | 41 | /** 42 | * Quick and dirty pattern to differentiate IP addresses from hostnames. This 43 | * is an approximation of Android's private InetAddress#isNumeric API. 44 | * 45 | *

This matches IPv6 addresses as a hex string containing at least one 46 | * colon, and possibly including dots after the first colon. It matches IPv4 47 | * addresses as strings containing only decimal digits and dots. This pattern 48 | * matches strings like "a:.23" and "54" that are neither IP addresses nor 49 | * hostnames; they will be verified as IP addresses (which is a more strict 50 | * verification). 51 | */ 52 | private static final Pattern VERIFY_AS_IP_ADDRESS = Pattern.compile( 53 | "([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)"); 54 | 55 | private static final int ALT_DNS_NAME = 2; 56 | private static final int ALT_IPA_NAME = 7; 57 | 58 | private OkHostnameVerifier() { 59 | } 60 | 61 | @Override 62 | public boolean verify(String host, SSLSession session) { 63 | try { 64 | Certificate[] certificates = session.getPeerCertificates(); 65 | return verify(host, (X509Certificate) certificates[0]); 66 | } catch (SSLException e) { 67 | return false; 68 | } 69 | } 70 | 71 | public boolean verify(String host, X509Certificate certificate) { 72 | return verifyAsIpAddress(host) 73 | ? verifyIpAddress(host, certificate) 74 | : verifyHostName(host, certificate); 75 | } 76 | 77 | static boolean verifyAsIpAddress(String host) { 78 | return VERIFY_AS_IP_ADDRESS.matcher(host).matches(); 79 | } 80 | 81 | /** 82 | * Returns true if {@code certificate} matches {@code ipAddress}. 83 | */ 84 | private boolean verifyIpAddress(String ipAddress, X509Certificate certificate) { 85 | List altNames = getSubjectAltNames(certificate, ALT_IPA_NAME); 86 | for (int i = 0, size = altNames.size(); i < size; i++) { 87 | if (ipAddress.equalsIgnoreCase(altNames.get(i))) { 88 | return true; 89 | } 90 | } 91 | return false; 92 | } 93 | 94 | /** 95 | * Returns true if {@code certificate} matches {@code hostName}. 96 | */ 97 | private boolean verifyHostName(String hostName, X509Certificate certificate) { 98 | hostName = hostName.toLowerCase(Locale.US); 99 | boolean hasDns = false; 100 | List altNames = getSubjectAltNames(certificate, ALT_DNS_NAME); 101 | for (int i = 0, size = altNames.size(); i < size; i++) { 102 | hasDns = true; 103 | if (verifyHostName(hostName, altNames.get(i))) { 104 | return true; 105 | } 106 | } 107 | 108 | if (!hasDns) { 109 | X500Principal principal = certificate.getSubjectX500Principal(); 110 | // RFC 2818 advises using the most specific name for matching. 111 | String cn = new DistinguishedNameParser(principal).findMostSpecific("cn"); 112 | if (cn != null) { 113 | return verifyHostName(hostName, cn); 114 | } 115 | } 116 | 117 | return false; 118 | } 119 | 120 | public static List allSubjectAltNames(X509Certificate certificate) { 121 | List altIpaNames = getSubjectAltNames(certificate, ALT_IPA_NAME); 122 | List altDnsNames = getSubjectAltNames(certificate, ALT_DNS_NAME); 123 | List result = new ArrayList(altIpaNames.size() + altDnsNames.size()); 124 | result.addAll(altIpaNames); 125 | result.addAll(altDnsNames); 126 | return result; 127 | } 128 | 129 | private static List getSubjectAltNames(X509Certificate certificate, int type) { 130 | List result = new ArrayList(); 131 | try { 132 | Collection subjectAltNames = certificate.getSubjectAlternativeNames(); 133 | if (subjectAltNames == null) { 134 | return Collections.emptyList(); 135 | } 136 | for (Object subjectAltName : subjectAltNames) { 137 | List entry = (List) subjectAltName; 138 | if (entry == null || entry.size() < 2) { 139 | continue; 140 | } 141 | Integer altNameType = (Integer) entry.get(0); 142 | if (altNameType == null) { 143 | continue; 144 | } 145 | if (altNameType == type) { 146 | String altName = (String) entry.get(1); 147 | if (altName != null) { 148 | result.add(altName); 149 | } 150 | } 151 | } 152 | return result; 153 | } catch (CertificateParsingException e) { 154 | return Collections.emptyList(); 155 | } 156 | } 157 | 158 | /** 159 | * Returns {@code true} iff {@code hostName} matches the domain name {@code pattern}. 160 | * 161 | * @param hostName lower-case host name. 162 | * @param pattern domain name pattern from certificate. May be a wildcard pattern such as 163 | * {@code *.android.com}. 164 | */ 165 | private boolean verifyHostName(String hostName, String pattern) { 166 | // Basic sanity checks 167 | // Check length == 0 instead of .isEmpty() to support Java 5. 168 | if ((hostName == null) || (hostName.length() == 0) || (hostName.startsWith(".")) 169 | || (hostName.endsWith(".."))) { 170 | // Invalid domain name 171 | return false; 172 | } 173 | if ((pattern == null) || (pattern.length() == 0) || (pattern.startsWith(".")) 174 | || (pattern.endsWith(".."))) { 175 | // Invalid pattern/domain name 176 | return false; 177 | } 178 | 179 | // Normalize hostName and pattern by turning them into absolute domain names if they are not 180 | // yet absolute. This is needed because server certificates do not normally contain absolute 181 | // names or patterns, but they should be treated as absolute. At the same time, any hostName 182 | // presented to this method should also be treated as absolute for the purposes of matching 183 | // to the server certificate. 184 | // www.android.com matches www.android.com 185 | // www.android.com matches www.android.com. 186 | // www.android.com. matches www.android.com. 187 | // www.android.com. matches www.android.com 188 | if (!hostName.endsWith(".")) { 189 | hostName += '.'; 190 | } 191 | if (!pattern.endsWith(".")) { 192 | pattern += '.'; 193 | } 194 | // hostName and pattern are now absolute domain names. 195 | 196 | pattern = pattern.toLowerCase(Locale.US); 197 | // hostName and pattern are now in lower case -- domain names are case-insensitive. 198 | 199 | if (!pattern.contains("*")) { 200 | // Not a wildcard pattern -- hostName and pattern must match exactly. 201 | return hostName.equals(pattern); 202 | } 203 | // Wildcard pattern 204 | 205 | // WILDCARD PATTERN RULES: 206 | // 1. Asterisk (*) is only permitted in the left-most domain name label and must be the 207 | // only character in that label (i.e., must match the whole left-most label). 208 | // For example, *.example.com is permitted, while *a.example.com, a*.example.com, 209 | // a*b.example.com, a.*.example.com are not permitted. 210 | // 2. Asterisk (*) cannot match across domain name labels. 211 | // For example, *.example.com matches test.example.com but does not match 212 | // sub.test.example.com. 213 | // 3. Wildcard patterns for single-label domain names are not permitted. 214 | 215 | if ((!pattern.startsWith("*.")) || (pattern.indexOf('*', 1) != -1)) { 216 | // Asterisk (*) is only permitted in the left-most domain name label and must be the only 217 | // character in that label 218 | return false; 219 | } 220 | 221 | // Optimization: check whether hostName is too short to match the pattern. hostName must be at 222 | // least as long as the pattern because asterisk must match the whole left-most label and 223 | // hostName starts with a non-empty label. Thus, asterisk has to match one or more characters. 224 | if (hostName.length() < pattern.length()) { 225 | // hostName too short to match the pattern. 226 | return false; 227 | } 228 | 229 | if ("*.".equals(pattern)) { 230 | // Wildcard pattern for single-label domain name -- not permitted. 231 | return false; 232 | } 233 | 234 | // hostName must end with the region of pattern following the asterisk. 235 | String suffix = pattern.substring(1); 236 | if (!hostName.endsWith(suffix)) { 237 | // hostName does not end with the suffix 238 | return false; 239 | } 240 | 241 | // Check that asterisk did not match across domain name labels. 242 | int suffixStartIndexInHostName = hostName.length() - suffix.length(); 243 | if ((suffixStartIndexInHostName > 0) 244 | && (hostName.lastIndexOf('.', suffixStartIndexInHostName - 1) != -1)) { 245 | // Asterisk is matching across domain name labels -- not permitted. 246 | return false; 247 | } 248 | 249 | // hostName matches pattern 250 | return true; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/OpeningHandshakeException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the License is distributed on an 12 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific 14 | * language governing permissions and limitations under the 15 | * License. 16 | */ 17 | package com.neovisionaries.ws.client; 18 | 19 | 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | 24 | /** 25 | * An exception raised due to a violation against the WebSocket protocol. 26 | * 27 | * @since 1.19 28 | */ 29 | public class OpeningHandshakeException extends WebSocketException 30 | { 31 | private static final long serialVersionUID = 1L; 32 | 33 | 34 | private final StatusLine mStatusLine; 35 | private final Map> mHeaders; 36 | private final byte[] mBody; 37 | 38 | 39 | OpeningHandshakeException( 40 | WebSocketError error, String message, 41 | StatusLine statusLine, Map> headers) 42 | { 43 | this(error, message, statusLine, headers, null); 44 | } 45 | 46 | 47 | OpeningHandshakeException( 48 | WebSocketError error, String message, 49 | StatusLine statusLine, Map> headers, byte[] body) 50 | { 51 | super(error, message); 52 | 53 | mStatusLine = statusLine; 54 | mHeaders = headers; 55 | mBody = body; 56 | } 57 | 58 | 59 | /** 60 | * Get the status line contained in the WebSocket opening handshake 61 | * response from the server. 62 | * 63 | * @return 64 | * The status line. 65 | */ 66 | public StatusLine getStatusLine() 67 | { 68 | return mStatusLine; 69 | } 70 | 71 | 72 | /** 73 | * Get the HTTP headers contained in the WebSocket opening handshake 74 | * response from the server. 75 | * 76 | * @return 77 | * The HTTP headers. The returned map is an instance of 78 | * {@link java.util.TreeMap TreeMap} with {@link 79 | * String#CASE_INSENSITIVE_ORDER} comparator. 80 | */ 81 | public Map> getHeaders() 82 | { 83 | return mHeaders; 84 | } 85 | 86 | 87 | /** 88 | * Get the response body contained in the WebSocket opening handshake 89 | * response from the server. 90 | * 91 | *

92 | * This method returns a non-null value only when (1) the status code 93 | * is not 101 (Switching Protocols), (2) the response from the server 94 | * has a response body, (3) the response has "Content-Length" header, 95 | * and (4) no error occurred during reading the response body. In other 96 | * cases, this method returns {@code null}. 97 | *

98 | * 99 | * @return 100 | * The response body. 101 | */ 102 | public byte[] getBody() 103 | { 104 | return mBody; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/PayloadGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | /** 20 | * Payload generator. 21 | * 22 | * @since 1.20 23 | * 24 | * @author Takahiko Kawasaki 25 | */ 26 | public interface PayloadGenerator 27 | { 28 | /** 29 | * Generate a payload of a frame. 30 | * 31 | *

32 | * Note that the maximum payload length of control frames 33 | * (e.g. ping frames) is 125 in bytes. Therefore, the length 34 | * of a byte array returned from this method must not exceed 35 | * 125 bytes. 36 | *

37 | * 38 | * @return 39 | * A payload of a frame. 40 | */ 41 | byte[] generate(); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/PerMessageCompressionExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | /** 20 | * Per-Message Compression Extension (RFC 7692). 21 | * 22 | * @see RFC 7692 23 | */ 24 | abstract class PerMessageCompressionExtension extends WebSocketExtension 25 | { 26 | public PerMessageCompressionExtension(String name) 27 | { 28 | super(name); 29 | } 30 | 31 | 32 | public PerMessageCompressionExtension(WebSocketExtension source) 33 | { 34 | super(source); 35 | } 36 | 37 | 38 | /** 39 | * Decompress the compressed message. 40 | */ 41 | protected abstract byte[] decompress(byte[] compressed) throws WebSocketException; 42 | 43 | 44 | /** 45 | * Compress the plain message. 46 | */ 47 | protected abstract byte[] compress(byte[] plain) throws WebSocketException; 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/PeriodicalFrameSender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2018 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import java.util.Timer; 20 | import java.util.TimerTask; 21 | 22 | 23 | abstract class PeriodicalFrameSender 24 | { 25 | private final WebSocket mWebSocket; 26 | private String mTimerName; 27 | private Timer mTimer; 28 | private boolean mScheduled; 29 | private long mInterval; 30 | private PayloadGenerator mGenerator; 31 | 32 | 33 | public PeriodicalFrameSender( 34 | WebSocket webSocket, String timerName, PayloadGenerator generator) 35 | { 36 | mWebSocket = webSocket; 37 | mTimerName = timerName; 38 | mGenerator = generator; 39 | } 40 | 41 | 42 | public void start() 43 | { 44 | setInterval(getInterval()); 45 | } 46 | 47 | 48 | public void stop() 49 | { 50 | synchronized (this) 51 | { 52 | if (mTimer == null) 53 | { 54 | return; 55 | } 56 | 57 | mScheduled = false; 58 | mTimer.cancel(); 59 | } 60 | } 61 | 62 | 63 | public long getInterval() 64 | { 65 | synchronized (this) 66 | { 67 | return mInterval; 68 | } 69 | } 70 | 71 | 72 | public void setInterval(long interval) 73 | { 74 | if (interval < 0) 75 | { 76 | interval = 0; 77 | } 78 | 79 | synchronized (this) 80 | { 81 | mInterval = interval; 82 | } 83 | 84 | if (interval == 0) 85 | { 86 | return; 87 | } 88 | 89 | if (mWebSocket.isOpen() == false) 90 | { 91 | return; 92 | } 93 | 94 | synchronized (this) 95 | { 96 | if (mTimer == null) 97 | { 98 | if (mTimerName == null) 99 | { 100 | mTimer = new Timer(); 101 | } 102 | else 103 | { 104 | mTimer = new Timer(mTimerName); 105 | } 106 | } 107 | 108 | if (mScheduled == false) 109 | { 110 | mScheduled = schedule(mTimer, new Task(), interval); 111 | } 112 | } 113 | } 114 | 115 | 116 | public PayloadGenerator getPayloadGenerator() 117 | { 118 | synchronized (this) 119 | { 120 | return mGenerator; 121 | } 122 | } 123 | 124 | 125 | public void setPayloadGenerator(PayloadGenerator generator) 126 | { 127 | synchronized (this) 128 | { 129 | mGenerator = generator; 130 | } 131 | } 132 | 133 | 134 | public String getTimerName() 135 | { 136 | return mTimerName; 137 | } 138 | 139 | 140 | public void setTimerName(String timerName) 141 | { 142 | synchronized (this) 143 | { 144 | mTimerName = timerName; 145 | } 146 | } 147 | 148 | 149 | private final class Task extends TimerTask 150 | { 151 | @Override 152 | public void run() 153 | { 154 | doTask(); 155 | } 156 | } 157 | 158 | 159 | private void doTask() 160 | { 161 | synchronized (this) 162 | { 163 | if (mInterval == 0 || mWebSocket.isOpen() == false) 164 | { 165 | mScheduled = false; 166 | 167 | // Not schedule a new task. 168 | return; 169 | } 170 | 171 | // Create a frame and send it to the server. 172 | mWebSocket.sendFrame(createFrame()); 173 | 174 | // Schedule a new task. 175 | mScheduled = schedule(mTimer, new Task(), mInterval); 176 | } 177 | } 178 | 179 | 180 | private WebSocketFrame createFrame() 181 | { 182 | // Prepare payload of a frame. 183 | byte[] payload = generatePayload(); 184 | 185 | // Let the subclass create a frame. 186 | return createFrame(payload); 187 | } 188 | 189 | 190 | private byte[] generatePayload() 191 | { 192 | if (mGenerator == null) 193 | { 194 | return null; 195 | } 196 | 197 | try 198 | { 199 | // Let the generator generate payload. 200 | return mGenerator.generate(); 201 | } 202 | catch (Throwable t) 203 | { 204 | // Empty payload. 205 | return null; 206 | } 207 | } 208 | 209 | 210 | private static boolean schedule(Timer timer, Task task, long interval) 211 | { 212 | try 213 | { 214 | // Schedule the task. 215 | timer.schedule(task, interval); 216 | 217 | // Successfully scheduled the task. 218 | return true; 219 | } 220 | catch (RuntimeException e) 221 | { 222 | // Failed to schedule the task. Probably, the exception is 223 | // an IllegalStateException which is raised due to one of 224 | // the following reasons (according to the Javadoc): 225 | // 226 | // (1) if task was already scheduled or cancelled, 227 | // (2) timer was cancelled, or 228 | // (3) timer thread terminated. 229 | // 230 | // Because a new task is created every time this method is 231 | // called and there is no code to call TimerTask.cancel(), 232 | // (1) cannot be a reason. 233 | // 234 | // In either case of (2) and (3), we don't have to retry to 235 | // schedule the task, because the timer that is expected to 236 | // host the task will stop or has stopped anyway. 237 | return false; 238 | } 239 | } 240 | 241 | 242 | protected abstract WebSocketFrame createFrame(byte[] payload); 243 | } 244 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/PingSender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | class PingSender extends PeriodicalFrameSender 20 | { 21 | private static final String TIMER_NAME = "PingSender"; 22 | 23 | 24 | public PingSender(WebSocket webSocket, PayloadGenerator generator) 25 | { 26 | super(webSocket, TIMER_NAME, generator); 27 | } 28 | 29 | 30 | @Override 31 | protected WebSocketFrame createFrame(byte[] payload) 32 | { 33 | return WebSocketFrame.createPingFrame(payload); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/PongSender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | class PongSender extends PeriodicalFrameSender 20 | { 21 | private static final String TIMER_NAME = "PongSender"; 22 | 23 | 24 | public PongSender(WebSocket webSocket, PayloadGenerator generator) 25 | { 26 | super(webSocket, TIMER_NAME, generator); 27 | } 28 | 29 | 30 | @Override 31 | protected WebSocketFrame createFrame(byte[] payload) 32 | { 33 | return WebSocketFrame.createPongFrame(payload); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/ProxyHandshaker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import java.io.EOFException; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.io.OutputStream; 23 | import java.net.Socket; 24 | import java.util.List; 25 | import java.util.Map; 26 | 27 | 28 | class ProxyHandshaker 29 | { 30 | private static final String RN = "\r\n"; 31 | private final String mHost; 32 | private final int mPort; 33 | private final ProxySettings mSettings; 34 | 35 | 36 | public ProxyHandshaker(String host, int port, ProxySettings settings) 37 | { 38 | mHost = host; 39 | mPort = port; 40 | mSettings = settings; 41 | } 42 | 43 | 44 | public void perform(Socket socket) throws IOException 45 | { 46 | // Send a CONNECT request to the proxy server. 47 | sendRequest(socket); 48 | 49 | // Receive a response. 50 | receiveResponse(socket); 51 | } 52 | 53 | 54 | private void sendRequest(Socket socket) throws IOException 55 | { 56 | // Build a CONNECT request. 57 | String request = buildRequest(); 58 | 59 | // Convert the request to a byte array. 60 | byte[] requestBytes = Misc.getBytesUTF8(request); 61 | 62 | // Get the stream to send data to the proxy server. 63 | OutputStream output = socket.getOutputStream(); 64 | 65 | // Send the request to the proxy server. 66 | output.write(requestBytes); 67 | output.flush(); 68 | } 69 | 70 | 71 | private String buildRequest() 72 | { 73 | String host = String.format("%s:%d", mHost, mPort); 74 | 75 | // CONNECT 76 | StringBuilder builder = new StringBuilder() 77 | .append("CONNECT ").append(host).append(" HTTP/1.1").append(RN) 78 | .append("Host: ").append(host).append(RN); 79 | 80 | 81 | // Additional headers 82 | addHeaders(builder); 83 | 84 | // Proxy-Authorization 85 | addProxyAuthorization(builder); 86 | 87 | // The entire request. 88 | return builder.append(RN).toString(); 89 | } 90 | 91 | 92 | private void addHeaders(StringBuilder builder) 93 | { 94 | // For each additional header. 95 | for (Map.Entry> header : mSettings.getHeaders().entrySet()) 96 | { 97 | // Header name. 98 | String name = header.getKey(); 99 | 100 | // For each header value. 101 | for (String value : header.getValue()) 102 | { 103 | if (value == null) 104 | { 105 | value = ""; 106 | } 107 | 108 | builder.append(name).append(": ").append(value).append(RN); 109 | } 110 | } 111 | } 112 | 113 | 114 | private void addProxyAuthorization(StringBuilder builder) 115 | { 116 | String id = mSettings.getId(); 117 | 118 | if (id == null || id.length() == 0) 119 | { 120 | return; 121 | } 122 | 123 | String password = mSettings.getPassword(); 124 | 125 | if (password == null) 126 | { 127 | password = ""; 128 | } 129 | 130 | // {id}:{password} 131 | String credentials = String.format("%s:%s", id, password); 132 | 133 | // The current implementation always uses Basic Authentication. 134 | builder 135 | .append("Proxy-Authorization: Basic ") 136 | .append(Base64.encode(credentials)) 137 | .append(RN); 138 | } 139 | 140 | 141 | private void receiveResponse(Socket socket) throws IOException 142 | { 143 | // Get the stream to read data from the proxy server. 144 | InputStream input = socket.getInputStream(); 145 | 146 | // Read the status line. 147 | readStatusLine(input); 148 | 149 | // Skip HTTP headers, including an empty line (= the separator 150 | // between the header part and the body part). 151 | skipHeaders(input); 152 | } 153 | 154 | 155 | private void readStatusLine(InputStream input) throws IOException 156 | { 157 | // Read the status line. 158 | String statusLine = Misc.readLine(input, "UTF-8"); 159 | 160 | // If the response from the proxy server does not contain a status line. 161 | if (statusLine == null || statusLine.length() == 0) 162 | { 163 | throw new IOException("The response from the proxy server does not contain a status line."); 164 | } 165 | 166 | // Expect "HTTP/1.1 200 Connection established" 167 | String[] elements = statusLine.split(" +", 3); 168 | 169 | if (elements.length < 2) 170 | { 171 | throw new IOException( 172 | "The status line in the response from the proxy server is badly formatted. " + 173 | "The status line is: " + statusLine); 174 | } 175 | 176 | // If the status code is not "200". 177 | if ("200".equals(elements[1]) == false) 178 | { 179 | throw new IOException( 180 | "The status code in the response from the proxy server is not '200 Connection established'. " + 181 | "The status line is: " + statusLine); 182 | } 183 | 184 | // OK. A connection was established. 185 | } 186 | 187 | 188 | private void skipHeaders(InputStream input) throws IOException 189 | { 190 | // The number of normal letters in a line. 191 | int count = 0; 192 | 193 | while (true) 194 | { 195 | // Read a byte from the stream. 196 | int ch = input.read(); 197 | 198 | // If the end of the stream was reached. 199 | if (ch == -1) 200 | { 201 | // Unexpected EOF. 202 | throw new EOFException("The end of the stream from the proxy server was reached unexpectedly."); 203 | } 204 | 205 | // If the end of the line was reached. 206 | if (ch == '\n') 207 | { 208 | // If there is no normal byte in the line. 209 | if (count == 0) 210 | { 211 | // An empty line (the separator) was found. 212 | return; 213 | } 214 | 215 | // Reset the counter and go to the next line. 216 | count = 0; 217 | continue; 218 | } 219 | 220 | // If the read byte is not a carriage return. 221 | if (ch != '\r') 222 | { 223 | // Increment the number of normal bytes on the line. 224 | ++count; 225 | continue; 226 | } 227 | 228 | // Read the next byte. 229 | ch = input.read(); 230 | 231 | // If the end of the stream was reached. 232 | if (ch == -1) 233 | { 234 | // Unexpected EOF. 235 | throw new EOFException("The end of the stream from the proxy server was reached unexpectedly after a carriage return."); 236 | } 237 | 238 | if (ch != '\n') 239 | { 240 | // Regard the last '\r' as a normal byte as well as the current 'ch'. 241 | count += 2; 242 | continue; 243 | } 244 | 245 | // '\r\n' was detected. 246 | 247 | // If there is no normal byte in the line. 248 | if (count == 0) 249 | { 250 | // An empty line (the separator) was found. 251 | return; 252 | } 253 | 254 | // Reset the counter and go to the next line. 255 | count = 0; 256 | } 257 | } 258 | 259 | 260 | /** 261 | * To be able to verify the hostname of the certificate received 262 | * if a connection is made to an https/wss endpoint, access to this 263 | * hostname is required. 264 | * 265 | * @return the hostname of the server the proxy is asked to connect to. 266 | */ 267 | String getProxiedHostname() 268 | { 269 | return mHost; 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/SNIHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import java.lang.reflect.Constructor; 20 | import java.lang.reflect.Method; 21 | import java.net.Socket; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import javax.net.ssl.SSLParameters; 25 | import javax.net.ssl.SSLSocket; 26 | 27 | 28 | class SNIHelper 29 | { 30 | private static Constructor sSNIHostNameConstructor; 31 | private static Method sSetServerNamesMethod; 32 | 33 | 34 | static 35 | { 36 | try 37 | { 38 | initialize(); 39 | } 40 | catch (Exception e) 41 | { 42 | e.printStackTrace(); 43 | } 44 | } 45 | 46 | 47 | private static void initialize() throws Exception 48 | { 49 | // Constructor which represents javax.net.ssl.SNIHostName(String). 50 | // The class is available since Java 1.8 / Android API Level 24 (Android 7.0) 51 | sSNIHostNameConstructor = Misc.getConstructor( 52 | "javax.net.ssl.SNIHostName", new Class[] { String.class }); 53 | 54 | // Method which represents javax.net.ssl.SSLParameters.setServerNames(List). 55 | // The method is available since Java 1.8 / Android API Level 24 (Android 7.0) 56 | sSetServerNamesMethod = Misc.getMethod( 57 | "javax.net.ssl.SSLParameters", "setServerNames", new Class[] { List.class }); 58 | } 59 | 60 | 61 | private static Object createSNIHostName(String hostname) 62 | { 63 | // return new SNIHostName(hostname); 64 | return Misc.newInstance(sSNIHostNameConstructor, hostname); 65 | } 66 | 67 | 68 | private static List createSNIHostNames(String[] hostnames) 69 | { 70 | List list = new ArrayList(hostnames.length); 71 | 72 | // Create a list of SNIHostName from the String array. 73 | for (String hostname : hostnames) 74 | { 75 | // Create a new SNIHostName instance and add it to the list. 76 | list.add(createSNIHostName(hostname)); 77 | } 78 | 79 | return list; 80 | } 81 | 82 | 83 | private static void setServerNames(SSLParameters parameters, String[] hostnames) 84 | { 85 | // Call parameters.setServerNames(List) method. 86 | Misc.invoke(sSetServerNamesMethod, parameters, createSNIHostNames(hostnames)); 87 | } 88 | 89 | 90 | static void setServerNames(Socket socket, String[] hostnames) 91 | { 92 | if ((socket instanceof SSLSocket) == false) 93 | { 94 | return; 95 | } 96 | 97 | if (hostnames == null) 98 | { 99 | return; 100 | } 101 | 102 | //Android versions below Nougat (Version 7/SDK 24) require setHostname() to be invoked on the socket object 103 | //before the connection is established in order to properly enable SNI. 104 | int androidSDKVersion = getAndroidSDKVersion(); 105 | if (androidSDKVersion > 0 && androidSDKVersion < 24) 106 | { 107 | try 108 | { 109 | Method method = socket.getClass().getMethod("setHostname", String.class); 110 | method.invoke(socket, hostnames[0]); 111 | } 112 | catch (Exception e) 113 | { 114 | System.err.println("SNI configuration failed: " + e.getMessage()); 115 | } 116 | return; 117 | } 118 | 119 | SSLParameters parameters = ((SSLSocket)socket).getSSLParameters(); 120 | if (parameters == null) 121 | { 122 | return; 123 | } 124 | 125 | // Call SSLParameters.setServerNames(List) method. 126 | setServerNames(parameters, hostnames); 127 | } 128 | 129 | public static int getAndroidSDKVersion() 130 | { 131 | try 132 | { 133 | return Class.forName("android.os.Build$VERSION").getField("SDK_INT").getInt(null); 134 | } 135 | catch (Exception ex) 136 | { 137 | try 138 | { 139 | return Integer.parseInt((String) Class.forName("android.os.Build$VERSION").getField("SDK").get(null)); 140 | } 141 | catch (Exception ex1) 142 | { 143 | return 0; 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/SocketFactorySettings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import javax.net.SocketFactory; 20 | import javax.net.ssl.SSLContext; 21 | import javax.net.ssl.SSLSocketFactory; 22 | 23 | 24 | class SocketFactorySettings 25 | { 26 | private SocketFactory mSocketFactory; 27 | private SSLSocketFactory mSSLSocketFactory; 28 | private SSLContext mSSLContext; 29 | 30 | public SocketFactorySettings() {} 31 | 32 | 33 | public SocketFactorySettings(SocketFactorySettings settings) 34 | { 35 | mSocketFactory = settings.mSocketFactory; 36 | mSSLSocketFactory = settings.mSSLSocketFactory; 37 | mSSLContext = settings.mSSLContext; 38 | } 39 | 40 | 41 | public SocketFactory getSocketFactory() 42 | { 43 | return mSocketFactory; 44 | } 45 | 46 | 47 | public void setSocketFactory(SocketFactory factory) 48 | { 49 | mSocketFactory = factory; 50 | } 51 | 52 | 53 | public SSLSocketFactory getSSLSocketFactory() 54 | { 55 | return mSSLSocketFactory; 56 | } 57 | 58 | 59 | public void setSSLSocketFactory(SSLSocketFactory factory) 60 | { 61 | mSSLSocketFactory = factory; 62 | } 63 | 64 | 65 | public SSLContext getSSLContext() 66 | { 67 | return mSSLContext; 68 | } 69 | 70 | 71 | public void setSSLContext(SSLContext context) 72 | { 73 | mSSLContext = context; 74 | } 75 | 76 | 77 | public SocketFactory selectSocketFactory(boolean secure) 78 | { 79 | if (secure) 80 | { 81 | if (mSSLContext != null) 82 | { 83 | return mSSLContext.getSocketFactory(); 84 | } 85 | 86 | if (mSSLSocketFactory != null) 87 | { 88 | return mSSLSocketFactory; 89 | } 90 | 91 | return SSLSocketFactory.getDefault(); 92 | } 93 | 94 | if (mSocketFactory != null) 95 | { 96 | return mSocketFactory; 97 | } 98 | 99 | return SocketFactory.getDefault(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/SocketInitiator.java: -------------------------------------------------------------------------------- 1 | package com.neovisionaries.ws.client; 2 | 3 | import java.io.IOException; 4 | import java.net.Inet4Address; 5 | import java.net.Inet6Address; 6 | import java.net.InetAddress; 7 | import java.net.InetSocketAddress; 8 | import java.net.Socket; 9 | import java.net.SocketAddress; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.concurrent.CountDownLatch; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | import javax.net.SocketFactory; 16 | 17 | 18 | /** 19 | * Lets multiple sockets race the given IP addresses until one has been 20 | * established. 21 | * 22 | * This follows RFC 6555 (Happy 23 | * Eyeballs). 24 | * 25 | * @author Lennart Grahl 26 | */ 27 | public class SocketInitiator { 28 | /** 29 | * A wait signal will be awaited by a {@link SocketRacer} before it 30 | * starts to connect. 31 | * 32 | * When a {@link SocketRacer} A is done, it will unblock the 33 | * following racer B by marking B's signal as done. 34 | */ 35 | private class Signal 36 | { 37 | private final CountDownLatch mLatch; 38 | private final int mMaxDelay; 39 | 40 | 41 | Signal(int maxDelay) 42 | { 43 | mLatch = new CountDownLatch(1); 44 | mMaxDelay = maxDelay; 45 | } 46 | 47 | 48 | boolean isDone() 49 | { 50 | return mLatch.getCount() == 0; 51 | } 52 | 53 | 54 | void await() throws InterruptedException 55 | { 56 | mLatch.await(mMaxDelay, TimeUnit.MILLISECONDS); 57 | } 58 | 59 | 60 | void done() 61 | { 62 | mLatch.countDown(); 63 | } 64 | } 65 | 66 | 67 | /** 68 | * This thread connects to a socket and notifies a {@link SocketFuture} 69 | * shared across all racer threads when it is done. A racer thread is done 70 | * when... 71 | * 72 | *
    73 | *
  • it has established a connection, or
  • 74 | *
  • when establishing a connection failed with an exception, or
  • 75 | *
  • another racer established a connection.
  • 76 | *
77 | */ 78 | private class SocketRacer extends Thread 79 | { 80 | private final SocketFuture mFuture; 81 | private final SocketFactory mSocketFactory; 82 | private final SocketAddress mSocketAddress; 83 | private String[] mServerNames; 84 | private final int mConnectTimeout; 85 | private final Signal mStartSignal; 86 | private final Signal mDoneSignal; 87 | 88 | 89 | SocketRacer( 90 | SocketFuture future, SocketFactory socketFactory, SocketAddress socketAddress, 91 | String[] serverNames, int connectTimeout, Signal startSignal, Signal doneSignal) 92 | { 93 | mFuture = future; 94 | mSocketFactory = socketFactory; 95 | mSocketAddress = socketAddress; 96 | mServerNames = serverNames; 97 | mConnectTimeout = connectTimeout; 98 | mStartSignal = startSignal; 99 | mDoneSignal = doneSignal; 100 | } 101 | 102 | 103 | public void run() { 104 | Socket socket = null; 105 | try 106 | { 107 | // Await start signal. 108 | if (mStartSignal != null) 109 | { 110 | mStartSignal.await(); 111 | } 112 | 113 | // Check if a socket has already been established. 114 | if (mFuture.hasSocket()) 115 | { 116 | return; 117 | } 118 | 119 | // Let the socket factory create a socket. 120 | socket = mSocketFactory.createSocket(); 121 | 122 | // Set up server names for SNI as necessary if possible. 123 | SNIHelper.setServerNames(socket, mServerNames); 124 | 125 | // Connect to the server (either a proxy or a WebSocket endpoint). 126 | socket.connect(mSocketAddress, mConnectTimeout); 127 | 128 | // Socket established. 129 | complete(socket); 130 | } 131 | catch (Exception e) 132 | { 133 | abort(e); 134 | 135 | if (socket != null) 136 | { 137 | try 138 | { 139 | socket.close(); 140 | } 141 | catch (IOException ioe) 142 | { 143 | // ignored 144 | } 145 | } 146 | } 147 | } 148 | 149 | 150 | private void complete(Socket socket) 151 | { 152 | synchronized (mFuture) 153 | { 154 | // Check if already completed or aborted. 155 | if (mDoneSignal.isDone()) { 156 | return; 157 | } 158 | 159 | // Socket established. 160 | mFuture.setSocket(this, socket); 161 | 162 | // Socket racer complete. 163 | mDoneSignal.done(); 164 | } 165 | } 166 | 167 | 168 | void abort(Exception exception) 169 | { 170 | synchronized (mFuture) 171 | { 172 | // Check if already completed or aborted. 173 | if (mDoneSignal.isDone()) 174 | { 175 | return; 176 | } 177 | 178 | // Socket not established. 179 | mFuture.setException(exception); 180 | 181 | // Socket racer complete. 182 | mDoneSignal.done(); 183 | } 184 | } 185 | } 186 | 187 | 188 | /** 189 | * The socket future is shared across all {@link SocketRacer} threads and 190 | * aggregates the results. A socket future is considered fulfilled when... 191 | * 192 | *
    193 | *
  • any racer thread has established a socket in which case all 194 | * other racers will be stopped, or
  • 195 | *
  • all racer threads returned with an exception, or
  • 196 | *
  • there was no racer thread (e.g. in case there is no network 197 | * interface).
  • 198 | *
199 | * 200 | * In the first case, the socket will be returned. In all other cases, an 201 | * exception will be thrown, indicating the failure type. 202 | */ 203 | private class SocketFuture 204 | { 205 | private CountDownLatch mLatch; 206 | private List mRacers; 207 | private Socket mSocket; 208 | private Exception mException; 209 | 210 | 211 | synchronized boolean hasSocket() 212 | { 213 | return mSocket != null; 214 | } 215 | 216 | 217 | synchronized void setSocket(SocketRacer current, Socket socket) 218 | { 219 | // Sanity check. 220 | if (mLatch == null || mRacers == null) 221 | { 222 | throw new IllegalStateException("Cannot set socket before awaiting!"); 223 | } 224 | 225 | // Set socket if not already set, otherwise close socket. 226 | if (mSocket == null) 227 | { 228 | mSocket = socket; 229 | 230 | // Stop all other racers. 231 | for (SocketRacer racer: mRacers) 232 | { 233 | // Skip instance that is setting the socket. 234 | if (racer == current) 235 | { 236 | continue; 237 | } 238 | racer.abort(new InterruptedException()); 239 | racer.interrupt(); 240 | } 241 | } 242 | else 243 | { 244 | try 245 | { 246 | socket.close(); 247 | } 248 | catch (IOException e) 249 | { 250 | // ignored 251 | } 252 | } 253 | 254 | // Racer complete. 255 | mLatch.countDown(); 256 | } 257 | 258 | 259 | synchronized void setException(Exception exception) 260 | { 261 | // Sanity check. 262 | if (mLatch == null || mRacers == null) 263 | { 264 | throw new IllegalStateException("Cannot set exception before awaiting!"); 265 | } 266 | 267 | // Set exception if not already set. 268 | if (mException == null) 269 | { 270 | mException = exception; 271 | } 272 | 273 | // Racer complete. 274 | mLatch.countDown(); 275 | } 276 | 277 | 278 | Socket await(List racers) throws Exception 279 | { 280 | // Store racers. 281 | mRacers = racers; 282 | 283 | // Create new latch. 284 | mLatch = new CountDownLatch(mRacers.size()); 285 | 286 | // Start each racer. 287 | for (SocketRacer racer: mRacers) 288 | { 289 | racer.start(); 290 | } 291 | 292 | // Wait until all racers complete. 293 | mLatch.await(); 294 | 295 | // Return the socket, if any, otherwise the first exception raised 296 | if (mSocket != null) 297 | { 298 | return mSocket; 299 | } 300 | else if (mException != null) 301 | { 302 | throw mException; 303 | } 304 | else 305 | { 306 | throw new WebSocketException(WebSocketError.SOCKET_CONNECT_ERROR, 307 | "No viable interface to connect"); 308 | } 309 | } 310 | } 311 | 312 | 313 | private final SocketFactory mSocketFactory; 314 | private final Address mAddress; 315 | private final int mConnectTimeout; 316 | private final String[] mServerNames; 317 | private final DualStackMode mMode; 318 | private final int mFallbackDelay; 319 | 320 | 321 | public SocketInitiator( 322 | SocketFactory socketFactory, Address address, int connectTimeout, String[] serverNames, 323 | DualStackMode mode, int fallbackDelay) 324 | { 325 | mSocketFactory = socketFactory; 326 | mAddress = address; 327 | mConnectTimeout = connectTimeout; 328 | mServerNames = serverNames; 329 | mMode = mode; 330 | mFallbackDelay = fallbackDelay; 331 | } 332 | 333 | 334 | public Socket establish(InetAddress[] addresses) throws Exception 335 | { 336 | // Create socket future. 337 | SocketFuture future = new SocketFuture(); 338 | 339 | // Create socket racer for each IP address. 340 | List racers = new ArrayList(addresses.length); 341 | int delay = 0; 342 | Signal startSignal = null; 343 | for (InetAddress address: addresses) 344 | { 345 | // Check if the mode requires us to skip this address. 346 | if (mMode == DualStackMode.IPV4_ONLY && !(address instanceof Inet4Address) 347 | || mMode == DualStackMode.IPV6_ONLY && !(address instanceof Inet6Address)) 348 | { 349 | continue; 350 | } 351 | 352 | // Increase the *happy eyeballs* delay (see RFC 6555, sec 5.5). 353 | delay += mFallbackDelay; 354 | 355 | // Create the *done* signal which acts as a *start* signal for the subsequent racer. 356 | Signal doneSignal = new Signal(delay); 357 | 358 | // Create racer to establish the socket. 359 | SocketAddress socketAddress = new InetSocketAddress(address, mAddress.getPort()); 360 | SocketRacer racer = new SocketRacer( 361 | future, mSocketFactory, socketAddress, mServerNames, mConnectTimeout, 362 | startSignal, doneSignal); 363 | racers.add(racer); 364 | 365 | // Replace *start* signal with this racer's *done* signal. 366 | startSignal = doneSignal; 367 | } 368 | 369 | // Wait until one of the sockets has been established, or all failed with an exception. 370 | return future.await(racers); 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/StateManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import static com.neovisionaries.ws.client.WebSocketState.CLOSING; 20 | import static com.neovisionaries.ws.client.WebSocketState.CREATED; 21 | 22 | 23 | class StateManager 24 | { 25 | enum CloseInitiator 26 | { 27 | NONE, 28 | SERVER, 29 | CLIENT 30 | } 31 | 32 | 33 | private WebSocketState mState; 34 | private CloseInitiator mCloseInitiator = CloseInitiator.NONE; 35 | 36 | 37 | public StateManager() 38 | { 39 | mState = CREATED; 40 | } 41 | 42 | 43 | public WebSocketState getState() 44 | { 45 | return mState; 46 | } 47 | 48 | 49 | public void setState(WebSocketState state) 50 | { 51 | mState = state; 52 | } 53 | 54 | 55 | public void changeToClosing(CloseInitiator closeInitiator) 56 | { 57 | mState = CLOSING; 58 | 59 | // Set the close initiator only when it has not been set yet. 60 | if (mCloseInitiator == CloseInitiator.NONE) 61 | { 62 | mCloseInitiator = closeInitiator; 63 | } 64 | } 65 | 66 | 67 | public boolean getClosedByServer() 68 | { 69 | return mCloseInitiator == CloseInitiator.SERVER; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/StatusLine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the License is distributed on an 12 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific 14 | * language governing permissions and limitations under the 15 | * License. 16 | */ 17 | package com.neovisionaries.ws.client; 18 | 19 | 20 | /** 21 | * HTTP status line returned from an HTTP server. 22 | * 23 | * @since 1.19 24 | */ 25 | public class StatusLine 26 | { 27 | /** 28 | * HTTP version. 29 | */ 30 | private final String mHttpVersion; 31 | 32 | 33 | /** 34 | * Status code. 35 | */ 36 | private final int mStatusCode; 37 | 38 | 39 | /** 40 | * Reason phrase. 41 | */ 42 | private final String mReasonPhrase; 43 | 44 | 45 | /** 46 | * String representation of this instance (= the raw status line). 47 | */ 48 | private final String mString; 49 | 50 | 51 | /** 52 | * Constructor with a raw status line. 53 | * 54 | * @param line 55 | * A status line. 56 | * 57 | * @throws NullPointerException 58 | * {@code line} is {@code null} 59 | * 60 | * @throws IllegalArgumentException 61 | * The number of elements in {@code line} is less than 2. 62 | * 63 | * @throws NumberFormatException 64 | * Failed to parse the second element in {@code line} 65 | * as an integer. 66 | */ 67 | StatusLine(String line) 68 | { 69 | // HTTP-Version Status-Code Reason-Phrase 70 | String[] elements = line.split(" +", 3); 71 | 72 | if (elements.length < 2) 73 | { 74 | throw new IllegalArgumentException(); 75 | } 76 | 77 | mHttpVersion = elements[0]; 78 | mStatusCode = Integer.parseInt(elements[1]); 79 | mReasonPhrase = (elements.length == 3) ? elements[2] : null; 80 | mString = line; 81 | } 82 | 83 | 84 | /** 85 | * Get the HTTP version. 86 | * 87 | * @return 88 | * The HTTP version. For example, {@code "HTTP/1.1"}. 89 | */ 90 | public String getHttpVersion() 91 | { 92 | return mHttpVersion; 93 | } 94 | 95 | 96 | /** 97 | * Get the status code. 98 | * 99 | * @return 100 | * The status code. For example, {@code 404}. 101 | */ 102 | public int getStatusCode() 103 | { 104 | return mStatusCode; 105 | } 106 | 107 | 108 | /** 109 | * Get the reason phrase. 110 | * 111 | * @return 112 | * The reason phrase. For example, {@code "Not Found"}. 113 | */ 114 | public String getReasonPhrase() 115 | { 116 | return mReasonPhrase; 117 | } 118 | 119 | 120 | /** 121 | * Get the string representation of this instance, which is 122 | * equal to the raw status line. 123 | * 124 | * @return 125 | * The raw status line. For example, 126 | * {@code "HTTP/1.1 404 Not Found"}. 127 | */ 128 | @Override 129 | public String toString() 130 | { 131 | return mString; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/ThreadType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | /** 20 | * Types of threads which are created internally in the implementation. 21 | * 22 | * @since 2.0 23 | */ 24 | public enum ThreadType 25 | { 26 | /** 27 | * A thread which reads WebSocket frames from the server 28 | * (ReadingThread). 29 | */ 30 | READING_THREAD, 31 | 32 | 33 | /** 34 | * A thread which sends WebSocket frames to the server 35 | * (WritingThread). 36 | */ 37 | WRITING_THREAD, 38 | 39 | 40 | /** 41 | * A thread which calls {@link WebSocket#connect()} asynchronously 42 | * (ConnectThread). 43 | */ 44 | CONNECT_THREAD, 45 | 46 | 47 | /** 48 | * A thread which does finalization of a {@link WebSocket} instance. 49 | * (FinishThread). 50 | */ 51 | FINISH_THREAD, 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/Token.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | class Token 20 | { 21 | /** 22 | * Check if the given string conforms to the rules described 23 | * in "2.2 Basic Rules" of RFC 2616. 26 | */ 27 | public static boolean isValid(String token) 28 | { 29 | if (token == null || token.length() == 0) 30 | { 31 | return false; 32 | } 33 | 34 | int len = token.length(); 35 | 36 | for (int i = 0; i < len; ++i) 37 | { 38 | if (isSeparator(token.charAt(i))) 39 | { 40 | return false; 41 | } 42 | } 43 | 44 | return true; 45 | } 46 | 47 | 48 | public static boolean isSeparator(char ch) 49 | { 50 | switch (ch) 51 | { 52 | case '(': 53 | case ')': 54 | case '<': 55 | case '>': 56 | case '@': 57 | case ',': 58 | case ';': 59 | case ':': 60 | case '\\': 61 | case '"': 62 | case '/': 63 | case '[': 64 | case ']': 65 | case '?': 66 | case '=': 67 | case '{': 68 | case '}': 69 | case ' ': 70 | case '\t': 71 | return true; 72 | 73 | default: 74 | return false; 75 | } 76 | } 77 | 78 | 79 | public static String unquote(String text) 80 | { 81 | if (text == null) 82 | { 83 | return null; 84 | } 85 | 86 | int len = text.length(); 87 | 88 | if (len < 2 || text.charAt(0) != '"' || text.charAt(len-1) != '"') 89 | { 90 | return text; 91 | } 92 | 93 | text = text.substring(1, len-1); 94 | 95 | return unescape(text); 96 | } 97 | 98 | 99 | public static String unescape(String text) 100 | { 101 | if (text == null) 102 | { 103 | return null; 104 | } 105 | 106 | if (text.indexOf('\\') < 0) 107 | { 108 | return text; 109 | } 110 | 111 | int len = text.length(); 112 | boolean escaped = false; 113 | StringBuilder builder = new StringBuilder(); 114 | 115 | 116 | for (int i = 0; i < len; ++i) 117 | { 118 | char ch = text.charAt(i); 119 | 120 | if (ch == '\\' && escaped == false) 121 | { 122 | escaped = true; 123 | continue; 124 | } 125 | 126 | escaped = false; 127 | builder.append(ch); 128 | } 129 | 130 | return builder.toString(); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/WebSocketAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2017 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | 23 | /** 24 | * An empty implementation of {@link WebSocketListener} interface. 25 | * 26 | * @see WebSocketListener 27 | */ 28 | public class WebSocketAdapter implements WebSocketListener 29 | { 30 | @Override 31 | public void onStateChanged(WebSocket websocket, WebSocketState newState) throws Exception 32 | { 33 | } 34 | 35 | 36 | @Override 37 | public void onConnected(WebSocket websocket, Map> headers) throws Exception 38 | { 39 | } 40 | 41 | 42 | @Override 43 | public void onConnectError(WebSocket websocket, WebSocketException exception) throws Exception 44 | { 45 | } 46 | 47 | 48 | @Override 49 | public void onDisconnected(WebSocket websocket, 50 | WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame, 51 | boolean closedByServer) throws Exception 52 | { 53 | } 54 | 55 | 56 | @Override 57 | public void onFrame(WebSocket websocket, WebSocketFrame frame) throws Exception 58 | { 59 | } 60 | 61 | 62 | @Override 63 | public void onContinuationFrame(WebSocket websocket, WebSocketFrame frame) throws Exception 64 | { 65 | } 66 | 67 | 68 | @Override 69 | public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception 70 | { 71 | } 72 | 73 | 74 | @Override 75 | public void onBinaryFrame(WebSocket websocket, WebSocketFrame frame) throws Exception 76 | { 77 | } 78 | 79 | 80 | @Override 81 | public void onCloseFrame(WebSocket websocket, WebSocketFrame frame) throws Exception 82 | { 83 | } 84 | 85 | 86 | @Override 87 | public void onPingFrame(WebSocket websocket, WebSocketFrame frame) throws Exception 88 | { 89 | } 90 | 91 | 92 | @Override 93 | public void onPongFrame(WebSocket websocket, WebSocketFrame frame) throws Exception 94 | { 95 | } 96 | 97 | 98 | @Override 99 | public void onTextMessage(WebSocket websocket, String text) throws Exception 100 | { 101 | } 102 | 103 | 104 | @Override 105 | public void onTextMessage(WebSocket websocket, byte[] data) throws Exception 106 | { 107 | } 108 | 109 | 110 | @Override 111 | public void onBinaryMessage(WebSocket websocket, byte[] binary) throws Exception 112 | { 113 | } 114 | 115 | 116 | @Override 117 | public void onSendingFrame(WebSocket websocket, WebSocketFrame frame) throws Exception 118 | { 119 | } 120 | 121 | 122 | @Override 123 | public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception 124 | { 125 | } 126 | 127 | 128 | @Override 129 | public void onFrameUnsent(WebSocket websocket, WebSocketFrame frame) throws Exception 130 | { 131 | } 132 | 133 | 134 | @Override 135 | public void onError(WebSocket websocket, WebSocketException cause) throws Exception 136 | { 137 | } 138 | 139 | 140 | @Override 141 | public void onFrameError(WebSocket websocket, WebSocketException cause, WebSocketFrame frame) throws Exception 142 | { 143 | } 144 | 145 | 146 | @Override 147 | public void onMessageError(WebSocket websocket, WebSocketException cause, List frames) throws Exception 148 | { 149 | } 150 | 151 | 152 | @Override 153 | public void onMessageDecompressionError(WebSocket websocket, WebSocketException cause, byte[] compressed) throws Exception 154 | { 155 | } 156 | 157 | 158 | @Override 159 | public void onTextMessageError(WebSocket websocket, WebSocketException cause, byte[] data) throws Exception 160 | { 161 | } 162 | 163 | 164 | @Override 165 | public void onSendError(WebSocket websocket, WebSocketException cause, WebSocketFrame frame) throws Exception 166 | { 167 | } 168 | 169 | 170 | @Override 171 | public void onUnexpectedError(WebSocket websocket, WebSocketException cause) throws Exception 172 | { 173 | } 174 | 175 | 176 | @Override 177 | public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception 178 | { 179 | } 180 | 181 | 182 | @Override 183 | public void onSendingHandshake(WebSocket websocket, String requestLine, List headers) throws Exception 184 | { 185 | } 186 | 187 | 188 | @Override 189 | public void onThreadCreated(WebSocket websocket, ThreadType threadType, Thread thread) throws Exception 190 | { 191 | } 192 | 193 | 194 | @Override 195 | public void onThreadStarted(WebSocket websocket, ThreadType threadType, Thread thread) throws Exception 196 | { 197 | } 198 | 199 | 200 | @Override 201 | public void onThreadStopping(WebSocket websocket, ThreadType threadType, Thread thread) throws Exception 202 | { 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/WebSocketCloseCode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | /** 20 | * Close code. 21 | * 22 | * @see RFC 6455, 7.4.1. Defined Status Codes 24 | */ 25 | public class WebSocketCloseCode 26 | { 27 | /** 28 | * 1000; 29 | * 30 | * 1000 indicates a normal closure, meaning that the purpose for 31 | * which the connection was established has been fulfilled. 32 | * 33 | */ 34 | public static final int NORMAL = 1000; 35 | 36 | 37 | /** 38 | * 1001; 39 | * 40 | * 1001 indicates that an endpoint is "going away", such as a server 41 | * going down or a browser having navigated away from a page. 42 | * 43 | */ 44 | public static final int AWAY = 1001; 45 | 46 | 47 | /** 48 | * 1002; 49 | * 50 | * 1002 indicates that an endpoint is terminating the connection due 51 | * to a protocol error. 52 | * 53 | */ 54 | public static final int UNCONFORMED = 1002; 55 | 56 | 57 | /** 58 | * 1003; 59 | * 60 | * 1003 indicates that an endpoint is terminating the connection 61 | * because it has received a type of data it cannot accept 62 | * (e.g., an endpoint that understands only text data MAY 63 | * send this if it receives a binary message). 64 | * 65 | */ 66 | public static final int UNACCEPTABLE = 1003; 67 | 68 | 69 | /** 70 | * 1005; 71 | * 72 | * 1005 is a reserved value and MUST NOT be set as a status code in a 73 | * Close control frame by an endpoint. It is designated for use in 74 | * applications expecting a status code to indicate that no status 75 | * code was actually present. 76 | * 77 | */ 78 | public static final int NONE = 1005; 79 | 80 | 81 | /** 82 | * 1006; 83 | * 84 | * 1006 is a reserved value and MUST NOT be set as a status code in a 85 | * Close control frame by an endpoint. It is designated for use in 86 | * applications expecting a status code to indicate that the 87 | * connection was closed abnormally, e.g., without sending or 88 | * receiving a Close control frame. 89 | * 90 | */ 91 | public static final int ABNORMAL = 1006; 92 | 93 | 94 | /** 95 | * 1007; 96 | * 97 | * 1007 indicates that an endpoint is terminating the connection 98 | * because it has received data within a message that was not 99 | * consistent with the type of the message (e.g., non-UTF-8 100 | * [RFC3629] data 101 | * within a text message). 102 | * 103 | */ 104 | public static final int INCONSISTENT = 1007; 105 | 106 | 107 | /** 108 | * 1008; 109 | * 110 | * 1008 indicates that an endpoint is terminating the connection 111 | * because it has received a message that violates its policy. 112 | * This is a generic status code that can be returned when there 113 | * is no other more suitable status code (e.g., 1003 or 1009) 114 | * or if there is a need to hide specific details about the policy. 115 | * 116 | */ 117 | public static final int VIOLATED = 1008; 118 | 119 | 120 | /** 121 | * 1009; 122 | * 123 | * 1009 indicates that an endpoint is terminating the connection 124 | * because it has received a message that is too big for it to 125 | * process. 126 | * 127 | */ 128 | public static final int OVERSIZE = 1009; 129 | 130 | 131 | /** 132 | * 1010; 133 | * 134 | * 1010 indicates that an endpoint (client) is terminating the 135 | * connection because it has expected the server to negotiate 136 | * one or more extension, but the server didn't return them in 137 | * the response message of the WebSocket handshake. The 138 | * list of extensions that are needed SHOULD appear in the 139 | * /reason/ part of the Close frame. Note that this status 140 | * code is not used by the server, because it can fail the 141 | * WebSocket handshake instead. 142 | * 143 | */ 144 | public static final int UNEXTENDED = 1010; 145 | 146 | 147 | /** 148 | * 1011; 149 | * 150 | * 1011 indicates that a server is terminating the connection because 151 | * it encountered an unexpected condition that prevented it from 152 | * fulfilling the request. 153 | * 154 | */ 155 | public static final int UNEXPECTED = 1011; 156 | 157 | 158 | /** 159 | * 1015; 160 | * 161 | * 1015 is a reserved value and MUST NOT be set as a status code in a 162 | * Close control frame by an endpoint. It is designated for use in 163 | * applications expecting a status code to indicate that the 164 | * connection was closed due to a failure to perform a TLS handshake 165 | * (e.g., the server certificate can't be verified). 166 | * 167 | */ 168 | public static final int INSECURE = 1015; 169 | 170 | 171 | private WebSocketCloseCode() 172 | { 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/WebSocketException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | /** 20 | * WebSocket exception. 21 | */ 22 | public class WebSocketException extends Exception 23 | { 24 | private static final long serialVersionUID = 1L; 25 | private final WebSocketError mError; 26 | 27 | 28 | public WebSocketException(WebSocketError error) 29 | { 30 | mError = error; 31 | } 32 | 33 | 34 | public WebSocketException(WebSocketError error, String message) 35 | { 36 | super(message); 37 | 38 | mError = error; 39 | } 40 | 41 | 42 | public WebSocketException(WebSocketError error, Throwable cause) 43 | { 44 | super(cause); 45 | 46 | mError = error; 47 | } 48 | 49 | 50 | public WebSocketException(WebSocketError error, String message, Throwable cause) 51 | { 52 | super(message, cause); 53 | 54 | mError = error; 55 | } 56 | 57 | 58 | public WebSocketError getError() 59 | { 60 | return mError; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/WebSocketExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import java.util.LinkedHashMap; 20 | import java.util.Map; 21 | 22 | 23 | /** 24 | * A class to hold the name and the parameters of 25 | * a WebSocket extension. 26 | */ 27 | public class WebSocketExtension 28 | { 29 | /** 30 | * The name of permessage-deflate extension that is 31 | * defined in 7. The "permessage-deflate" Extension in RFC 7692. 34 | * 35 | * @since 1.17 36 | */ 37 | public static final String PERMESSAGE_DEFLATE = "permessage-deflate"; 38 | 39 | 40 | private final String mName; 41 | private final Map mParameters; 42 | 43 | 44 | /** 45 | * Constructor with an extension name. 46 | * 47 | * @param name 48 | * The extension name. 49 | * 50 | * @throws IllegalArgumentException 51 | * The given name is not a valid token. 52 | */ 53 | public WebSocketExtension(String name) 54 | { 55 | // Check the validity of the name. 56 | if (Token.isValid(name) == false) 57 | { 58 | // The name is not a valid token. 59 | throw new IllegalArgumentException("'name' is not a valid token."); 60 | } 61 | 62 | mName = name; 63 | mParameters = new LinkedHashMap(); 64 | } 65 | 66 | 67 | /** 68 | * Copy constructor. 69 | * 70 | * @param source 71 | * A source extension. Must not be {@code null}. 72 | * 73 | * @throws IllegalArgumentException 74 | * The given argument is {@code null}. 75 | * 76 | * @since 1.6 77 | */ 78 | public WebSocketExtension(WebSocketExtension source) 79 | { 80 | if (source == null) 81 | { 82 | // If the given instance is null. 83 | throw new IllegalArgumentException("'source' is null."); 84 | } 85 | 86 | mName = source.getName(); 87 | mParameters = new LinkedHashMap(source.getParameters()); 88 | } 89 | 90 | 91 | /** 92 | * Get the extension name. 93 | * 94 | * @return 95 | * The extension name. 96 | */ 97 | public String getName() 98 | { 99 | return mName; 100 | } 101 | 102 | 103 | /** 104 | * Get the parameters. 105 | * 106 | * @return 107 | * The parameters. 108 | */ 109 | public Map getParameters() 110 | { 111 | return mParameters; 112 | } 113 | 114 | 115 | /** 116 | * Check if the parameter identified by the key is contained. 117 | * 118 | * @param key 119 | * The name of the parameter. 120 | * 121 | * @return 122 | * {@code true} if the parameter is contained. 123 | */ 124 | public boolean containsParameter(String key) 125 | { 126 | return mParameters.containsKey(key); 127 | } 128 | 129 | 130 | /** 131 | * Get the value of the specified parameter. 132 | * 133 | * @param key 134 | * The name of the parameter. 135 | * 136 | * @return 137 | * The value of the parameter. {@code null} may be returned. 138 | */ 139 | public String getParameter(String key) 140 | { 141 | return mParameters.get(key); 142 | } 143 | 144 | 145 | /** 146 | * Set a value to the specified parameter. 147 | * 148 | * @param key 149 | * The name of the parameter. 150 | * 151 | * @param value 152 | * The value of the parameter. If not {@code null}, it must be 153 | * a valid token. Note that RFC 6455 says "When using the quoted-string syntax 155 | * variant, the value after quoted-string unescaping MUST 156 | * conform to the 'token' ABNF." 157 | * 158 | * @return 159 | * {@code this} object. 160 | * 161 | * @throws IllegalArgumentException 162 | *
    163 | *
  • The key is not a valid token. 164 | *
  • The value is not {@code null} and it is not a valid token. 165 | *
166 | */ 167 | public WebSocketExtension setParameter(String key, String value) 168 | { 169 | // Check the validity of the key. 170 | if (Token.isValid(key) == false) 171 | { 172 | // The key is not a valid token. 173 | throw new IllegalArgumentException("'key' is not a valid token."); 174 | } 175 | 176 | // If the value is not null. 177 | if (value != null) 178 | { 179 | // Check the validity of the value. 180 | if (Token.isValid(value) == false) 181 | { 182 | // The value is not a valid token. 183 | throw new IllegalArgumentException("'value' is not a valid token."); 184 | } 185 | } 186 | 187 | mParameters.put(key, value); 188 | 189 | return this; 190 | } 191 | 192 | 193 | /** 194 | * Stringify this object into the format "{name}[; {key}[={value}]]*". 195 | */ 196 | @Override 197 | public String toString() 198 | { 199 | StringBuilder builder = new StringBuilder(mName); 200 | 201 | for (Map.Entry entry : mParameters.entrySet()) 202 | { 203 | // "; {key}" 204 | builder.append("; ").append(entry.getKey()); 205 | 206 | String value = entry.getValue(); 207 | 208 | if (value != null && value.length() != 0) 209 | { 210 | // "={value}" 211 | builder.append("=").append(value); 212 | } 213 | } 214 | 215 | return builder.toString(); 216 | } 217 | 218 | 219 | /** 220 | * Validate this instance. This method is expected to be overridden. 221 | */ 222 | void validate() throws WebSocketException 223 | { 224 | } 225 | 226 | 227 | /** 228 | * Parse a string as a {@link WebSocketExtension}. The input string 229 | * should comply with the format described in 9.1. Negotiating 231 | * Extensions in RFC 6455. 233 | * 234 | * @param string 235 | * A string that represents a WebSocket extension. 236 | * 237 | * @return 238 | * A new {@link WebSocketExtension} instance that represents 239 | * the given string. If the input string does not comply with 240 | * RFC 6455, {@code null} is returned. 241 | */ 242 | public static WebSocketExtension parse(String string) 243 | { 244 | if (string == null) 245 | { 246 | return null; 247 | } 248 | 249 | // Split the string by semi-colons. 250 | String[] elements = string.trim().split("\\s*;\\s*"); 251 | 252 | if (elements.length == 0) 253 | { 254 | // Even an extension name is not included. 255 | return null; 256 | } 257 | 258 | // The first element is the extension name. 259 | String name = elements[0]; 260 | 261 | if (Token.isValid(name) == false) 262 | { 263 | // The extension name is not a valid token. 264 | return null; 265 | } 266 | 267 | // Create an instance for the extension name. 268 | WebSocketExtension extension = createInstance(name); 269 | 270 | // For each "{key}[={value}]". 271 | for (int i = 1; i < elements.length; ++i) 272 | { 273 | // Split by '=' to get the key and the value. 274 | String[] pair = elements[i].split("\\s*=\\s*", 2); 275 | 276 | // If {key} is not contained. 277 | if (pair.length == 0 || pair[0].length() == 0) 278 | { 279 | // Ignore. 280 | continue; 281 | } 282 | 283 | // The name of the parameter. 284 | String key = pair[0]; 285 | 286 | if (Token.isValid(key) == false) 287 | { 288 | // The parameter name is not a valid token. 289 | // Ignore this parameter. 290 | continue; 291 | } 292 | 293 | // The value of the parameter. 294 | String value = extractValue(pair); 295 | 296 | if (value != null) 297 | { 298 | if (Token.isValid(value) == false) 299 | { 300 | // The parameter value is not a valid token. 301 | // Ignore this parameter. 302 | continue; 303 | } 304 | } 305 | 306 | // Add the pair of the key and the value. 307 | extension.setParameter(key, value); 308 | } 309 | 310 | return extension; 311 | } 312 | 313 | 314 | private static String extractValue(String[] pair) 315 | { 316 | if (pair.length != 2) 317 | { 318 | return null; 319 | } 320 | 321 | return Token.unquote(pair[1]); 322 | } 323 | 324 | 325 | private static WebSocketExtension createInstance(String name) 326 | { 327 | if (PERMESSAGE_DEFLATE.equals(name)) 328 | { 329 | return new PerMessageDeflateExtension(name); 330 | } 331 | 332 | return new WebSocketExtension(name); 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/WebSocketInputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import java.io.FilterInputStream; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | 23 | 24 | class WebSocketInputStream extends FilterInputStream 25 | { 26 | public WebSocketInputStream(InputStream in) 27 | { 28 | super(in); 29 | } 30 | 31 | 32 | public String readLine() throws IOException 33 | { 34 | return Misc.readLine(this, "UTF-8"); 35 | } 36 | 37 | 38 | public WebSocketFrame readFrame() throws IOException, WebSocketException 39 | { 40 | // Buffer. 41 | byte[] buffer = new byte[8]; 42 | 43 | try 44 | { 45 | // Read the first two bytes. 46 | readBytes(buffer, 2); 47 | } 48 | catch (InsufficientDataException e) 49 | { 50 | if (e.getReadByteCount() == 0) 51 | { 52 | // The connection has been closed without receiving a close frame. 53 | // Strictly speaking, this is a violation against RFC 6455. 54 | throw new NoMoreFrameException(); 55 | } 56 | else 57 | { 58 | // Re-throw the exception. 59 | throw e; 60 | } 61 | } 62 | 63 | // FIN 64 | boolean fin = ((buffer[0] & 0x80) != 0); 65 | 66 | // RSV1, RSV2, RSV3 67 | boolean rsv1 = ((buffer[0] & 0x40) != 0); 68 | boolean rsv2 = ((buffer[0] & 0x20) != 0); 69 | boolean rsv3 = ((buffer[0] & 0x10) != 0); 70 | 71 | // Opcode 72 | int opcode = (buffer[0] & 0x0F); 73 | 74 | // Mask flag. This should never be true because the specification 75 | // (RFC 6455, 5. Data Framing, 5.1. Overview) says as follows: 76 | // 77 | // A server MUST NOT mask any frames that it sends to the client. 78 | // 79 | boolean mask = ((buffer[1] & 0x80) != 0); 80 | 81 | // The payload length. It is expressed in 7 bits. 82 | long payloadLength = buffer[1] & 0x7F; 83 | 84 | if (payloadLength == 126) 85 | { 86 | // Read the extended payload length. 87 | // It is expressed in 2 bytes in network byte order. 88 | readBytes(buffer, 2); 89 | 90 | // Interpret the bytes as a number. 91 | payloadLength = (((buffer[0] & 0xFF) << 8) | 92 | ((buffer[1] & 0xFF) )); 93 | } 94 | else if (payloadLength == 127) 95 | { 96 | // Read the extended payload length. 97 | // It is expressed in 8 bytes in network byte order. 98 | readBytes(buffer, 8); 99 | 100 | // From RFC 6455, p29. 101 | // 102 | // the most significant bit MUST be 0 103 | // 104 | if ((buffer[0] & 0x80) != 0) 105 | { 106 | // The payload length in a frame is invalid. 107 | throw new WebSocketException( 108 | WebSocketError.INVALID_PAYLOAD_LENGTH, 109 | "The payload length of a frame is invalid."); 110 | } 111 | 112 | // Interpret the bytes as a number. 113 | payloadLength = (((buffer[0] & 0xFF) << 56) | 114 | ((buffer[1] & 0xFF) << 48) | 115 | ((buffer[2] & 0xFF) << 40) | 116 | ((buffer[3] & 0xFF) << 32) | 117 | ((buffer[4] & 0xFF) << 24) | 118 | ((buffer[5] & 0xFF) << 16) | 119 | ((buffer[6] & 0xFF) << 8) | 120 | ((buffer[7] & 0xFF) )); 121 | } 122 | 123 | // Masking key 124 | byte[] maskingKey = null; 125 | 126 | if (mask) 127 | { 128 | // Read the masking key. (This should never happen.) 129 | maskingKey = new byte[4]; 130 | readBytes(maskingKey, 4); 131 | } 132 | 133 | if (Integer.MAX_VALUE < payloadLength) 134 | { 135 | // In Java, the maximum array size is Integer.MAX_VALUE. 136 | // Skip the payload and raise an exception. 137 | skipQuietly(payloadLength); 138 | throw new WebSocketException( 139 | WebSocketError.TOO_LONG_PAYLOAD, 140 | "The payload length of a frame exceeds the maximum array size in Java."); 141 | } 142 | 143 | // Read the payload if the payload length is not 0. 144 | byte[] payload = readPayload(payloadLength, mask, maskingKey); 145 | 146 | // Create a WebSocketFrame instance that represents a frame. 147 | return new WebSocketFrame() 148 | .setFin(fin) 149 | .setRsv1(rsv1) 150 | .setRsv2(rsv2) 151 | .setRsv3(rsv3) 152 | .setOpcode(opcode) 153 | .setMask(mask) 154 | .setPayload(payload); 155 | } 156 | 157 | 158 | void readBytes(byte[] buffer, int length) throws IOException, WebSocketException 159 | { 160 | // Read 161 | int total = 0; 162 | 163 | while (total < length) 164 | { 165 | int count = read(buffer, total, length - total); 166 | 167 | if (count <= 0) 168 | { 169 | // The end of the stream has been reached unexpectedly. 170 | throw new InsufficientDataException(length, total); 171 | } 172 | 173 | total += count; 174 | } 175 | } 176 | 177 | 178 | private void skipQuietly(long length) 179 | { 180 | try 181 | { 182 | skip(length); 183 | } 184 | catch (IOException e) 185 | { 186 | } 187 | } 188 | 189 | 190 | private byte[] readPayload(long payloadLength, boolean mask, byte[] maskingKey) throws IOException, WebSocketException 191 | { 192 | if (payloadLength == 0) 193 | { 194 | return null; 195 | } 196 | 197 | byte[] payload; 198 | 199 | try 200 | { 201 | // Allocate a memory area to hold the content of the payload. 202 | payload = new byte[(int)payloadLength]; 203 | } 204 | catch (OutOfMemoryError e) 205 | { 206 | // OutOfMemoryError occurred during a trial to allocate a memory area 207 | // for a frame's payload. Skip the payload and raise an exception. 208 | skipQuietly(payloadLength); 209 | throw new WebSocketException( 210 | WebSocketError.INSUFFICIENT_MEMORY_FOR_PAYLOAD, 211 | "OutOfMemoryError occurred during a trial to allocate a memory area for a frame's payload: " + e.getMessage(), e); 212 | } 213 | 214 | // Read the payload. 215 | readBytes(payload, payload.length); 216 | 217 | // If masked. 218 | if (mask) 219 | { 220 | // Unmasked the payload. 221 | WebSocketFrame.mask(maskingKey, payload); 222 | } 223 | 224 | return payload; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/WebSocketOpcode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | /** 20 | * Opcode. 21 | * 22 | * @see RFC 6455, 5.2. Base Framing Protocol 24 | */ 25 | public class WebSocketOpcode 26 | { 27 | /** 28 | * Opcode for "frame continuation" (0x0). 29 | */ 30 | public static final int CONTINUATION = 0x0; 31 | 32 | 33 | /** 34 | * Opcode for "text frame" (0x1). 35 | */ 36 | public static final int TEXT = 0x1; 37 | 38 | 39 | /** 40 | * Opcode for "binary frame" (0x2). 41 | */ 42 | public static final int BINARY = 0x2; 43 | 44 | 45 | /** 46 | * Opcode for "connection close" (0x8). 47 | */ 48 | public static final int CLOSE = 0x8; 49 | 50 | 51 | /** 52 | * Opcode for "ping" (0x9). 53 | */ 54 | public static final int PING = 0x9; 55 | 56 | 57 | /** 58 | * Opcode for "pong" (0xA). 59 | */ 60 | public static final int PONG = 0xA; 61 | 62 | 63 | private WebSocketOpcode() 64 | { 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/WebSocketOutputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import java.io.BufferedOutputStream; 20 | import java.io.IOException; 21 | import java.io.OutputStream; 22 | 23 | 24 | class WebSocketOutputStream extends BufferedOutputStream 25 | { 26 | public WebSocketOutputStream(OutputStream out) 27 | { 28 | super(out); 29 | } 30 | 31 | 32 | public void write(String string) throws IOException 33 | { 34 | // Convert the string into a byte array. 35 | byte[] bytes = Misc.getBytesUTF8(string); 36 | 37 | write(bytes); 38 | } 39 | 40 | 41 | public void write(WebSocketFrame frame) throws IOException 42 | { 43 | writeFrame0(frame); 44 | writeFrame1(frame); 45 | writeFrameExtendedPayloadLength(frame); 46 | 47 | // Generate a random masking key. 48 | byte[] maskingKey = Misc.nextBytes(4); 49 | 50 | // Write the masking key. 51 | write(maskingKey); 52 | 53 | // Write the payload. 54 | writeFramePayload(frame, maskingKey); 55 | } 56 | 57 | 58 | private void writeFrame0(WebSocketFrame frame) throws IOException 59 | { 60 | int b = (frame.getFin() ? 0x80 : 0x00) 61 | | (frame.getRsv1() ? 0x40 : 0x00) 62 | | (frame.getRsv2() ? 0x20 : 0x00) 63 | | (frame.getRsv3() ? 0x10 : 0x00) 64 | | (frame.getOpcode() & 0x0F); 65 | 66 | write(b); 67 | } 68 | 69 | 70 | private void writeFrame1(WebSocketFrame frame) throws IOException 71 | { 72 | // Frames sent from a client are always masked. 73 | int b = 0x80; 74 | 75 | int len = frame.getPayloadLength(); 76 | 77 | if (len <= 125) 78 | { 79 | b |= len; 80 | } 81 | else if (len <= 65535) 82 | { 83 | b |= 126; 84 | } 85 | else 86 | { 87 | b |= 127; 88 | } 89 | 90 | write(b); 91 | } 92 | 93 | 94 | private void writeFrameExtendedPayloadLength(WebSocketFrame frame) throws IOException 95 | { 96 | int len = frame.getPayloadLength(); 97 | byte buf[]; 98 | 99 | if (len <= 125) 100 | { 101 | return; 102 | } 103 | 104 | if (len <= 65535) 105 | { 106 | buf = new byte[2]; 107 | // 2-byte in network byte order. 108 | buf[1] = (byte) (len & 0xFF); 109 | buf[0] = (byte) ((len >> 8) & 0xFF); 110 | } else { 111 | buf = new byte[8]; 112 | for (int i = 7; i >= 0; i--) { 113 | buf[i] = (byte) (len & 0xFF); 114 | len >>>= 8; 115 | } 116 | } 117 | write(buf); 118 | } 119 | 120 | 121 | private void writeFramePayload(WebSocketFrame frame, byte[] maskingKey) throws IOException 122 | { 123 | byte[] payload = frame.getPayload(); 124 | 125 | if (payload == null) 126 | { 127 | return; 128 | } 129 | 130 | byte[] masked = new byte[payload.length]; 131 | 132 | for (int i = 0; i < payload.length; ++i) 133 | { 134 | // Mask 135 | masked[i] = (byte)((payload[i] ^ maskingKey[i % 4]) & 0xFF); 136 | } 137 | // Write 138 | write(masked); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/WebSocketState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | /** 20 | * WebSocket state. 21 | * 22 | *

23 | * The initial state of a {@link WebSocket} instance is 24 | * CREATED. {@code WebSocket.}{@link 25 | * WebSocket#connect() connect()} method is allowed to be called 26 | * only when the state is {@code CREATED}. If the method is called 27 | * when the state is not {@code CREATED}, a {@link WebSocketException} 28 | * is thrown (its error code is {@link WebSocketError#NOT_IN_CREATED_STATE 29 | * NOT_IN_CREATED_STATE}). 30 | *

31 | * 32 | *

33 | * At the beginning of the implementation of {@code connect()} method, 34 | * the state is changed to CONNECTING, and then 35 | * {@link WebSocketListener#onStateChanged(WebSocket, WebSocketState) 36 | * onStateChanged()} method of each registered listener ({@link 37 | * WebSocketListener}) is called. 38 | *

39 | * 40 | *

41 | * After the state is changed to {@code CONNECTING}, a WebSocket 42 | * opening 43 | * handshake is performed. If an error occurred during the 44 | * handshake, the state is changed to {@code CLOSED} ({@code 45 | * onStateChanged()} method of listeners is called) and a {@code 46 | * WebSocketException} is thrown. There are various reasons for 47 | * handshake failure. If you want to know the reason, get the error 48 | * code ({@link WebSocketError}) by calling {@link 49 | * WebSocketException#getError() getError()} method of the exception. 50 | *

51 | * 52 | *

53 | * After the opening handshake succeeded, the state is changed to 54 | * OPEN. Listeners' {@code onStateChanged()} method 55 | * and {@link WebSocketListener#onConnected(WebSocket, java.util.Map) 56 | * onConnected()} method are called in this order. Note that {@code 57 | * onConnected()} method is called by another thread. 58 | *

59 | * 60 | *

61 | * Upon either sending or receiving a close frame, 63 | * a closing 64 | * handshake is started. The state is changed to 65 | * CLOSING and {@code onStateChanged()} method of 66 | * listeners is called. 67 | *

68 | * 69 | *

70 | * After the client and the server have exchanged close frames, the 71 | * state is changed to CLOSED. Listeners' 72 | * {@code onStateChanged()} method and {@link 73 | * WebSocketListener#onDisconnected(WebSocket, WebSocketFrame, 74 | * WebSocketFrame, boolean) onDisconnected()} method is called in 75 | * this order. 76 | *

77 | */ 78 | public enum WebSocketState 79 | { 80 | /** 81 | * The initial state of a {@link WebSocket} instance. 82 | */ 83 | CREATED, 84 | 85 | 86 | /** 87 | * An opening 88 | * handshake is being performed. 89 | */ 90 | CONNECTING, 91 | 92 | 93 | /** 94 | * The WebSocket connection is established (= the opening handshake 96 | * has succeeded) and usable. 97 | */ 98 | OPEN, 99 | 100 | 101 | /** 102 | * A closing 103 | * handshake is being performed. 104 | */ 105 | CLOSING, 106 | 107 | 108 | /** 109 | * The WebSocket connection is closed. 110 | */ 111 | CLOSED 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/WebSocketThread.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | abstract class WebSocketThread extends Thread 20 | { 21 | protected final WebSocket mWebSocket; 22 | private final ThreadType mThreadType; 23 | 24 | 25 | WebSocketThread(String name, WebSocket ws, ThreadType type) 26 | { 27 | super(name); 28 | 29 | mWebSocket = ws; 30 | mThreadType = type; 31 | } 32 | 33 | 34 | @Override 35 | public void run() 36 | { 37 | ListenerManager lm = mWebSocket.getListenerManager(); 38 | 39 | if (lm != null) 40 | { 41 | // Execute onThreadStarted() of the listeners. 42 | lm.callOnThreadStarted(mThreadType, this); 43 | } 44 | 45 | runMain(); 46 | 47 | if (lm != null) 48 | { 49 | // Execute onThreadStopping() of the listeners. 50 | lm.callOnThreadStopping(mThreadType, this); 51 | } 52 | } 53 | 54 | 55 | public void callOnThreadCreated() 56 | { 57 | ListenerManager lm = mWebSocket.getListenerManager(); 58 | 59 | if (lm != null) 60 | { 61 | lm.callOnThreadCreated(mThreadType, this); 62 | } 63 | } 64 | 65 | 66 | protected abstract void runMain(); 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/neovisionaries/ws/client/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * High-quality WebSocket client implementation in Java. This implementation 3 | * 4 | *
    5 | *
  • complies with RFC 6455 (The WebSocket Protocol), 6 | *
  • works on Java SE 1.5+ and Android, 7 | *
  • supports all the frame types (continuation, binary, text, close, ping and pong), 8 | *
  • provides a method to send a fragmented frame in addition to methods for unfragmented frames, 9 | *
  • provides a method to get the underlying raw socket of a WebSocket to configure it, 10 | *
  • provides a method for Basic Authentication, 11 | *
  • provides a factory class which utilizes {@link javax.net.SocketFactory} interface, 12 | *
  • provides a rich listener interface to hook WebSocket events, 13 | *
  • has fine-grained error codes for fine-grained controllability on errors, 14 | *
  • allows to disable validity checks on RSV1/RSV2/RSV3 bits and opcode of frames, 15 | *
  • supports HTTP proxy, especially "Secure WebSocket" (wss) through 16 | * "Secure Proxy" (https), 17 | *
  • and supports RFC 7692 18 | * (Compression Extensions for WebSocket), also known as permessage-deflate 19 | * (not enabled by default). 20 | *
21 | * 22 | *

23 | * See the description of {@link com.neovisionaries.ws.client.WebSocket WebSocket} 24 | * class for usage. The source code is hosted at 25 | * GitHub. 26 | *

27 | * 28 | *

29 | * For Maven: 30 | *

31 | *
32 | * 35 | *
36 |  * <dependency>
37 |  *     <groupId>com.neovisionaries</groupId>
38 |  *     <artifactId>nv-websocket-client</artifactId>
39 |  *     <version>2.14</version>
40 |  * </dependency>
41 | *
42 | * 43 | * @version 2.14 44 | * 45 | * @author Takahiko Kawasaki 46 | */ 47 | package com.neovisionaries.ws.client; 48 | -------------------------------------------------------------------------------- /src/test/java/com/neovisionaries/ws/client/MiscTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import java.net.URI; 21 | import org.junit.Test; 22 | 23 | 24 | public class MiscTest 25 | { 26 | private static void extractHostTest(String expected, String input) 27 | { 28 | URI uri = URI.create(input); 29 | 30 | String result = Misc.extractHost(uri); 31 | 32 | assertEquals(expected, result); 33 | } 34 | 35 | 36 | private static void extractHostFromAuthorityPartTest(String expected, String input) 37 | { 38 | String result = Misc.extractHostFromAuthorityPart(input); 39 | 40 | assertEquals(expected, result); 41 | } 42 | 43 | 44 | private static void extractHostFromEntireUriTest(String expected, String input) 45 | { 46 | String result = Misc.extractHostFromEntireUri(input); 47 | 48 | assertEquals(expected, result); 49 | } 50 | 51 | 52 | @Test 53 | public void test01() 54 | { 55 | extractHostFromAuthorityPartTest("example.com", "example.com"); 56 | } 57 | 58 | 59 | @Test 60 | public void test02() 61 | { 62 | extractHostFromAuthorityPartTest("example.com", "example.com:8080"); 63 | } 64 | 65 | 66 | @Test 67 | public void test03() 68 | { 69 | extractHostFromAuthorityPartTest("example.com", "id:password@example.com"); 70 | } 71 | 72 | 73 | @Test 74 | public void test04() 75 | { 76 | extractHostFromAuthorityPartTest("example.com", "id:password@example.com:8080"); 77 | } 78 | 79 | 80 | @Test 81 | public void test05() 82 | { 83 | extractHostFromAuthorityPartTest("example.com", "id@example.com"); 84 | } 85 | 86 | 87 | @Test 88 | public void test06() 89 | { 90 | extractHostFromAuthorityPartTest("example.com", "id:@example.com"); 91 | } 92 | 93 | 94 | @Test 95 | public void test07() 96 | { 97 | extractHostFromAuthorityPartTest("example.com", ":@example.com"); 98 | } 99 | 100 | 101 | @Test 102 | public void test08() 103 | { 104 | extractHostFromAuthorityPartTest("example.com", ":password@example.com"); 105 | } 106 | 107 | 108 | @Test 109 | public void test09() 110 | { 111 | extractHostFromAuthorityPartTest("example.com", "@example.com"); 112 | } 113 | 114 | 115 | @Test 116 | public void test10() 117 | { 118 | extractHostFromEntireUriTest("example.com", "ws://example.com"); 119 | } 120 | 121 | 122 | @Test 123 | public void test11() 124 | { 125 | extractHostFromEntireUriTest("example.com", "ws://example.com:8080"); 126 | } 127 | 128 | 129 | @Test 130 | public void test12() 131 | { 132 | extractHostFromEntireUriTest("example.com", "ws://id:password@example.com"); 133 | } 134 | 135 | 136 | @Test 137 | public void test13() 138 | { 139 | extractHostFromEntireUriTest("example.com", "ws://id:password@example.com:8080"); 140 | } 141 | 142 | 143 | @Test 144 | public void test14() 145 | { 146 | extractHostFromEntireUriTest("example.com", "ws://example.com/"); 147 | } 148 | 149 | 150 | @Test 151 | public void test15() 152 | { 153 | extractHostFromEntireUriTest("example.com", "ws://example.com:8080/"); 154 | } 155 | 156 | 157 | @Test 158 | public void test16() 159 | { 160 | extractHostFromEntireUriTest("example.com", "ws://id:password@example.com/"); 161 | } 162 | 163 | 164 | @Test 165 | public void test17() 166 | { 167 | extractHostFromEntireUriTest("example.com", "ws://id:password@example.com:8080/"); 168 | } 169 | 170 | 171 | @Test 172 | public void test18() 173 | { 174 | extractHostFromEntireUriTest("example.com", "ws://example.com/path?key=@value"); 175 | } 176 | 177 | 178 | @Test 179 | public void test19() 180 | { 181 | extractHostFromEntireUriTest("example.com", "ws://example.com:8080/path?key=@value"); 182 | } 183 | 184 | 185 | @Test 186 | public void test20() 187 | { 188 | extractHostFromEntireUriTest("example.com", "ws://id:password@example.com/path?key=@value"); 189 | } 190 | 191 | 192 | @Test 193 | public void test21() 194 | { 195 | extractHostFromEntireUriTest("example.com", "ws://id:password@example.com:8080/path?key=@value"); 196 | } 197 | 198 | 199 | @Test 200 | public void test22() 201 | { 202 | extractHostTest("example.com", "ws://example.com"); 203 | } 204 | 205 | 206 | @Test 207 | public void test23() 208 | { 209 | extractHostTest("example.com", "ws://example.com:8080"); 210 | } 211 | 212 | 213 | @Test 214 | public void test24() 215 | { 216 | extractHostTest("example.com", "ws://id:password@example.com"); 217 | } 218 | 219 | 220 | @Test 221 | public void test25() 222 | { 223 | extractHostTest("example.com", "ws://id:password@example.com:8080"); 224 | } 225 | 226 | 227 | @Test 228 | public void test26() 229 | { 230 | extractHostTest("example.com", "ws://id:password@example.com:8080/path?key=@value"); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/test/java/com/neovisionaries/ws/client/PerMessageDeflateExtensionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertFalse; 21 | import static org.junit.Assert.assertNotNull; 22 | import static org.junit.Assert.assertSame; 23 | import static org.junit.Assert.assertTrue; 24 | import org.junit.Test; 25 | 26 | 27 | public class PerMessageDeflateExtensionTest 28 | { 29 | private static final int DEFAULT_WINDOW_SIZE = 32768; 30 | 31 | 32 | private static PerMessageDeflateExtension parse(String text) 33 | { 34 | return (PerMessageDeflateExtension)WebSocketExtension.parse(text); 35 | } 36 | 37 | 38 | private static PerMessageDeflateExtension parseValid(String text) 39 | { 40 | PerMessageDeflateExtension extension = parse(text); 41 | 42 | try 43 | { 44 | extension.validate(); 45 | } 46 | catch (WebSocketException e) 47 | { 48 | return null; 49 | } 50 | 51 | return extension; 52 | } 53 | 54 | 55 | private static WebSocketException parseInvalid(String text) 56 | { 57 | PerMessageDeflateExtension extension = parse(text); 58 | 59 | try 60 | { 61 | extension.validate(); 62 | } 63 | catch (WebSocketException e) 64 | { 65 | return e; 66 | } 67 | 68 | return null; 69 | } 70 | 71 | 72 | @Test 73 | public void test001() 74 | { 75 | PerMessageDeflateExtension extension = parseValid("permessage-deflate"); 76 | 77 | assertNotNull(extension); 78 | assertFalse(extension.isServerNoContextTakeover()); 79 | assertFalse(extension.isClientNoContextTakeover()); 80 | assertEquals(DEFAULT_WINDOW_SIZE, extension.getServerWindowSize()); 81 | assertEquals(DEFAULT_WINDOW_SIZE, extension.getClientWindowSize()); 82 | } 83 | 84 | 85 | @Test 86 | public void test002() 87 | { 88 | PerMessageDeflateExtension extension = parseValid("permessage-deflate; server_no_context_takeover; client_no_context_takeover"); 89 | 90 | assertNotNull(extension); 91 | assertTrue(extension.isServerNoContextTakeover()); 92 | assertTrue(extension.isClientNoContextTakeover()); 93 | } 94 | 95 | 96 | @Test 97 | public void test003() 98 | { 99 | PerMessageDeflateExtension extension = parseValid("permessage-deflate; server_max_window_bits=8; client_max_window_bits=8"); 100 | 101 | assertNotNull(extension); 102 | assertEquals(256, extension.getServerWindowSize()); 103 | assertEquals(256, extension.getClientWindowSize()); 104 | } 105 | 106 | 107 | @Test 108 | public void test004() 109 | { 110 | WebSocketException exception = parseInvalid("permessage-deflate; unknown_parameter"); 111 | 112 | assertNotNull(exception); 113 | assertSame(WebSocketError.PERMESSAGE_DEFLATE_UNSUPPORTED_PARAMETER, exception.getError()); 114 | } 115 | 116 | 117 | @Test 118 | public void test005() 119 | { 120 | WebSocketException exception = parseInvalid("permessage-deflate; server_max_window_bits"); 121 | 122 | assertNotNull(exception); 123 | assertSame(WebSocketError.PERMESSAGE_DEFLATE_INVALID_MAX_WINDOW_BITS, exception.getError()); 124 | } 125 | 126 | 127 | @Test 128 | public void test006() 129 | { 130 | WebSocketException exception = parseInvalid("permessage-deflate; server_max_window_bits=abc"); 131 | 132 | assertNotNull(exception); 133 | assertSame(WebSocketError.PERMESSAGE_DEFLATE_INVALID_MAX_WINDOW_BITS, exception.getError()); 134 | } 135 | 136 | 137 | @Test 138 | public void test007() 139 | { 140 | WebSocketException exception = parseInvalid("permessage-deflate; server_max_window_bits=0"); 141 | 142 | assertNotNull(exception); 143 | assertSame(WebSocketError.PERMESSAGE_DEFLATE_INVALID_MAX_WINDOW_BITS, exception.getError()); 144 | } 145 | 146 | 147 | @Test 148 | public void test008() 149 | { 150 | WebSocketException exception = parseInvalid("permessage-deflate; server_max_window_bits=7"); 151 | 152 | assertNotNull(exception); 153 | assertSame(WebSocketError.PERMESSAGE_DEFLATE_INVALID_MAX_WINDOW_BITS, exception.getError()); 154 | } 155 | 156 | 157 | @Test 158 | public void test009() 159 | { 160 | WebSocketException exception = parseInvalid("permessage-deflate; server_max_window_bits=16"); 161 | 162 | assertNotNull(exception); 163 | assertSame(WebSocketError.PERMESSAGE_DEFLATE_INVALID_MAX_WINDOW_BITS, exception.getError()); 164 | } 165 | 166 | 167 | @Test 168 | public void test010() 169 | { 170 | WebSocketException exception = parseInvalid("permessage-deflate; client_max_window_bits"); 171 | 172 | assertNotNull(exception); 173 | assertSame(WebSocketError.PERMESSAGE_DEFLATE_INVALID_MAX_WINDOW_BITS, exception.getError()); 174 | } 175 | 176 | 177 | @Test 178 | public void test011() 179 | { 180 | WebSocketException exception = parseInvalid("permessage-deflate; client_max_window_bits=abc"); 181 | 182 | assertNotNull(exception); 183 | assertSame(WebSocketError.PERMESSAGE_DEFLATE_INVALID_MAX_WINDOW_BITS, exception.getError()); 184 | } 185 | 186 | 187 | @Test 188 | public void test012() 189 | { 190 | WebSocketException exception = parseInvalid("permessage-deflate; client_max_window_bits=0"); 191 | 192 | assertNotNull(exception); 193 | assertSame(WebSocketError.PERMESSAGE_DEFLATE_INVALID_MAX_WINDOW_BITS, exception.getError()); 194 | } 195 | 196 | 197 | @Test 198 | public void test013() 199 | { 200 | WebSocketException exception = parseInvalid("permessage-deflate; client_max_window_bits=7"); 201 | 202 | assertNotNull(exception); 203 | assertSame(WebSocketError.PERMESSAGE_DEFLATE_INVALID_MAX_WINDOW_BITS, exception.getError()); 204 | } 205 | 206 | 207 | @Test 208 | public void test014() 209 | { 210 | WebSocketException exception = parseInvalid("permessage-deflate; client_max_window_bits=16"); 211 | 212 | assertNotNull(exception); 213 | assertSame(WebSocketError.PERMESSAGE_DEFLATE_INVALID_MAX_WINDOW_BITS, exception.getError()); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/test/java/com/neovisionaries/ws/client/TokenTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertFalse; 21 | import static org.junit.Assert.assertTrue; 22 | import org.junit.Test; 23 | 24 | 25 | public class TokenTest 26 | { 27 | private static void isValid(String text) 28 | { 29 | assertTrue(Token.isValid(text)); 30 | } 31 | 32 | 33 | private static void isInvalid(String text) 34 | { 35 | assertFalse(Token.isValid(text)); 36 | } 37 | 38 | 39 | private static void unescape(String expected, String input) 40 | { 41 | assertEquals(expected, Token.unescape(input)); 42 | } 43 | 44 | 45 | private static void unquote(String expected, String input) 46 | { 47 | assertEquals(expected, Token.unquote(input)); 48 | } 49 | 50 | 51 | @Test 52 | public void test001() 53 | { 54 | isInvalid(null); 55 | } 56 | 57 | 58 | @Test 59 | public void test002() 60 | { 61 | isInvalid(""); 62 | } 63 | 64 | 65 | @Test 66 | public void test003() 67 | { 68 | isInvalid(" "); 69 | } 70 | 71 | 72 | @Test 73 | public void test004() 74 | { 75 | isValid("abc"); 76 | } 77 | 78 | 79 | @Test 80 | public void test005() 81 | { 82 | unescape(null, null); 83 | } 84 | 85 | 86 | @Test 87 | public void test006() 88 | { 89 | unescape("", ""); 90 | } 91 | 92 | 93 | @Test 94 | public void test007() 95 | { 96 | unescape("abc", "abc"); 97 | } 98 | 99 | 100 | @Test 101 | public void test008() 102 | { 103 | unescape("abc", "ab\\c"); 104 | } 105 | 106 | 107 | @Test 108 | public void test009() 109 | { 110 | unescape("ab\\", "ab\\\\"); 111 | } 112 | 113 | 114 | @Test 115 | public void test010() 116 | { 117 | unescape("ab\\c", "ab\\\\c"); 118 | } 119 | 120 | 121 | @Test 122 | public void test011() 123 | { 124 | unquote(null, null); 125 | } 126 | 127 | 128 | @Test 129 | public void test012() 130 | { 131 | unquote("", ""); 132 | } 133 | 134 | 135 | @Test 136 | public void test013() 137 | { 138 | unquote("abc", "abc"); 139 | } 140 | 141 | 142 | @Test 143 | public void test014() 144 | { 145 | unquote("abc", "\"abc\""); 146 | } 147 | 148 | 149 | @Test 150 | public void test015() 151 | { 152 | unquote("\"abc", "\"abc"); 153 | } 154 | 155 | 156 | @Test 157 | public void test016() 158 | { 159 | unquote("abc\"", "abc\""); 160 | } 161 | 162 | 163 | @Test 164 | public void test017() 165 | { 166 | unquote("abc", "\"ab\\c\""); 167 | } 168 | 169 | 170 | @Test 171 | public void test018() 172 | { 173 | unquote("ab\\c", "\"ab\\\\c\""); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/test/java/com/neovisionaries/ws/client/WebSocketExtensionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertFalse; 21 | import static org.junit.Assert.assertNotNull; 22 | import static org.junit.Assert.assertNull; 23 | import static org.junit.Assert.assertTrue; 24 | import org.junit.Test; 25 | 26 | 27 | public class WebSocketExtensionTest 28 | { 29 | private static WebSocketExtension parse(String text) 30 | { 31 | return WebSocketExtension.parse(text); 32 | } 33 | 34 | 35 | @Test 36 | public void test001() 37 | { 38 | WebSocketExtension extension = parse("abc"); 39 | 40 | assertNotNull(extension); 41 | assertEquals("abc", extension.getName()); 42 | } 43 | 44 | 45 | @Test 46 | public void test002() 47 | { 48 | WebSocketExtension extension = parse("abc; x=1; y=2"); 49 | 50 | assertNotNull(extension); 51 | assertEquals("abc", extension.getName()); 52 | assertEquals("1", extension.getParameter("x")); 53 | assertEquals("2", extension.getParameter("y")); 54 | } 55 | 56 | 57 | @Test 58 | public void test003() 59 | { 60 | WebSocketExtension extension = parse("abc; x"); 61 | 62 | assertNotNull(extension); 63 | assertEquals("abc", extension.getName()); 64 | assertNull(extension.getParameter("x")); 65 | assertTrue(extension.containsParameter("x")); 66 | } 67 | 68 | 69 | @Test 70 | public void test004() 71 | { 72 | WebSocketExtension extension = parse("abc; x="); 73 | 74 | assertNotNull(extension); 75 | assertEquals("abc", extension.getName()); 76 | assertFalse(extension.containsParameter("x")); 77 | } 78 | 79 | 80 | @Test 81 | public void test005() 82 | { 83 | WebSocketExtension extension = parse("abc; x=\"1\"; y=\"2\""); 84 | 85 | assertNotNull(extension); 86 | assertEquals("abc", extension.getName()); 87 | assertEquals("1", extension.getParameter("x")); 88 | assertEquals("2", extension.getParameter("y")); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/com/neovisionaries/ws/client/WebSocketFrameTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016 Neo Visionaries Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.neovisionaries.ws.client; 17 | 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertNotNull; 21 | import static org.junit.Assert.assertNull; 22 | import static org.junit.Assert.assertTrue; 23 | import java.util.List; 24 | import org.junit.Test; 25 | 26 | 27 | public class WebSocketFrameTest 28 | { 29 | @Test 30 | public void test001() 31 | { 32 | WebSocketFrame frame = WebSocketFrame.createTextFrame(null); 33 | 34 | assertTrue(frame.toString().endsWith("Payload=null)")); 35 | } 36 | 37 | 38 | @Test 39 | public void test002() 40 | { 41 | WebSocketFrame frame = WebSocketFrame.createTextFrame("dummy"); 42 | frame.setRsv1(true); 43 | 44 | assertTrue(frame.toString().endsWith("Payload=compressed)")); 45 | } 46 | 47 | 48 | @Test 49 | public void test003() 50 | { 51 | WebSocketFrame frame = WebSocketFrame.createTextFrame("hello"); 52 | 53 | assertTrue(frame.toString().endsWith("Payload=\"hello\")")); 54 | } 55 | 56 | 57 | @Test 58 | public void test004() 59 | { 60 | WebSocketFrame frame = WebSocketFrame.createBinaryFrame(null); 61 | 62 | assertTrue(frame.toString().endsWith("Payload=null)")); 63 | } 64 | 65 | 66 | @Test 67 | public void test005() 68 | { 69 | byte[] payload = new byte[] { (byte)0x01, (byte)0x23, (byte)0xAB }; 70 | WebSocketFrame frame = WebSocketFrame.createBinaryFrame(payload); 71 | frame.setRsv1(true); 72 | 73 | assertTrue(frame.toString().endsWith("Payload=compressed)")); 74 | } 75 | 76 | 77 | @Test 78 | public void test006() 79 | { 80 | byte[] payload = new byte[] { (byte)0x01, (byte)0x23, (byte)0xAB }; 81 | WebSocketFrame frame = WebSocketFrame.createBinaryFrame(payload); 82 | 83 | assertTrue(frame.toString().endsWith("Payload=01 23 AB)")); 84 | } 85 | 86 | 87 | @Test 88 | public void test007() 89 | { 90 | WebSocketFrame frame = WebSocketFrame.createTextFrame("0123456789"); 91 | List list = WebSocketFrame.splitIfNecessary(frame, 3, null); 92 | 93 | assertNotNull(list); 94 | assertEquals(4, list.size()); 95 | 96 | frame = list.get(0); 97 | assertEquals("012", frame.getPayloadText()); 98 | assertEquals(true, frame.isTextFrame()); 99 | assertEquals(false, frame.getFin()); 100 | 101 | frame = list.get(1); 102 | assertEquals("345", frame.getPayloadText()); 103 | assertEquals(true, frame.isContinuationFrame()); 104 | assertEquals(false, frame.getFin()); 105 | 106 | frame = list.get(2); 107 | assertEquals("678", frame.getPayloadText()); 108 | assertEquals(true, frame.isContinuationFrame()); 109 | assertEquals(false, frame.getFin()); 110 | 111 | frame = list.get(3); 112 | assertEquals("9", frame.getPayloadText()); 113 | assertEquals(true, frame.isContinuationFrame()); 114 | assertEquals(true, frame.getFin()); 115 | } 116 | 117 | 118 | @Test 119 | public void test008() 120 | { 121 | WebSocketFrame frame = WebSocketFrame.createContinuationFrame("ABCDEF"); 122 | List list = WebSocketFrame.splitIfNecessary(frame, 2, null); 123 | 124 | assertNotNull(list); 125 | assertEquals(3, list.size()); 126 | 127 | frame = list.get(0); 128 | assertEquals("AB", frame.getPayloadText()); 129 | assertEquals(true, frame.isContinuationFrame()); 130 | assertEquals(false, frame.getFin()); 131 | 132 | frame = list.get(1); 133 | assertEquals("CD", frame.getPayloadText()); 134 | assertEquals(true, frame.isContinuationFrame()); 135 | assertEquals(false, frame.getFin()); 136 | 137 | frame = list.get(2); 138 | assertEquals("EF", frame.getPayloadText()); 139 | assertEquals(true, frame.isContinuationFrame()); 140 | assertEquals(false, frame.getFin()); 141 | } 142 | 143 | 144 | @Test 145 | public void test009() 146 | { 147 | String payload = "000000000000000000000000000000"; 148 | WebSocketFrame frame = WebSocketFrame.createTextFrame(payload); 149 | PerMessageCompressionExtension pmce = new PerMessageDeflateExtension(); 150 | 151 | // splitIfNecessary() compresses the WebSocket frame and does not split it. 152 | List list = WebSocketFrame.splitIfNecessary(frame, payload.length() - 1, pmce); 153 | 154 | assertNull(list); 155 | 156 | // splitIfNecessary() compresses the WebSocket frame and 157 | list = WebSocketFrame.splitIfNecessary(frame, 1, pmce); 158 | assertNotNull(list); 159 | } 160 | 161 | 162 | @Test 163 | public void test010() 164 | { 165 | String payload = "000000000000000000000000000000111111111111111111111111111111"; 166 | WebSocketFrame frame = WebSocketFrame.createTextFrame(payload); 167 | PerMessageCompressionExtension pmce = new PerMessageDeflateExtension(); 168 | 169 | // splitIfNecessary() compresses the WebSocket frame and splits it. 170 | List list = WebSocketFrame.splitIfNecessary(frame, 1, pmce); 171 | 172 | assertNotNull(list); 173 | 174 | // Compute the total payload length of the WebSocket frames. 175 | int totalLength = 0; 176 | for (WebSocketFrame f : list) 177 | { 178 | totalLength += f.getPayloadLength(); 179 | } 180 | 181 | // The total payload length of the WebSocket frames should be less 182 | // than the payload length of the original WebSocket frame. 183 | assertTrue(totalLength < payload.length()); 184 | } 185 | } 186 | --------------------------------------------------------------------------------