Пример #1
0
  protected void handleClose(WebSocketFragment aFragment) {
    // parse close message
    boolean hasInvalidUtf8 = false;

    try {
      byte[] data = aFragment.getPayloadData();
      if (data != null) {
        if (data.length >= 2) {
          closeStatus = WebSocketUtil.convertBytesToShort(data, 0);
          if (data.length > 2) {
            closeMessage =
                convertFromBytesToString(WebSocketUtil.copySubArray(data, 2, data.length - 2));
          }
        }
      }
    } catch (CharacterCodingException e) {
      logger.error("An error occurred while decoding from UTF8 to get text in close message.", e);
      sendErrorToObserver(e);
      hasInvalidUtf8 = true;
    }

    // actually close
    if (isClosing()) {
      closeSocket();
    } else {
      setClosing(true);
      if (hasInvalidUtf8) {
        close(WebSocketCloseStatusInvalidUtf8, null);
      } else {
        close(0, null);
      }
    }
  }
Пример #2
0
  protected void sendMessage(byte[] aMessage, MessageOpCode aOpCode) {
    if (!isClosing()) {
      int messageLength = aMessage.length;
      if (messageLength <= getMaxPayloadSize()) {
        // create and send fragment
        WebSocketFragment fragment = new WebSocketFragment(aOpCode, true, sendWithMask(), aMessage);
        sendMessage(fragment);
      } else {
        List<WebSocketFragment> fragments = new ArrayList<WebSocketFragment>();
        int fragmentCount = messageLength / getMaxPayloadSize();
        if (messageLength % getMaxPayloadSize() > 0) {
          fragmentCount++;
        }

        // build fragments
        for (int i = 0; i < fragmentCount; i++) {
          WebSocketFragment fragment = null;
          int fragmentLength = getMaxPayloadSize();
          if (i == 0) {
            fragment =
                new WebSocketFragment(
                    aOpCode,
                    false,
                    sendWithMask(),
                    WebSocketUtil.copySubArray(aMessage, i * getMaxPayloadSize(), fragmentLength));
          } else if (i == fragmentCount - 1) {
            fragmentLength = messageLength % getMaxPayloadSize();
            if (fragmentLength == 0) {
              fragmentLength = getMaxPayloadSize();
            }
            fragment =
                new WebSocketFragment(
                    MessageOpCode.CONTINUATION,
                    true,
                    sendWithMask(),
                    WebSocketUtil.copySubArray(aMessage, i * getMaxPayloadSize(), fragmentLength));
          } else {
            fragment =
                new WebSocketFragment(
                    MessageOpCode.CONTINUATION,
                    false,
                    sendWithMask(),
                    WebSocketUtil.copySubArray(aMessage, i * getMaxPayloadSize(), fragmentLength));
          }
          fragments.add(fragment);
        }
        // send fragments
        for (WebSocketFragment fragment : fragments) {
          sendMessage(fragment);
        }
      }
    }
  }
Пример #3
0
  protected void handleMessageData(byte[] aData) {
    // grab last fragment, use if not complete
    WebSocketFragment fragment = pendingFragments.peek();
    if (fragment == null || fragment.isValid()) {
      // assign web socket fragment since the last one was complete
      fragment = new WebSocketFragment(aData);

      pendingFragments.offer(fragment);
    } else if (fragment != null) {
      fragment.appendFragment(aData);
      if (fragment.canBeParsed()) {
        fragment.parseContent();
      }
    }

    // if we have a complete fragment, handle it
    if (fragment.isValid()) {
      // handle complete fragment
      handleCompleteFragment(fragment);

      // if we have extra data, handle it
      if (aData.length > fragment.getMessageLength()) {
        handleMessageData(
            WebSocketUtil.copySubArray(
                aData, fragment.getMessageLength(), aData.length - fragment.getMessageLength()));
      }
    }
  }
