src/java.net.http/share/classes/jdk/internal/net/http/websocket/MessageDecoder.java
branchhttp-client-branch
changeset 56263 4933a477d628
child 56290 e178d19ff91c
equal deleted inserted replaced
56262:d818a6a8295a 56263:4933a477d628
       
     1 /*
       
     2  * Copyright (c) 2015, 2018, 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.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package jdk.internal.net.http.websocket;
       
    27 
       
    28 import jdk.internal.net.http.websocket.Frame.Opcode;
       
    29 
       
    30 import java.net.http.WebSocket.MessagePart;
       
    31 import java.nio.ByteBuffer;
       
    32 import java.nio.CharBuffer;
       
    33 import java.nio.charset.CharacterCodingException;
       
    34 
       
    35 import static java.lang.String.format;
       
    36 import static java.nio.charset.StandardCharsets.UTF_8;
       
    37 import static java.util.Objects.requireNonNull;
       
    38 import static jdk.internal.net.http.common.Utils.dump;
       
    39 import static jdk.internal.net.http.websocket.StatusCodes.NO_STATUS_CODE;
       
    40 import static jdk.internal.net.http.websocket.StatusCodes.isLegalToReceiveFromServer;
       
    41 
       
    42 /*
       
    43  * Consumes frame parts and notifies a message consumer, when there is
       
    44  * sufficient data to produce a message, or part thereof.
       
    45  *
       
    46  * Data consumed but not yet translated is accumulated until it's sufficient to
       
    47  * form a message.
       
    48  */
       
    49 /* Non-final for testing purposes only */
       
    50 class MessageDecoder implements Frame.Consumer {
       
    51 
       
    52     private final static boolean DEBUG = false;
       
    53     private final MessageStreamConsumer output;
       
    54     private final UTF8AccumulatingDecoder decoder = new UTF8AccumulatingDecoder();
       
    55     private boolean fin;
       
    56     private Opcode opcode, originatingOpcode;
       
    57     private MessagePart part = MessagePart.WHOLE;
       
    58     private long payloadLen;
       
    59     private long unconsumedPayloadLen;
       
    60     private ByteBuffer binaryData;
       
    61 
       
    62     MessageDecoder(MessageStreamConsumer output) {
       
    63         this.output = requireNonNull(output);
       
    64     }
       
    65 
       
    66     /* Exposed for testing purposes only */
       
    67     MessageStreamConsumer getOutput() {
       
    68         return output;
       
    69     }
       
    70 
       
    71     @Override
       
    72     public void fin(boolean value) {
       
    73         if (DEBUG) {
       
    74             System.out.printf("[Input] fin %s%n", value);
       
    75         }
       
    76         fin = value;
       
    77     }
       
    78 
       
    79     @Override
       
    80     public void rsv1(boolean value) {
       
    81         if (DEBUG) {
       
    82             System.out.printf("[Input] rsv1 %s%n", value);
       
    83         }
       
    84         if (value) {
       
    85             throw new FailWebSocketException("Unexpected rsv1 bit");
       
    86         }
       
    87     }
       
    88 
       
    89     @Override
       
    90     public void rsv2(boolean value) {
       
    91         if (DEBUG) {
       
    92             System.out.printf("[Input] rsv2 %s%n", value);
       
    93         }
       
    94         if (value) {
       
    95             throw new FailWebSocketException("Unexpected rsv2 bit");
       
    96         }
       
    97     }
       
    98 
       
    99     @Override
       
   100     public void rsv3(boolean value) {
       
   101         if (DEBUG) {
       
   102             System.out.printf("[Input] rsv3 %s%n", value);
       
   103         }
       
   104         if (value) {
       
   105             throw new FailWebSocketException("Unexpected rsv3 bit");
       
   106         }
       
   107     }
       
   108 
       
   109     @Override
       
   110     public void opcode(Opcode v) {
       
   111         if (DEBUG) {
       
   112             System.out.printf("[Input] opcode %s%n", v);
       
   113         }
       
   114         if (v == Opcode.PING || v == Opcode.PONG || v == Opcode.CLOSE) {
       
   115             if (!fin) {
       
   116                 throw new FailWebSocketException("Fragmented control frame  " + v);
       
   117             }
       
   118             opcode = v;
       
   119         } else if (v == Opcode.TEXT || v == Opcode.BINARY) {
       
   120             if (originatingOpcode != null) {
       
   121                 throw new FailWebSocketException(
       
   122                         format("Unexpected frame %s (fin=%s)", v, fin));
       
   123             }
       
   124             opcode = v;
       
   125             if (!fin) {
       
   126                 originatingOpcode = v;
       
   127             }
       
   128         } else if (v == Opcode.CONTINUATION) {
       
   129             if (originatingOpcode == null) {
       
   130                 throw new FailWebSocketException(
       
   131                         format("Unexpected frame %s (fin=%s)", v, fin));
       
   132             }
       
   133             opcode = v;
       
   134         } else {
       
   135             throw new FailWebSocketException("Unexpected opcode " + v);
       
   136         }
       
   137     }
       
   138 
       
   139     @Override
       
   140     public void mask(boolean value) {
       
   141         if (DEBUG) {
       
   142             System.out.printf("[Input] mask %s%n", value);
       
   143         }
       
   144         if (value) {
       
   145             throw new FailWebSocketException("Masked frame received");
       
   146         }
       
   147     }
       
   148 
       
   149     @Override
       
   150     public void payloadLen(long value) {
       
   151         if (DEBUG) {
       
   152             System.out.printf("[Input] payloadLen %s%n", value);
       
   153         }
       
   154         if (opcode.isControl()) {
       
   155             if (value > Frame.MAX_CONTROL_FRAME_PAYLOAD_LENGTH) {
       
   156                 throw new FailWebSocketException(
       
   157                         format("%s's payload length %s", opcode, value));
       
   158             }
       
   159             assert Opcode.CLOSE.isControl();
       
   160             if (opcode == Opcode.CLOSE && value == 1) {
       
   161                 throw new FailWebSocketException("Incomplete status code");
       
   162             }
       
   163         }
       
   164         payloadLen = value;
       
   165         unconsumedPayloadLen = value;
       
   166     }
       
   167 
       
   168     @Override
       
   169     public void maskingKey(int value) {
       
   170         // `MessageDecoder.mask(boolean)` is where a masked frame is detected and
       
   171         // reported on; `MessageDecoder.mask(boolean)` MUST be invoked before
       
   172         // this method;
       
   173         // So this method (`maskingKey`) is not supposed to be invoked while
       
   174         // reading a frame that has came from the server. If this method is
       
   175         // invoked, then it's an error in implementation, thus InternalError
       
   176         throw new InternalError();
       
   177     }
       
   178 
       
   179     @Override
       
   180     public void payloadData(ByteBuffer data) {
       
   181         if (DEBUG) {
       
   182             System.out.printf("[Input] payload %s%n", data);
       
   183         }
       
   184         unconsumedPayloadLen -= data.remaining();
       
   185         boolean isLast = unconsumedPayloadLen == 0;
       
   186         if (opcode.isControl()) {
       
   187             if (binaryData != null) { // An intermediate or the last chunk
       
   188                 binaryData.put(data);
       
   189             } else if (!isLast) { // The first chunk
       
   190                 int remaining = data.remaining();
       
   191                 // It shouldn't be 125, otherwise the next chunk will be of size
       
   192                 // 0, which is not what Reader promises to deliver (eager
       
   193                 // reading)
       
   194                 assert remaining < Frame.MAX_CONTROL_FRAME_PAYLOAD_LENGTH
       
   195                         : dump(remaining);
       
   196                 binaryData = ByteBuffer.allocate(
       
   197                         Frame.MAX_CONTROL_FRAME_PAYLOAD_LENGTH).put(data);
       
   198             } else { // The only chunk
       
   199                 binaryData = ByteBuffer.allocate(data.remaining()).put(data);
       
   200             }
       
   201         } else {
       
   202             part = determinePart(isLast);
       
   203             boolean text = opcode == Opcode.TEXT || originatingOpcode == Opcode.TEXT;
       
   204             if (!text) {
       
   205                 output.onBinary(data.slice(), part);
       
   206                 data.position(data.limit()); // Consume
       
   207             } else {
       
   208                 boolean binaryNonEmpty = data.hasRemaining();
       
   209                 CharBuffer textData;
       
   210                 try {
       
   211                     boolean eof = part == MessagePart.WHOLE
       
   212                             || part == MessagePart.LAST;
       
   213                     textData = decoder.decode(data, eof);
       
   214                 } catch (CharacterCodingException e) {
       
   215                     throw new FailWebSocketException(
       
   216                             "Invalid UTF-8 in frame " + opcode,
       
   217                             StatusCodes.NOT_CONSISTENT).initCause(e);
       
   218                 }
       
   219                 if (!(binaryNonEmpty && !textData.hasRemaining())) {
       
   220                     // If there's a binary data, that result in no text, then we
       
   221                     // don't deliver anything
       
   222                     output.onText(textData, part);
       
   223                 }
       
   224             }
       
   225         }
       
   226     }
       
   227 
       
   228     @Override
       
   229     public void endFrame() {
       
   230         if (DEBUG) {
       
   231             System.out.println("[Input] end frame");
       
   232         }
       
   233         if (opcode.isControl()) {
       
   234             binaryData.flip();
       
   235         }
       
   236         switch (opcode) {
       
   237             case CLOSE:
       
   238                 char statusCode = NO_STATUS_CODE;
       
   239                 String reason = "";
       
   240                 if (payloadLen != 0) {
       
   241                     int len = binaryData.remaining();
       
   242                     assert 2 <= len
       
   243                             && len <= Frame.MAX_CONTROL_FRAME_PAYLOAD_LENGTH
       
   244                             : dump(len, payloadLen);
       
   245                     statusCode = binaryData.getChar();
       
   246                     if (!isLegalToReceiveFromServer(statusCode)) {
       
   247                         throw new FailWebSocketException(
       
   248                                 "Illegal status code: " + statusCode);
       
   249                     }
       
   250                     try {
       
   251                         reason = UTF_8.newDecoder().decode(binaryData).toString();
       
   252                     } catch (CharacterCodingException e) {
       
   253                         throw new FailWebSocketException("Illegal close reason")
       
   254                                 .initCause(e);
       
   255                     }
       
   256                 }
       
   257                 output.onClose(statusCode, reason);
       
   258                 break;
       
   259             case PING:
       
   260                 output.onPing(binaryData);
       
   261                 binaryData = null;
       
   262                 break;
       
   263             case PONG:
       
   264                 output.onPong(binaryData);
       
   265                 binaryData = null;
       
   266                 break;
       
   267             default:
       
   268                 assert opcode == Opcode.TEXT || opcode == Opcode.BINARY
       
   269                         || opcode == Opcode.CONTINUATION : dump(opcode);
       
   270                 if (fin) {
       
   271                     // It is always the last chunk:
       
   272                     // either TEXT(FIN=TRUE)/BINARY(FIN=TRUE) or CONT(FIN=TRUE)
       
   273                     originatingOpcode = null;
       
   274                 }
       
   275                 break;
       
   276         }
       
   277         payloadLen = 0;
       
   278         opcode = null;
       
   279     }
       
   280 
       
   281     private MessagePart determinePart(boolean isLast) {
       
   282         boolean lastChunk = fin && isLast;
       
   283         switch (part) {
       
   284             case LAST:
       
   285             case WHOLE:
       
   286                 return lastChunk ? MessagePart.WHOLE : MessagePart.FIRST;
       
   287             case FIRST:
       
   288             case PART:
       
   289                 return lastChunk ? MessagePart.LAST : MessagePart.PART;
       
   290             default:
       
   291                 throw new InternalError(String.valueOf(part));
       
   292         }
       
   293     }
       
   294 }