private void writeControlFrame(int opcode, ByteString payload) throws IOException { if (writerClosed) throw new IOException("closed"); int length = payload.size(); if (length > PAYLOAD_BYTE_MAX) { throw new IllegalArgumentException( "Payload size must be less than or equal to " + PAYLOAD_BYTE_MAX); } int b0 = B0_FLAG_FIN | opcode; sinkBuffer.writeByte(b0); int b1 = length; if (isClient) { b1 |= B1_FLAG_MASK; sinkBuffer.writeByte(b1); random.nextBytes(maskKey); sinkBuffer.write(maskKey); if (length > 0) { long payloadStart = sinkBuffer.size(); sinkBuffer.write(payload); sinkBuffer.readAndWriteUnsafe(maskCursor); maskCursor.seek(payloadStart); toggleMask(maskCursor, maskKey); maskCursor.close(); } } else { sinkBuffer.writeByte(b1); sinkBuffer.write(payload); } sink.flush(); }
@Override public void write(Buffer source, long byteCount) throws IOException { if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); if (source.size() < byteCount) { throw new IllegalArgumentException("size=" + source.size() + " byteCount=" + byteCount); } if (byteCount == 0) return; source.readUnsafe(cursor); try { long remaining = byteCount; for (int length = cursor.seek(0); remaining > 0 && length > 0; length = cursor.next()) { int toIntercept = (int) Math.min(length, remaining); intercept(cursor.data, cursor.start, toIntercept); remaining -= toIntercept; } } finally { cursor.close(); } super.write(source, byteCount); }
WebSocketWriter(boolean isClient, BufferedSink sink, Random random) { if (sink == null) throw new NullPointerException("sink == null"); if (random == null) throw new NullPointerException("random == null"); this.isClient = isClient; this.sink = sink; this.sinkBuffer = sink.buffer(); this.random = random; // Masks are only a concern for client writers. maskKey = isClient ? new byte[4] : null; maskCursor = isClient ? new Buffer.UnsafeCursor() : null; }
private void writeControlFrame(int opcode, ByteString payload) throws IOException { if (writerClosed) throw new IOException("closed"); int length = payload.size(); if (length > PAYLOAD_BYTE_MAX) { throw new IllegalArgumentException( "Payload size must be less than or equal to " + PAYLOAD_BYTE_MAX); } int b0 = B0_FLAG_FIN | opcode; sinkBuffer.writeByte(b0); int b1 = length; if (isClient) { b1 |= B1_FLAG_MASK; sinkBuffer.writeByte(b1); random.nextBytes(maskKey); sinkBuffer.write(maskKey); if (length > 0) { long payloadStart = sinkBuffer.size(); sinkBuffer.write(payload); sinkBuffer.readAndWriteUnsafe(maskCursor); maskCursor.seek(payloadStart); toggleMask(maskCursor, maskKey); maskCursor.close(); } } else { sinkBuffer.writeByte(b1); sinkBuffer.write(payload); } sink.flush(); }
/** * Reads a message body into across one or more frames. Control frames that occur between * fragments will be processed. If the message payload is masked this will unmask as it's being * processed. */ private void readMessage() throws IOException { while (true) { if (closed) throw new IOException("closed"); if (frameLength > 0) { source.readFully(messageFrameBuffer, frameLength); if (!isClient) { messageFrameBuffer.readAndWriteUnsafe(maskCursor); maskCursor.seek(messageFrameBuffer.size() - frameLength); toggleMask(maskCursor, maskKey); maskCursor.close(); } } if (isFinalFrame) break; // We are exhausted and have no continuations. readUntilNonControlFrame(); if (opcode != OPCODE_CONTINUATION) { throw new ProtocolException("Expected continuation opcode. Got: " + toHexString(opcode)); } } } }
WebSocketWriter(boolean isClient, BufferedSink sink, Random random) { if (sink == null) throw new NullPointerException("sink == null"); if (random == null) throw new NullPointerException("random == null"); this.isClient = isClient; this.sink = sink; this.sinkBuffer = sink.buffer(); this.random = random; // Masks are only a concern for client writers. maskKey = isClient ? new byte[4] : null; maskCursor = isClient ? new Buffer.UnsafeCursor() : null; }
WebSocketReader(boolean isClient, BufferedSource source, FrameCallback frameCallback) { if (source == null) throw new NullPointerException("source == null"); if (frameCallback == null) throw new NullPointerException("frameCallback == null"); this.isClient = isClient; this.source = source; this.frameCallback = frameCallback; // Masks are only a concern for server writers. maskKey = isClient ? null : new byte[4]; maskCursor = isClient ? null : new Buffer.UnsafeCursor(); }
static void toggleMask(Buffer.UnsafeCursor cursor, byte[] key) { int keyIndex = 0; int keyLength = key.length; do { byte[] buffer = cursor.data; for (int i = cursor.start, end = cursor.end; i < end; i++, keyIndex++) { keyIndex %= keyLength; // Reassign to prevent overflow breaking counter. buffer[i] = (byte) (buffer[i] ^ key[keyIndex]); } } while (cursor.next() != -1); }
/** * Reads a message body into across one or more frames. Control frames that occur between * fragments will be processed. If the message payload is masked this will unmask as it's being * processed. */ private void readMessage() throws IOException { while (true) { if (closed) throw new IOException("closed"); if (frameLength > 0) { source.readFully(messageFrameBuffer, frameLength); if (!isClient) { messageFrameBuffer.readAndWriteUnsafe(maskCursor); maskCursor.seek(messageFrameBuffer.size() - frameLength); toggleMask(maskCursor, maskKey); maskCursor.close(); } } if (isFinalFrame) break; // We are exhausted and have no continuations. readUntilNonControlFrame(); if (opcode != OPCODE_CONTINUATION) { throw new ProtocolException("Expected continuation opcode. Got: " + toHexString(opcode)); } } } }
@Override public long read(Buffer sink, long byteCount) throws IOException { if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); if (byteCount == 0) return 0; long result = super.read(sink, byteCount); if (result == -1L) return result; sink.readUnsafe(cursor); try { long remaining = result; for (int length = cursor.seek(sink.size() - result); remaining > 0 && length > 0; length = cursor.next()) { int toIntercept = (int) Math.min(length, remaining); intercept(cursor.data, cursor.start, toIntercept); remaining -= toIntercept; } } finally { cursor.close(); } return result; }
@Test public void segmentBySegmentNavigation() throws Exception { Buffer buffer = bufferFactory.newBuffer(); UnsafeCursor cursor = buffer.readUnsafe(); assertEquals(-1, cursor.offset); try { long lastOffset = cursor.offset; while (cursor.next() != -1L) { assertTrue(cursor.offset > lastOffset); lastOffset = cursor.offset; } assertEquals(buffer.size(), cursor.offset); assertNull(cursor.data); assertEquals(-1, cursor.start); assertEquals(-1, cursor.end); } finally { cursor.close(); } }
@Test public void resizeEnlargeMovesCursorToOldSize() throws Exception { Buffer buffer = bufferFactory.newBuffer(); long originalSize = buffer.size(); Buffer expected = deepCopy(buffer); expected.writeUtf8("a"); try (UnsafeCursor cursor = buffer.readAndWriteUnsafe()) { cursor.seek(buffer.size() / 2); assertEquals(originalSize, buffer.size()); cursor.resizeBuffer(originalSize + 1); assertEquals(originalSize, cursor.offset); assertNotNull(cursor.data); assertNotEquals(-1, cursor.start); assertEquals(cursor.start + 1, cursor.end); cursor.data[cursor.start] = 'a'; } assertEquals(expected, buffer); }
static void toggleMask(Buffer.UnsafeCursor cursor, byte[] key) { int keyIndex = 0; int keyLength = key.length; do { byte[] buffer = cursor.data; for (int i = cursor.start, end = cursor.end; i < end; i++, keyIndex++) { keyIndex %= keyLength; // Reassign to prevent overflow breaking counter. buffer[i] = (byte) (buffer[i] ^ key[keyIndex]); } } while (cursor.next() != -1); }
@Test public void acquireAndRelease() throws Exception { Buffer buffer = bufferFactory.newBuffer(); UnsafeCursor cursor = new UnsafeCursor(); // Nothing initialized before acquire. assertEquals(-1, cursor.offset); assertNull(cursor.data); assertEquals(-1, cursor.start); assertEquals(-1, cursor.end); buffer.readUnsafe(cursor); cursor.close(); // Nothing initialized after close. assertEquals(-1, cursor.offset); assertNull(cursor.data); assertEquals(-1, cursor.start); assertEquals(-1, cursor.end); }
@Test public void enlarge() throws Exception { Buffer buffer = bufferFactory.newBuffer(); long originalSize = buffer.size(); Buffer expected = deepCopy(buffer); expected.writeUtf8("abc"); try (UnsafeCursor cursor = buffer.readAndWriteUnsafe()) { assertEquals(originalSize, cursor.resizeBuffer(originalSize + 3)); cursor.seek(originalSize); cursor.data[cursor.start] = 'a'; cursor.seek(originalSize + 1); cursor.data[cursor.start] = 'b'; cursor.seek(originalSize + 2); cursor.data[cursor.start] = 'c'; } assertEquals(expected, buffer); }
WebSocketReader(boolean isClient, BufferedSource source, FrameCallback frameCallback) { if (source == null) throw new NullPointerException("source == null"); if (frameCallback == null) throw new NullPointerException("frameCallback == null"); this.isClient = isClient; this.source = source; this.frameCallback = frameCallback; // Masks are only a concern for server writers. maskKey = isClient ? null : new byte[4]; maskCursor = isClient ? null : new Buffer.UnsafeCursor(); }