Пример #4
0
  public static byte[] mask(int aMask, byte[] aData, int aStart, int aLength) {
    if (aData != null) {
      // init
      byte[] results = new byte[aLength];

      // get mask
      byte[] maskBytes = WebSocketUtil.convertIntToBytes(aMask);

      // loop through mask data, masking
      int end = aStart + aLength;
      byte current;
      int index = aStart;
      if (end > aData.length) {
        end = aData.length;
      }
      int m = 0;
      while (index < end) {
        // set current byte
        current = aData[index];

        // mask
        current ^= maskBytes[m++ % 4];

        // append result & continue
        results[index - aStart] = current;
        index++;
      }

      return results;
    }

    return null;
  }
  /**
   * Handle the web socket handshake for the web socket specification <a href=
   * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">HyBi versions 13-17</a>.
   * Versions 13-17 share the same wire protocol.
   *
   * <p>Browser request to the server:
   *
   * <pre>
   * GET /chat HTTP/1.1
   * Host: server.example.com
   * Upgrade: websocket
   * Connection: Upgrade
   * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
   * Sec-WebSocket-Origin: http://example.com
   * Sec-WebSocket-Protocol: chat, superchat
   * Sec-WebSocket-Version: 13
   * </pre>
   *
   * <p>Server response:
   *
   * <pre>
   * HTTP/1.1 101 Switching Protocols
   * Upgrade: websocket
   * Connection: Upgrade
   * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
   * Sec-WebSocket-Protocol: chat
   * </pre>
   *
   * @param channel Channel
   * @param req HTTP request
   */
  @Override
  public ChannelFuture handshake(Channel channel, HttpRequest req) {

    if (logger.isDebugEnabled()) {
      logger.debug(String.format("Channel %s WS Version 13 server handshake", channel.getId()));
    }

    HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS);

    String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
    if (key == null) {
      throw new WebSocketHandshakeException("not a WebSocket request: missing key");
    }
    String acceptSeed = key + WEBSOCKET_13_ACCEPT_GUID;
    byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
    String accept = WebSocketUtil.base64(sha1);

    if (logger.isDebugEnabled()) {
      logger.debug(
          String.format("WS Version 13 Server Handshake key: %s. Response: %s.", key, accept));
    }

    res.setStatus(HttpResponseStatus.SWITCHING_PROTOCOLS);
    res.addHeader(Names.UPGRADE, WEBSOCKET.toLowerCase());
    res.addHeader(Names.CONNECTION, Names.UPGRADE);
    res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept);
    String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
    if (protocol != null) {
      res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, selectSubprotocol(protocol));
    }

    ChannelFuture future = channel.write(res);

    // Upgrade the connection and send the handshake response.
    ChannelPipeline p = channel.getPipeline();
    if (p.get(HttpChunkAggregator.class) != null) {
      p.remove(HttpChunkAggregator.class);
    }

    p.replace(
        HttpRequestDecoder.class,
        "wsdecoder",
        new WebSocket13FrameDecoder(true, allowExtensions, this.getMaxFramePayloadLength()));
    p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket13FrameEncoder(false));

    return future;
  }
Пример #6
0
 public void parseContent() {
   if (getFragment() != null && getFragment().length >= getPayloadStart() + getPayloadLength()) {
     // set payload
     if (hasMask()) {
       setPayloadData(
           WebSocketFragment.unmask(
               getMask(), getFragment(), getPayloadStart(), getPayloadLength()));
     } else {
       setPayloadData(
           WebSocketUtil.copySubArray(getFragment(), getPayloadStart(), getPayloadLength()));
     }
   }
 }
  /**
   * Handle the web socket handshake for the web socket specification <a href=
   * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">HyBi versions 13-17</a>.
   * Versions 13-17 share the same wire protocol.
   *
   * <p>Browser request to the server:
   *
   * <pre>
   * GET /chat HTTP/1.1
   * Host: server.example.com
   * Upgrade: websocket
   * Connection: Upgrade
   * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
   * Sec-WebSocket-Origin: http://example.com
   * Sec-WebSocket-Protocol: chat, superchat
   * Sec-WebSocket-Version: 13
   * </pre>
   *
   * <p>Server response:
   *
   * <pre>
   * HTTP/1.1 101 Switching Protocols
   * Upgrade: websocket
   * Connection: Upgrade
   * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
   * Sec-WebSocket-Protocol: chat
   * </pre>
   */
  @Override
  protected FullHttpResponse newHandshakeResponse(FullHttpRequest req, HttpHeaders headers) {
    FullHttpResponse res =
        new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS);
    if (headers != null) {
      res.headers().add(headers);
    }

    CharSequence key = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_KEY);
    if (key == null) {
      throw new WebSocketHandshakeException("not a WebSocket request: missing key");
    }
    String acceptSeed = key + WEBSOCKET_13_ACCEPT_GUID;
    byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
    String accept = WebSocketUtil.base64(sha1);

    if (logger.isDebugEnabled()) {
      logger.debug("WebSocket version 13 server handshake key: {}, response: {}", key, accept);
    }

    res.headers().add(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET);
    res.headers().add(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE);
    res.headers().add(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT, accept);
    String subprotocols = req.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL);
    if (subprotocols != null) {
      String selectedSubprotocol = selectSubprotocol(subprotocols);
      if (selectedSubprotocol == null) {
        if (logger.isDebugEnabled()) {
          logger.debug("Requested subprotocol(s) not supported: {}", subprotocols);
        }
      } else {
        res.headers().add(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL, selectedSubprotocol);
      }
    }
    return res;
  }
