1 /*
2  * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /*
25  * @test
26  * @build DummyWebSocketServer
27  * @run testng/othervm
28  *      -Djdk.internal.httpclient.websocket.debug=true
29  *       Abort
30  */
31 
32 import org.testng.annotations.Test;
33 
34 import java.io.IOException;
35 import java.net.ProtocolException;
36 import java.net.http.WebSocket;
37 import java.nio.ByteBuffer;
38 import java.util.Arrays;
39 import java.util.List;
40 import java.util.concurrent.CompletableFuture;
41 import java.util.concurrent.CompletionStage;
42 import java.util.concurrent.TimeUnit;
43 import java.util.concurrent.TimeoutException;
44 
45 import static java.net.http.HttpClient.newHttpClient;
46 import static java.net.http.WebSocket.NORMAL_CLOSURE;
47 import static org.testng.Assert.assertEquals;
48 import static org.testng.Assert.assertThrows;
49 import static org.testng.Assert.assertTrue;
50 import static org.testng.Assert.fail;
51 
52 public class Abort {
53 
54     private static final Class<NullPointerException> NPE = NullPointerException.class;
55     private static final Class<IllegalArgumentException> IAE = IllegalArgumentException.class;
56     private static final Class<IOException> IOE = IOException.class;
57 
58 
59     @Test
onOpenThenAbort()60     public void onOpenThenAbort() throws Exception {
61         int[] bytes = new int[]{
62                 0x88, 0x00, // opcode=close
63         };
64         try (var server = Support.serverWithCannedData(bytes)) {
65             server.open();
66             // messages are available
67             MockListener listener = new MockListener() {
68                 @Override
69                 protected void onOpen0(WebSocket webSocket) {
70                     // unbounded request
71                     webSocket.request(Long.MAX_VALUE);
72                     webSocket.abort();
73                 }
74             };
75             var webSocket = newHttpClient().newWebSocketBuilder()
76                     .buildAsync(server.getURI(), listener)
77                     .join();
78             try {
79                 TimeUnit.SECONDS.sleep(5);
80                 List<MockListener.Invocation> inv = listener.invocationsSoFar();
81                 // no more invocations after onOpen as WebSocket was aborted
82                 assertEquals(inv, List.of(MockListener.Invocation.onOpen(webSocket)));
83             } finally {
84                 webSocket.abort();
85             }
86         }
87     }
88 
89     @Test
onOpenThenOnTextThenAbort()90     public void onOpenThenOnTextThenAbort() throws Exception {
91         int[] bytes = new int[]{
92                 0x81, 0x00, // opcode=text, fin=true
93                 0x88, 0x00, // opcode=close
94         };
95         try (var server = Support.serverWithCannedData(bytes)) {
96             server.open();
97             MockListener listener = new MockListener() {
98                 @Override
99                 protected void onOpen0(WebSocket webSocket) {
100                     // unbounded request
101                     webSocket.request(Long.MAX_VALUE);
102                 }
103 
104                 @Override
105                 protected CompletionStage<?> onText0(WebSocket webSocket,
106                                                      CharSequence message,
107                                                      boolean last) {
108                     webSocket.abort();
109                     return super.onText0(webSocket, message, last);
110                 }
111             };
112             var webSocket = newHttpClient().newWebSocketBuilder()
113                     .buildAsync(server.getURI(), listener)
114                     .join();
115             try {
116                 TimeUnit.SECONDS.sleep(5);
117                 List<MockListener.Invocation> inv = listener.invocationsSoFar();
118                 // no more invocations after onOpen, onBinary as WebSocket was aborted
119                 List<MockListener.Invocation> expected = List.of(
120                         MockListener.Invocation.onOpen(webSocket),
121                         MockListener.Invocation.onText(webSocket, "", true));
122                 assertEquals(inv, expected);
123             } finally {
124                 webSocket.abort();
125             }
126         }
127     }
128 
129     @Test
onOpenThenOnBinaryThenAbort()130     public void onOpenThenOnBinaryThenAbort() throws Exception {
131         int[] bytes = new int[]{
132                 0x82, 0x00, // opcode=binary, fin=true
133                 0x88, 0x00, // opcode=close
134         };
135         try (var server = Support.serverWithCannedData(bytes)) {
136             server.open();
137             MockListener listener = new MockListener() {
138                 @Override
139                 protected void onOpen0(WebSocket webSocket) {
140                     // unbounded request
141                     webSocket.request(Long.MAX_VALUE);
142                 }
143 
144                 @Override
145                 protected CompletionStage<?> onBinary0(WebSocket webSocket,
146                                                        ByteBuffer message,
147                                                        boolean last) {
148                     webSocket.abort();
149                     return super.onBinary0(webSocket, message, last);
150                 }
151             };
152             var webSocket = newHttpClient().newWebSocketBuilder()
153                     .buildAsync(server.getURI(), listener)
154                     .join();
155             try {
156                 TimeUnit.SECONDS.sleep(5);
157                 List<MockListener.Invocation> inv = listener.invocationsSoFar();
158                 // no more invocations after onOpen, onBinary as WebSocket was aborted
159                 List<MockListener.Invocation> expected = List.of(
160                         MockListener.Invocation.onOpen(webSocket),
161                         MockListener.Invocation.onBinary(webSocket, ByteBuffer.allocate(0), true));
162                 assertEquals(inv, expected);
163             } finally {
164                 webSocket.abort();
165             }
166         }
167     }
168 
169     @Test
onOpenThenOnPingThenAbort()170     public void onOpenThenOnPingThenAbort() throws Exception {
171         int[] bytes = {
172                 0x89, 0x00, // opcode=ping
173                 0x88, 0x00, // opcode=close
174         };
175         try (var server = Support.serverWithCannedData(bytes)) {
176             server.open();
177             MockListener listener = new MockListener() {
178                 @Override
179                 protected void onOpen0(WebSocket webSocket) {
180                     // unbounded request
181                     webSocket.request(Long.MAX_VALUE);
182                 }
183 
184                 @Override
185                 protected CompletionStage<?> onPing0(WebSocket webSocket,
186                                                      ByteBuffer message) {
187                     webSocket.abort();
188                     return super.onPing0(webSocket, message);
189                 }
190             };
191             var webSocket = newHttpClient().newWebSocketBuilder()
192                     .buildAsync(server.getURI(), listener)
193                     .join();
194             try {
195                 TimeUnit.SECONDS.sleep(5);
196                 List<MockListener.Invocation> inv = listener.invocationsSoFar();
197                 // no more invocations after onOpen, onPing as WebSocket was aborted
198                 List<MockListener.Invocation> expected = List.of(
199                         MockListener.Invocation.onOpen(webSocket),
200                         MockListener.Invocation.onPing(webSocket, ByteBuffer.allocate(0)));
201                 assertEquals(inv, expected);
202             } finally {
203                 webSocket.abort();
204             }
205         }
206     }
207 
208     @Test
onOpenThenOnPongThenAbort()209     public void onOpenThenOnPongThenAbort() throws Exception {
210         int[] bytes = {
211                 0x8a, 0x00, // opcode=pong
212                 0x88, 0x00, // opcode=close
213         };
214         try (var server = Support.serverWithCannedData(bytes)) {
215             server.open();
216             MockListener listener = new MockListener() {
217                 @Override
218                 protected void onOpen0(WebSocket webSocket) {
219                     // unbounded request
220                     webSocket.request(Long.MAX_VALUE);
221                 }
222 
223                 @Override
224                 protected CompletionStage<?> onPong0(WebSocket webSocket,
225                                                      ByteBuffer message) {
226                     webSocket.abort();
227                     return super.onPong0(webSocket, message);
228                 }
229             };
230             var webSocket = newHttpClient().newWebSocketBuilder()
231                     .buildAsync(server.getURI(), listener)
232                     .join();
233             try {
234                 TimeUnit.SECONDS.sleep(5);
235                 List<MockListener.Invocation> inv = listener.invocationsSoFar();
236                 // no more invocations after onOpen, onPong as WebSocket was aborted
237                 List<MockListener.Invocation> expected = List.of(
238                         MockListener.Invocation.onOpen(webSocket),
239                         MockListener.Invocation.onPong(webSocket, ByteBuffer.allocate(0)));
240                 assertEquals(inv, expected);
241             } finally {
242                 webSocket.abort();
243             }
244         }
245     }
246 
247     @Test
onOpenThenOnCloseThenAbort()248     public void onOpenThenOnCloseThenAbort() throws Exception {
249         int[] bytes = {
250                 0x88, 0x00, // opcode=close
251                 0x8a, 0x00, // opcode=pong
252         };
253         try (var server = Support.serverWithCannedData(bytes)) {
254             server.open();
255             MockListener listener = new MockListener() {
256                 @Override
257                 protected void onOpen0(WebSocket webSocket) {
258                     // unbounded request
259                     webSocket.request(Long.MAX_VALUE);
260                 }
261 
262                 @Override
263                 protected CompletionStage<?> onClose0(WebSocket webSocket,
264                                                       int statusCode,
265                                                       String reason) {
266                     webSocket.abort();
267                     return super.onClose0(webSocket, statusCode, reason);
268                 }
269             };
270             var webSocket = newHttpClient().newWebSocketBuilder()
271                     .buildAsync(server.getURI(), listener)
272                     .join();
273             try {
274                 TimeUnit.SECONDS.sleep(5);
275                 List<MockListener.Invocation> inv = listener.invocationsSoFar();
276                 // no more invocations after onOpen, onClose
277                 List<MockListener.Invocation> expected = List.of(
278                         MockListener.Invocation.onOpen(webSocket),
279                         MockListener.Invocation.onClose(webSocket, 1005, ""));
280                 assertEquals(inv, expected);
281             } finally {
282                 webSocket.abort();
283             }
284         }
285     }
286 
287     @Test
onOpenThenOnErrorThenAbort()288     public void onOpenThenOnErrorThenAbort() throws Exception {
289         // A header of 128 bytes long Ping (which is a protocol error)
290         int[] badPingHeader = new int[]{0x89, 0x7e, 0x00, 0x80};
291         int[] closeMessage = new int[]{0x88, 0x00};
292         int[] bytes = new int[badPingHeader.length + 128 + closeMessage.length];
293         System.arraycopy(badPingHeader, 0, bytes, 0, badPingHeader.length);
294         System.arraycopy(closeMessage, 0, bytes, badPingHeader.length + 128, closeMessage.length);
295         try (var server = Support.serverWithCannedData(bytes)) {
296             server.open();
297             MockListener listener = new MockListener() {
298                 @Override
299                 protected void onOpen0(WebSocket webSocket) {
300                     // unbounded request
301                     webSocket.request(Long.MAX_VALUE);
302                 }
303 
304                 @Override
305                 protected void onError0(WebSocket webSocket, Throwable error) {
306                     webSocket.abort();
307                     super.onError0(webSocket, error);
308                 }
309             };
310             var webSocket = newHttpClient().newWebSocketBuilder()
311                     .buildAsync(server.getURI(), listener)
312                     .join();
313             try {
314                 TimeUnit.SECONDS.sleep(5);
315                 List<MockListener.Invocation> inv = listener.invocationsSoFar();
316                 // no more invocations after onOpen, onError
317                 List<MockListener.Invocation> expected = List.of(
318                         MockListener.Invocation.onOpen(webSocket),
319                         MockListener.Invocation.onError(webSocket, ProtocolException.class));
320                 System.out.println("actual invocations:" + Arrays.toString(inv.toArray()));
321                 assertEquals(inv, expected);
322             } finally {
323                 webSocket.abort();
324             }
325         }
326     }
327 
328     @Test
immediateAbort()329     public void immediateAbort() throws Exception {
330         CompletableFuture<Void> messageReceived = new CompletableFuture<>();
331         WebSocket.Listener listener = new WebSocket.Listener() {
332 
333             @Override
334             public void onOpen(WebSocket webSocket) {
335                 /* no initial request */
336             }
337 
338             @Override
339             public CompletionStage<?> onText(WebSocket webSocket,
340                                              CharSequence message,
341                                              boolean last) {
342                 messageReceived.complete(null);
343                 return null;
344             }
345 
346             @Override
347             public CompletionStage<?> onBinary(WebSocket webSocket,
348                                                ByteBuffer message,
349                                                boolean last) {
350                 messageReceived.complete(null);
351                 return null;
352             }
353 
354             @Override
355             public CompletionStage<?> onPing(WebSocket webSocket,
356                                              ByteBuffer message) {
357                 messageReceived.complete(null);
358                 return null;
359             }
360 
361             @Override
362             public CompletionStage<?> onPong(WebSocket webSocket,
363                                              ByteBuffer message) {
364                 messageReceived.complete(null);
365                 return null;
366             }
367 
368             @Override
369             public CompletionStage<?> onClose(WebSocket webSocket,
370                                               int statusCode,
371                                               String reason) {
372                 messageReceived.complete(null);
373                 return null;
374             }
375         };
376 
377         int[] bytes = new int[]{
378                 0x82, 0x00, // opcode=binary, fin=true
379                 0x88, 0x00, // opcode=close
380         };
381         try (var server = Support.serverWithCannedData(bytes)) {
382             server.open();
383 
384             WebSocket ws = newHttpClient()
385                     .newWebSocketBuilder()
386                     .buildAsync(server.getURI(), listener)
387                     .join();
388             try {
389                 for (int i = 0; i < 3; i++) {
390                     System.out.printf("iteration #%s%n", i);
391                     // after the first abort() each consecutive one must be a no-op,
392                     // moreover, query methods should continue to return consistent
393                     // values
394                     for (int j = 0; j < 3; j++) {
395                         System.out.printf("abort #%s%n", j);
396                         ws.abort();
397                         assertTrue(ws.isInputClosed());
398                         assertTrue(ws.isOutputClosed());
399                         assertEquals(ws.getSubprotocol(), "");
400                     }
401                     // at this point valid requests MUST be a no-op:
402                     for (int j = 0; j < 3; j++) {
403                         System.out.printf("request #%s%n", j);
404                         ws.request(1);
405                         ws.request(2);
406                         ws.request(8);
407                         ws.request(Integer.MAX_VALUE);
408                         ws.request(Long.MAX_VALUE);
409                         // invalid requests MUST throw IAE:
410                         assertThrows(IAE, () -> ws.request(Integer.MIN_VALUE));
411                         assertThrows(IAE, () -> ws.request(Long.MIN_VALUE));
412                         assertThrows(IAE, () -> ws.request(-1));
413                         assertThrows(IAE, () -> ws.request(0));
414                     }
415                 }
416                 // even though there is a bunch of messages readily available on the
417                 // wire we shouldn't have received any of them as we aborted before
418                 // the first request
419                 try {
420                     messageReceived.get(5, TimeUnit.SECONDS);
421                     fail();
422                 } catch (TimeoutException expected) {
423                     System.out.println("Finished waiting");
424                 }
425                 for (int i = 0; i < 3; i++) {
426                     System.out.printf("send #%s%n", i);
427                     Support.assertFails(IOE, ws.sendText("text!", false));
428                     Support.assertFails(IOE, ws.sendText("text!", true));
429                     Support.assertFails(IOE, ws.sendBinary(ByteBuffer.allocate(16), false));
430                     Support.assertFails(IOE, ws.sendBinary(ByteBuffer.allocate(16), true));
431                     Support.assertFails(IOE, ws.sendPing(ByteBuffer.allocate(16)));
432                     Support.assertFails(IOE, ws.sendPong(ByteBuffer.allocate(16)));
433                     Support.assertFails(IOE, ws.sendClose(NORMAL_CLOSURE, "a reason"));
434                     assertThrows(NPE, () -> ws.sendText(null, false));
435                     assertThrows(NPE, () -> ws.sendText(null, true));
436                     assertThrows(NPE, () -> ws.sendBinary(null, false));
437                     assertThrows(NPE, () -> ws.sendBinary(null, true));
438                     assertThrows(NPE, () -> ws.sendPing(null));
439                     assertThrows(NPE, () -> ws.sendPong(null));
440                     assertThrows(NPE, () -> ws.sendClose(NORMAL_CLOSURE, null));
441                 }
442             } finally {
443                 ws.abort();
444             }
445         }
446     }
447 }
448