com.datastax.driver.core.Frame Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cassandra-driver-core Show documentation
Show all versions of cassandra-driver-core Show documentation
A driver for Apache Cassandra 1.2+ that works exclusively with the Cassandra Query Language version 3
(CQL3) and Cassandra's binary protocol.
/*
* Copyright DataStax, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.datastax.driver.core;
import com.datastax.driver.core.exceptions.DriverInternalError;
import com.datastax.driver.core.exceptions.FrameTooLongException;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.TooLongFrameException;
import java.util.EnumSet;
import java.util.List;
/**
* A frame for the CQL binary protocol.
*
* Each frame contains a fixed size header (8 bytes for V1 and V2, 9 bytes for V3 and V4)
* followed by a variable size body. The content of the body depends on the header opcode value (the
* body can in particular be empty for some opcode values).
*
*
The protocol distinguishes 2 types of frames: requests and responses. Requests are those
* frames sent by the clients to the server, response are the ones sent by the server. Note however
* that the protocol supports server pushes (events) so responses does not necessarily come right
* after a client request.
*
*
Frames for protocol versions 1+2 are defined as:
*
*
*
*
* 0 8 16 24 32
* +---------+---------+---------+---------+
* | version | flags | stream | opcode |
* +---------+---------+---------+---------+
* | length |
* +---------+---------+---------+---------+
* | |
* . ... body ... .
* . .
* . .
* +---------------------------------------- *
*
*
* Frames for protocol versions 3+4 are defined as:
*
*
*
*
* 0 8 16 24 32 40
* +---------+---------+---------+---------+---------+
* | version | flags | stream | opcode |
* +---------+---------+---------+---------+---------+
* | length |
* +---------+---------+---------+---------+
* | |
* . ... body ... .
* . .
* . .
* +----------------------------------------
*
*
* @see "https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v1.spec"
* @see "https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v2.spec"
* @see "https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v3.spec"
* @see "https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v4.spec"
*/
class Frame {
final Header header;
final ByteBuf body;
Frame(Header header, ByteBuf body) {
this.header = header;
this.body = body;
}
private static Frame create(ByteBuf fullFrame) {
Header header = Header.decode(fullFrame);
assert header.bodyLength == fullFrame.readableBytes();
return new Frame(header, fullFrame);
}
private static int readStreamId(ByteBuf fullFrame, ProtocolVersion version) {
switch (version) {
case V1:
case V2:
return fullFrame.readByte();
case V3:
case V4:
case V5:
return fullFrame.readShort();
default:
throw version.unsupported();
}
}
static Frame create(
ProtocolVersion version, int opcode, int streamId, EnumSet flags, ByteBuf body) {
Header header = new Header(version, flags, streamId, opcode, body.readableBytes());
return new Frame(header, body);
}
static class Header {
final ProtocolVersion version;
final EnumSet flags;
final int streamId;
final int opcode;
final int bodyLength;
private Header(ProtocolVersion version, int flags, int streamId, int opcode, int bodyLength) {
this(version, Flag.deserialize(flags), streamId, opcode, bodyLength);
}
Header(ProtocolVersion version, EnumSet flags, int streamId, int opcode, int bodyLength) {
this.version = version;
this.flags = flags;
this.streamId = streamId;
this.opcode = opcode;
this.bodyLength = bodyLength;
}
Header withNewBodyLength(int newBodyLength) {
return new Header(version, flags, streamId, opcode, newBodyLength);
}
/**
* Return the expected frame header length in bytes according to the protocol version in use.
*
* @param version the protocol version in use
* @return the expected frame header length in bytes
*/
static int lengthFor(ProtocolVersion version) {
switch (version) {
case V1:
case V2:
return 8;
case V3:
case V4:
case V5:
return 9;
default:
throw version.unsupported();
}
}
public void encodeInto(ByteBuf destination) {
// Don't bother with the direction, we only send requests.
destination.writeByte(version.toInt());
destination.writeByte(Flag.serialize(flags));
switch (version) {
case V1:
case V2:
destination.writeByte(streamId);
break;
case V3:
case V4:
case V5:
destination.writeShort(streamId);
break;
default:
throw version.unsupported();
}
destination.writeByte(opcode);
destination.writeInt(bodyLength);
}
static Header decode(ByteBuf buffer) {
assert buffer.readableBytes() >= 1
: String.format("Frame too short (%d bytes)", buffer.readableBytes());
int versionBytes = buffer.readByte();
// version first byte is the "direction" of the frame (request or response)
ProtocolVersion version = ProtocolVersion.fromInt(versionBytes & 0x7F);
int hdrLen = Header.lengthFor(version);
assert buffer.readableBytes() >= (hdrLen - 1)
: String.format("Frame too short (%d bytes)", buffer.readableBytes());
int flags = buffer.readByte();
int streamId = readStreamId(buffer, version);
int opcode = buffer.readByte();
int length = buffer.readInt();
return new Header(version, flags, streamId, opcode, length);
}
enum Flag {
// The order of that enum matters!!
COMPRESSED,
TRACING,
CUSTOM_PAYLOAD,
WARNING,
USE_BETA;
static EnumSet deserialize(int flags) {
EnumSet set = EnumSet.noneOf(Flag.class);
Flag[] values = Flag.values();
for (int n = 0; n < 8; n++) {
if ((flags & (1 << n)) != 0) set.add(values[n]);
}
return set;
}
static int serialize(EnumSet flags) {
int i = 0;
for (Flag flag : flags) i |= 1 << flag.ordinal();
return i;
}
}
}
Frame with(ByteBuf newBody) {
return new Frame(header.withNewBodyLength(newBody.readableBytes()), newBody);
}
static final class Decoder extends ByteToMessageDecoder {
private DecoderForStreamIdSize decoder;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List