Пример #8
0
 protected void sendClose(int aStatus, String aMessage) {
   try {
     setClosing(true);
     System.out.println("Closing with (" + aStatus + "): " + aMessage);
     System.out.println("Message Bytes:");
     WebSocketFragment message =
         new WebSocketFragment(
             MessageOpCode.CLOSE, true, sendWithMask(), getCloseMessageBytes(aStatus, aMessage));
     byte[] messageBytes = message.getFragment();
     System.out.println("Fragment:");
     WebSocketUtil.printBytes(messageBytes);
     sendMessage(message);
   } catch (CharacterCodingException e) {
     logger.error("An error occurred while encoding UTF8 to send text in close message.", e);
     sendErrorToObserver(e);
     close(WebSocketCloseStatusInvalidUtf8, null);
   }
 }
Пример #9
0
  protected byte[] getCloseMessageBytes(Integer aStatus, String aMessage)
      throws CharacterCodingException {
    byte[] results = null;

    if (aMessage != null) {
      byte[] temp = convertFromStringToBytes(aMessage);
      if (temp.length + 2 <= getMaxPayloadSize()) {
        byte[] statusBytes =
            new byte[] {
              new Integer(aStatus % 0x100).byteValue(), new Integer(aStatus / 0x100).byteValue()
            };
        results = WebSocketUtil.appendArray(statusBytes, temp);
      }
    } else if (aStatus != null && aStatus > 0) {
      results =
          new byte[] {
            new Integer(aStatus % 0x100).byteValue(), new Integer(aStatus / 0x100).byteValue()
          };
    }

    return results;
  }
Пример #10
0
  public boolean parseHeader(byte[] aData, int aOffset) {
    // get header data bits
    int bufferLength = 14;

    byte[] data = aData;

    // do we have an existing fragment to work with
    if (getFragment() != null) {
      if (getFragment().length >= bufferLength) {
        data = getFragment();
      } else {
        byte[] both;
        if ((aData != null ? aData.length : 0) - aOffset >= bufferLength - getFragment().length) {
          both =
              WebSocketUtil.appendPartialArray(
                  getFragment(), aData, aOffset, bufferLength - getFragment().length);
        } else {
          both = WebSocketUtil.appendArray(getFragment(), aData);
        }
        data = both;
      }
    }

    if (data != null && data.length - aOffset < bufferLength) {
      bufferLength = data.length - aOffset;
    }
    if (bufferLength < 0 || data == null) {
      return false;
    }
    byte[] buffer = WebSocketUtil.copySubArray(data, 0, bufferLength);

    // determine opcode
    if (bufferLength > 0) {
      int index = 0;
      setFinal((buffer[index] & 0x80) != 0);
      setRSV1((buffer[index] & 0x40) != 0);
      setRSV2((buffer[index] & 0x20) != 0);
      setRSV3((buffer[index] & 0x20) != 0);
      setOpCode(buffer[index++] & 0x0F);

      // handle data depending on opcode
      switch (getOpCode()) {
        case TEXT:
          setPayloadType(PayloadType.TEXT);
          break;
        case BINARY:
          setPayloadType(PayloadType.BINARY);
          break;
      }

      // handle content, if any
      if (bufferLength > 1) {
        // do we have a mask
        boolean hasMask = (buffer[index] & 0x80) != 0;

        // get payload length
        Long dataLength = new Integer(buffer[index++] & 0x7F).longValue();
        if (dataLength == 126) {
          // exit if we are missing bytes
          if (bufferLength < 4) {
            return false;
          }

          dataLength = new Integer(WebSocketUtil.convertBytesToShort(buffer, index)).longValue();
          index += 2;
        } else if (dataLength == 127) {
          // exit if we are missing bytes
          if (bufferLength < 10) {
            return false;
          }

          dataLength = WebSocketUtil.convertBytesToLong(buffer, index);
          index += 8;
        }

        // if applicable, set mask value
        if (hasMask) {
          // exit if we are missing bytes
          if (bufferLength < index + 4) {
            return false;
          }

          // grab mask
          setMask(WebSocketUtil.convertBytesToInt(buffer, index));
          index += 4;
        }

        payloadStart = index;
        if (dataLength > Integer.MAX_VALUE) {
          throw new IllegalArgumentException(
              "Implementation does not support payload lengths in excess of "
                  + Integer.MAX_VALUE
                  + ": "
                  + dataLength);
        }
        payloadLength = dataLength.intValue();
        return true;
      }
    }

    return false;
  }
