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); } } }
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); } } } }
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())); } } }
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; }
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; }
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); } }
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; }
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; }
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; }