View Javadoc

    1   /*
    2    * Copyright 2012 The Netty Project
    3    *
    4    * The Netty Project licenses this file to you under the Apache License,
    5    * version 2.0 (the "License"); you may not use this file except in compliance
    6    * with the License. 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, WITHOUT
    12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    13   * License for the specific language governing permissions and limitations
    14   * under the License.
    15   */
    16  // (BSD License: http://www.opensource.org/licenses/bsd-license)
    17  //
    18  // Copyright (c) 2011, Joe Walnes and contributors
    19  // All rights reserved.
    20  //
    21  // Redistribution and use in source and binary forms, with or
    22  // without modification, are permitted provided that the
    23  // following conditions are met:
    24  //
    25  // * Redistributions of source code must retain the above
    26  // copyright notice, this list of conditions and the
    27  // following disclaimer.
    28  //
    29  // * Redistributions in binary form must reproduce the above
    30  // copyright notice, this list of conditions and the
    31  // following disclaimer in the documentation and/or other
    32  // materials provided with the distribution.
    33  //
    34  // * Neither the name of the Webbit nor the names of
    35  // its contributors may be used to endorse or promote products
    36  // derived from this software without specific prior written
    37  // permission.
    38  //
    39  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
    40  // CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
    41  // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
    42  // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    43  // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
    44  // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
    45  // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    46  // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
    47  // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
    48  // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    49  // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    50  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
    51  // OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    52  // POSSIBILITY OF SUCH DAMAGE.
    53
    54  package org.jboss.netty.handler.codec.http.websocketx;
    55
    56  import java.nio.ByteBuffer;
    57
    58  import org.jboss.netty.buffer.ChannelBuffer;
    59  import org.jboss.netty.buffer.ChannelBuffers;
    60  import org.jboss.netty.channel.Channel;
    61  import org.jboss.netty.channel.ChannelHandlerContext;
    62  import org.jboss.netty.handler.codec.frame.TooLongFrameException;
    63  import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
    64  import org.jboss.netty.logging.InternalLogger;
    65  import org.jboss.netty.logging.InternalLoggerFactory;
    66
    67  /**
    68   * <p>
    69   * Encodes a web socket frame into wire protocol version 8 format. This code was forked from <a
    70   * href="https://github.com/joewalnes/webbit">webbit</a> and modified.
    71   * </p>
    72   */
    73  public class WebSocket08FrameEncoder extends OneToOneEncoder {
    74
    75      private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocket08FrameEncoder.class);
    76
    77      private static final byte OPCODE_CONT = 0x0;
    78      private static final byte OPCODE_TEXT = 0x1;
    79      private static final byte OPCODE_BINARY = 0x2;
    80      private static final byte OPCODE_CLOSE = 0x8;
    81      private static final byte OPCODE_PING = 0x9;
    82      private static final byte OPCODE_PONG = 0xA;
    83
    84      private final boolean maskPayload;
    85
    86      /**
    87       * Constructor
    88       *
    89       * @param maskPayload
    90       *            Web socket clients must set this to true to mask payload. Server implementations must set this to
    91       *            false.
    92       */
    93      public WebSocket08FrameEncoder(boolean maskPayload) {
    94          this.maskPayload = maskPayload;
    95      }
    96
    97      @Override
    98      protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
    99
    100         byte[] mask;
    101
    102         if (msg instanceof WebSocketFrame) {
    103             WebSocketFrame frame = (WebSocketFrame) msg;
    104             ChannelBuffer data = frame.getBinaryData();
    105             if (data == null) {
    106                 data = ChannelBuffers.EMPTY_BUFFER;
    107             }
    108
    109             byte opcode;
    110             if (frame instanceof TextWebSocketFrame) {
    111                 opcode = OPCODE_TEXT;
    112             } else if (frame instanceof PingWebSocketFrame) {
    113                 opcode = OPCODE_PING;
    114             } else if (frame instanceof PongWebSocketFrame) {
    115                 opcode = OPCODE_PONG;
    116             } else if (frame instanceof CloseWebSocketFrame) {
    117                 opcode = OPCODE_CLOSE;
    118             } else if (frame instanceof BinaryWebSocketFrame) {
    119                 opcode = OPCODE_BINARY;
    120             } else if (frame instanceof ContinuationWebSocketFrame) {
    121                 opcode = OPCODE_CONT;
    122             } else {
    123                 throw new UnsupportedOperationException("Cannot encode frame of type: " + frame.getClass().getName());
    124             }
    125
    126             int length = data.readableBytes();
    127
    128             if (logger.isDebugEnabled()) {
    129                 logger.debug("Encoding WebSocket Frame opCode=" + opcode + " length=" + length);
    130             }
    131
    132             int b0 = 0;
    133             if (frame.isFinalFragment()) {
    134                 b0 |= 1 << 7;
    135             }
    136             b0 |= frame.getRsv() % 8 << 4;
    137             b0 |= opcode % 128;
    138
    139             ChannelBuffer header;
    140             ChannelBuffer body;
    141
    142             if (opcode == OPCODE_PING && length > 125) {
    143                 throw new TooLongFrameException("invalid payload for PING (payload length must be <= 125, was "
    144                         + length);
    145             }
    146
    147             int maskLength = maskPayload ? 4 : 0;
    148             if (length <= 125) {
    149                 header = ChannelBuffers.buffer(2 + maskLength);
    150                 header.writeByte(b0);
    151                 byte b = (byte) (maskPayload ? 0x80 | (byte) length : (byte) length);
    152                 header.writeByte(b);
    153             } else if (length <= 0xFFFF) {
    154                 header = ChannelBuffers.buffer(4 + maskLength);
    155                 header.writeByte(b0);
    156                 header.writeByte(maskPayload ? 0xFE : 126);
    157                 header.writeByte(length >>> 8 & 0xFF);
    158                 header.writeByte(length & 0xFF);
    159             } else {
    160                 header = ChannelBuffers.buffer(10 + maskLength);
    161                 header.writeByte(b0);
    162                 header.writeByte(maskPayload ? 0xFF : 127);
    163                 header.writeLong(length);
    164             }
    165
    166             // Write payload
    167             if (maskPayload) {
    168                 Integer random = (int) (Math.random() * Integer.MAX_VALUE);
    169                 mask = ByteBuffer.allocate(4).putInt(random).array();
    170                 header.writeBytes(mask);
    171
    172                 body = ChannelBuffers.buffer(length);
    173                 int counter = 0;
    174                 while (data.readableBytes() > 0) {
    175                     byte byteData = data.readByte();
    176                     body.writeByte(byteData ^ mask[+counter++ % 4]);
    177                 }
    178             } else {
    179                 body = data;
    180             }
    181             return ChannelBuffers.wrappedBuffer(header, body);
    182         }
    183
    184         // If not websocket, then just return the message
    185         return msg;
    186     }
    187
    188 }