Пример #11
0
 public void appendFragment(byte[] aData) {
   fragment = WebSocketUtil.appendArray(fragment, aData);
 }
  /**
   * Handle the web socket handshake for the web socket specification <a href=
   * "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00">HyBi version 0</a> and
   * lower. This standard is really a rehash of <a
   * href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76" >hixie-76</a> and <a
   * href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75" >hixie-75</a>.
   *
   * <p>Browser request to the server:
   *
   * <pre>
   * GET /demo HTTP/1.1
   * Upgrade: WebSocket
   * Connection: Upgrade
   * Host: example.com
   * Origin: http://example.com
   * Sec-WebSocket-Protocol: chat, sample
   * Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5
   * Sec-WebSocket-Key2: 12998 5 Y3 1  .P00
   *
   * ^n:ds[4U
   * </pre>
   *
   * <p>Server response:
   *
   * <pre>
   * HTTP/1.1 101 WebSocket Protocol Handshake
   * Upgrade: WebSocket
   * Connection: Upgrade
   * Sec-WebSocket-Origin: http://example.com
   * Sec-WebSocket-Location: ws://example.com/demo
   * Sec-WebSocket-Protocol: sample
   *
   * 8jKS'y:G*Co,Wxa-
   * </pre>
   *
   * @param channel Channel
   * @param req HTTP request
   */
  @Override
  public ChannelFuture handshake(Channel channel, FullHttpRequest req, ChannelPromise promise) {

    if (logger.isDebugEnabled()) {
      logger.debug(String.format("Channel %s WS Version 00 server handshake", channel.id()));
    }

    // Serve the WebSocket handshake request.
    if (!Values.UPGRADE.equalsIgnoreCase(req.headers().get(CONNECTION))
        || !WEBSOCKET.equalsIgnoreCase(req.headers().get(Names.UPGRADE))) {
      throw new WebSocketHandshakeException("not a WebSocket handshake request: missing upgrade");
    }

    // Hixie 75 does not contain these headers while Hixie 76 does
    boolean isHixie76 =
        req.headers().contains(SEC_WEBSOCKET_KEY1) && req.headers().contains(SEC_WEBSOCKET_KEY2);

    // Create the WebSocket handshake response.
    FullHttpResponse res =
        new DefaultFullHttpResponse(
            HTTP_1_1,
            new HttpResponseStatus(
                101, isHixie76 ? "WebSocket Protocol Handshake" : "Web Socket Protocol Handshake"));
    res.headers().add(Names.UPGRADE, WEBSOCKET);
    res.headers().add(CONNECTION, Values.UPGRADE);

    // Fill in the headers and contents depending on handshake getMethod.
    if (isHixie76) {
      // New handshake getMethod with a challenge:
      res.headers().add(SEC_WEBSOCKET_ORIGIN, req.headers().get(ORIGIN));
      res.headers().add(SEC_WEBSOCKET_LOCATION, uri());
      String subprotocols = req.headers().get(SEC_WEBSOCKET_PROTOCOL);
      if (subprotocols != null) {
        String selectedSubprotocol = selectSubprotocol(subprotocols);
        if (selectedSubprotocol == null) {
          throw new WebSocketHandshakeException(
              "Requested subprotocol(s) not supported: " + subprotocols);
        } else {
          res.headers().add(SEC_WEBSOCKET_PROTOCOL, selectedSubprotocol);
          setSelectedSubprotocol(selectedSubprotocol);
        }
      }

      // Calculate the answer of the challenge.
      String key1 = req.headers().get(SEC_WEBSOCKET_KEY1);
      String key2 = req.headers().get(SEC_WEBSOCKET_KEY2);
      int a =
          (int)
              (Long.parseLong(BEGINNING_DIGIT.matcher(key1).replaceAll(""))
                  / BEGINNING_SPACE.matcher(key1).replaceAll("").length());
      int b =
          (int)
              (Long.parseLong(BEGINNING_DIGIT.matcher(key2).replaceAll(""))
                  / BEGINNING_SPACE.matcher(key2).replaceAll("").length());
      long c = req.data().readLong();
      ByteBuf input = Unpooled.buffer(16);
      input.writeInt(a);
      input.writeInt(b);
      input.writeLong(c);
      res.data().writeBytes(WebSocketUtil.md5(input.array()));
    } else {
      // Old Hixie 75 handshake getMethod with no challenge:
      res.headers().add(WEBSOCKET_ORIGIN, req.headers().get(ORIGIN));
      res.headers().add(WEBSOCKET_LOCATION, uri());
      String protocol = req.headers().get(WEBSOCKET_PROTOCOL);
      if (protocol != null) {
        res.headers().add(WEBSOCKET_PROTOCOL, selectSubprotocol(protocol));
      }
    }

    // Upgrade the connection and send the handshake response.
    channel.write(res, promise);
    promise.addListener(
        new ChannelFutureListener() {
          @Override
          public void operationComplete(ChannelFuture future) {
            ChannelPipeline p = future.channel().pipeline();
            if (p.get(HttpObjectAggregator.class) != null) {
              p.remove(HttpObjectAggregator.class);
            }
            p.replaceAndForward(
                HttpRequestDecoder.class,
                "wsdecoder",
                new WebSocket00FrameDecoder(maxFramePayloadLength()));

            p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket00FrameEncoder());
          }
        });

    return promise;
  }