├── .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 | *
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