handle) {
+ super(handle);
+ }
+
+ /**
+ * Creates the {@code CANCEL} frame.
+ *
+ * @param byteBuf the {@link ByteBuf} representing the frame
+ * @return the {@code CANCEL} frame
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static CancelFrame createCancelFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return RECYCLER.get().setByteBuf(byteBuf.retain());
+ }
+
+ /**
+ * Creates the {@code CANCEL} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @return the {@code CANCEL} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ */
+ public static CancelFrame createCancelFrame(ByteBufAllocator byteBufAllocator) {
+ ByteBuf byteBuf = createFrameTypeAndFlags(byteBufAllocator, CANCEL);
+
+ return RECYCLER.get().setByteBuf(byteBuf);
+ }
+
+ @Override
+ public String toString() {
+ return "CancelFrame{} ";
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/DataFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/DataFrame.java
new file mode 100644
index 000000000..4f46ec744
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/DataFrame.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import io.netty.buffer.ByteBuf;
+import java.util.Objects;
+import java.util.function.Function;
+
+/** An RSocket frame that only contains data. */
+public interface DataFrame extends Frame {
+
+ /**
+ * Returns the data as a UTF-8 {@link String}.
+ *
+ * @return the data as a UTF-8 {@link String}
+ */
+ default String getDataAsUtf8() {
+ return getUnsafeData().toString(UTF_8);
+ }
+
+ /**
+ * Returns the length of the data in the frame.
+ *
+ * @return the length of the data in the frame
+ */
+ default int getDataLength() {
+ return getUnsafeData().readableBytes();
+ }
+
+ /**
+ * Returns the data directly.
+ *
+ * Note: this data will be outside of the {@link Frame}'s lifecycle and may be released
+ * at any time. It is highly recommended that you {@link ByteBuf#retain()} the data if you store
+ * it.
+ *
+ * @return the data directly
+ * @see #mapData(Function)
+ */
+ ByteBuf getUnsafeData();
+
+ /**
+ * Exposes the data for mapping to a different type.
+ *
+ * @param function the function to transform the data to a different type
+ * @param the different type
+ * @return the data mapped to a different type
+ * @throws NullPointerException if {@code function} is {@code null}
+ */
+ default T mapData(Function function) {
+ Objects.requireNonNull(function, "function must not be null");
+
+ return function.apply(getUnsafeData());
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/ErrorFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/ErrorFrame.java
new file mode 100644
index 000000000..092119cef
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/ErrorFrame.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.framing.FrameType.ERROR;
+import static io.rsocket.util.RecyclerFactory.createRecycler;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.util.Recycler;
+import io.netty.util.Recycler.Handle;
+import java.util.Objects;
+import reactor.util.annotation.Nullable;
+
+/**
+ * An RSocket {@code ERROR} frame.
+ *
+ * @see Error
+ * Frame
+ */
+public final class ErrorFrame extends AbstractRecyclableDataFrame {
+
+ private static final int OFFSET_ERROR_CODE = FRAME_TYPE_AND_FLAGS_BYTES;
+
+ private static final int OFFSET_DATA = OFFSET_ERROR_CODE + Integer.BYTES;
+
+ private static final Recycler RECYCLER = createRecycler(ErrorFrame::new);
+
+ private ErrorFrame(Handle handle) {
+ super(handle);
+ }
+
+ /**
+ * Creates the {@code ERROR} frame.
+ *
+ * @param byteBuf the {@link ByteBuf} representing the frame
+ * @return the {@code ERROR} frame.
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static ErrorFrame createErrorFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return RECYCLER.get().setByteBuf(byteBuf.retain());
+ }
+
+ /**
+ * Creates the {@code ERROR} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param errorCode the error code
+ * @param data the error data
+ * @return the {@code ERROR} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ */
+ public static ErrorFrame createErrorFrame(
+ ByteBufAllocator byteBufAllocator, int errorCode, @Nullable String data) {
+
+ return createErrorFrame(byteBufAllocator, errorCode, getUtf8AsByteBuf(data));
+ }
+
+ /**
+ * Creates the {@code ERROR} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param errorCode the error code
+ * @param data the error data
+ * @return the {@code ERROR} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ */
+ public static ErrorFrame createErrorFrame(
+ ByteBufAllocator byteBufAllocator, int errorCode, @Nullable ByteBuf data) {
+
+ ByteBuf byteBuf = createFrameTypeAndFlags(byteBufAllocator, ERROR).writeInt(errorCode);
+ byteBuf = appendData(byteBuf, data);
+
+ return RECYCLER.get().setByteBuf(byteBuf);
+ }
+
+ /**
+ * Returns the error code.
+ *
+ * @return the error code
+ */
+ public int getErrorCode() {
+ return getByteBuf().getInt(OFFSET_ERROR_CODE);
+ }
+
+ @Override
+ public ByteBuf getUnsafeData() {
+ return getData(OFFSET_DATA);
+ }
+
+ @Override
+ public String toString() {
+ return "ErrorFrame{"
+ + "errorCode="
+ + getErrorCode()
+ + ", data="
+ + mapData(ByteBufUtil::hexDump)
+ + '}';
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/ErrorType.java b/rsocket-core/src/main/java/io/rsocket/framing/ErrorType.java
new file mode 100644
index 000000000..46afa2a1a
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/ErrorType.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+/**
+ * The types of {@link Error} that can be set.
+ *
+ * @see Error
+ * Codes
+ */
+public final class ErrorType {
+
+ /**
+ * Application layer logic generating a Reactive Streams onError event. Stream ID MUST be > 0.
+ */
+ public static final int APPLICATION_ERROR = 0x00000201;;
+
+ /**
+ * The Responder canceled the request but may have started processing it (similar to REJECTED but
+ * doesn't guarantee lack of side-effects). Stream ID MUST be > 0.
+ */
+ public static final int CANCELED = 0x00000203;
+
+ /**
+ * The connection is being terminated. Stream ID MUST be 0. Sender or Receiver of this frame MUST
+ * wait for outstanding streams to terminate before closing the connection. New requests MAY not
+ * be accepted.
+ */
+ public static final int CONNECTION_CLOSE = 0x00000102;
+
+ /**
+ * The connection is being terminated. Stream ID MUST be 0. Sender or Receiver of this frame MAY
+ * close the connection immediately without waiting for outstanding streams to terminate.
+ */
+ public static final int CONNECTION_ERROR = 0x00000101;
+
+ /** The request is invalid. Stream ID MUST be > 0. */
+ public static final int INVALID = 0x00000204;
+
+ /**
+ * The Setup frame is invalid for the server (it could be that the client is too recent for the
+ * old server). Stream ID MUST be 0.
+ */
+ public static final int INVALID_SETUP = 0x00000001;
+
+ /**
+ * Despite being a valid request, the Responder decided to reject it. The Responder guarantees
+ * that it didn't process the request. The reason for the rejection is explained in the Error Data
+ * section. Stream ID MUST be > 0.
+ */
+ public static final int REJECTED = 0x00000202;
+
+ /**
+ * The server rejected the resume, it can specify the reason in the payload. Stream ID MUST be 0.
+ */
+ public static final int REJECTED_RESUME = 0x00000004;
+
+ /**
+ * The server rejected the setup, it can specify the reason in the payload. Stream ID MUST be 0.
+ */
+ public static final int REJECTED_SETUP = 0x00000003;
+
+ /** Reserved. */
+ public static final int RESERVED = 0x00000000;
+
+ /** Reserved for Extension Use. */
+ public static final int RESERVED_FOR_EXTENSION = 0xFFFFFFFF;
+
+ /**
+ * Some (or all) of the parameters specified by the client are unsupported by the server. Stream
+ * ID MUST be 0.
+ */
+ public static final int UNSUPPORTED_SETUP = 0x00000002;
+
+ private ErrorType() {}
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/ExtensionFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/ExtensionFrame.java
new file mode 100644
index 000000000..c2c1881f4
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/ExtensionFrame.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.framing.FrameType.EXT;
+import static io.rsocket.util.RecyclerFactory.createRecycler;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.util.Recycler;
+import io.netty.util.Recycler.Handle;
+import java.util.Objects;
+import reactor.util.annotation.Nullable;
+
+/**
+ * An RSocket {@code EXT} frame.
+ *
+ * @see Extension
+ * Frame
+ */
+public final class ExtensionFrame extends AbstractRecyclableMetadataAndDataFrame {
+
+ private static final int FLAG_IGNORE = 1 << 9;
+
+ private static final int OFFSET_EXTENDED_TYPE = FRAME_TYPE_AND_FLAGS_BYTES;
+
+ private static final int OFFSET_METADATA_LENGTH = OFFSET_EXTENDED_TYPE + Integer.BYTES;
+
+ private static final Recycler RECYCLER = createRecycler(ExtensionFrame::new);
+
+ private ExtensionFrame(Handle handle) {
+ super(handle);
+ }
+
+ /**
+ * Creates the {@code EXT} frame.
+ *
+ * @param byteBuf the {@link ByteBuf} representing the frame
+ * @return the {@code EXT} frame.
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static ExtensionFrame createExtensionFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return RECYCLER.get().setByteBuf(byteBuf.retain());
+ }
+
+ /**
+ * Creates the {@code EXT} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param ignore whether to set the Ignore flag
+ * @param extendedType the type of the extended frame
+ * @param metadata the {@code metadata}
+ * @param data the {@code data}
+ * @return the {@code EXT} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ */
+ public static ExtensionFrame createExtensionFrame(
+ ByteBufAllocator byteBufAllocator,
+ boolean ignore,
+ int extendedType,
+ @Nullable String metadata,
+ @Nullable String data) {
+
+ return createExtensionFrame(
+ byteBufAllocator, ignore, extendedType, getUtf8AsByteBuf(metadata), getUtf8AsByteBuf(data));
+ }
+
+ /**
+ * Creates the {@code EXT} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param ignore whether to set the Ignore flag
+ * @param extendedType the type of the extended frame
+ * @param metadata the {@code metadata}
+ * @param data the {@code data}
+ * @return the {@code EXT} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ */
+ public static ExtensionFrame createExtensionFrame(
+ ByteBufAllocator byteBufAllocator,
+ boolean ignore,
+ int extendedType,
+ @Nullable ByteBuf metadata,
+ @Nullable ByteBuf data) {
+
+ ByteBuf byteBuf = createFrameTypeAndFlags(byteBufAllocator, EXT);
+
+ if (ignore) {
+ byteBuf = setFlag(byteBuf, FLAG_IGNORE);
+ }
+
+ byteBuf = byteBuf.writeInt(extendedType);
+ byteBuf = appendMetadata(byteBufAllocator, byteBuf, metadata);
+ byteBuf = appendData(byteBuf, data);
+
+ return RECYCLER.get().setByteBuf(byteBuf);
+ }
+
+ /**
+ * Returns the extended type.
+ *
+ * @return the extended type
+ */
+ public int getExtendedType() {
+ return getByteBuf().getInt(OFFSET_EXTENDED_TYPE);
+ }
+
+ @Override
+ public ByteBuf getUnsafeData() {
+ return getData(OFFSET_METADATA_LENGTH);
+ }
+
+ @Override
+ public @Nullable ByteBuf getUnsafeMetadata() {
+ return getMetadata(OFFSET_METADATA_LENGTH);
+ }
+
+ /**
+ * Returns whether the Ignore flag is set.
+ *
+ * @return whether the Ignore flag is set
+ */
+ public boolean isIgnoreFlagSet() {
+ return isFlagSet(FLAG_IGNORE);
+ }
+
+ @Override
+ public String toString() {
+ return "ExtensionFrame{"
+ + "ignore="
+ + isIgnoreFlagSet()
+ + ", extendedType="
+ + getExtendedType()
+ + ", metadata="
+ + mapMetadata(ByteBufUtil::hexDump)
+ + ", data="
+ + mapData(ByteBufUtil::hexDump)
+ + '}';
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/FragmentableFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/FragmentableFrame.java
new file mode 100644
index 000000000..d8b083951
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/FragmentableFrame.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import reactor.util.annotation.Nullable;
+
+/** An RSocket frame that is fragmentable */
+public interface FragmentableFrame extends MetadataAndDataFrame {
+
+ /**
+ * Generates the fragment for this frame.
+ *
+ * @param byteBufAllocator the {@link ByteBufAllocator} to use
+ * @param metadata the metadata
+ * @param data the data
+ * @return the fragment for this frame
+ * @throws NullPointerException if {@code ByteBufAllocator} is {@code null}
+ */
+ FragmentableFrame createFragment(
+ ByteBufAllocator byteBufAllocator, @Nullable ByteBuf metadata, @Nullable ByteBuf data);
+
+ /**
+ * Generates the non-fragment for this frame.
+ *
+ * @param byteBufAllocator the {@link ByteBufAllocator} to use
+ * @param metadata the metadata
+ * @param data the data
+ * @return the non-fragment for this frame
+ * @throws NullPointerException if {@code ByteBufAllocator} is {@code null}
+ */
+ FragmentableFrame createNonFragment(
+ ByteBufAllocator byteBufAllocator, @Nullable ByteBuf metadata, @Nullable ByteBuf data);
+
+ /**
+ * Returns whether the Follows flag is set.
+ *
+ * @return whether the Follows flag is set
+ */
+ boolean isFollowsFlagSet();
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/Frame.java b/rsocket-core/src/main/java/io/rsocket/framing/Frame.java
new file mode 100644
index 000000000..9823db942
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/Frame.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import io.netty.buffer.ByteBuf;
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import reactor.core.Disposable;
+
+/**
+ * An RSocket frame.
+ *
+ * @see Framing
+ */
+public interface Frame extends Disposable {
+
+ /** The shift length for the frame type. */
+ int FRAME_TYPE_SHIFT = Short.SIZE - FrameType.ENCODED_SIZE;
+
+ /**
+ * Exposes the {@code Frame} as a {@link ByteBuf} for consumption.
+ *
+ * @param consumer the {@link Consumer} to consume the {@code Frame} as a {@link ByteBuf}
+ * @throws NullPointerException if {@code consumer} is {@code null}
+ */
+ void consumeFrame(Consumer consumer);
+
+ /**
+ * Exposes the {@code Frame} as a {@link ByteBuf} for mapping to a different type.
+ *
+ * @param function the {@link Function} to transform the {@code Frame} as a {@link ByteBuf} to a
+ * different type
+ * @param the different type
+ * @return the {@code Frame} as a {@link ByteBuf} mapped to a different type
+ * @throws NullPointerException if {@code function} is {@code null}
+ */
+ T mapFrame(Function function);
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/FrameFactory.java b/rsocket-core/src/main/java/io/rsocket/framing/FrameFactory.java
new file mode 100644
index 000000000..9631f09b0
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/FrameFactory.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.framing.CancelFrame.createCancelFrame;
+import static io.rsocket.framing.ErrorFrame.createErrorFrame;
+import static io.rsocket.framing.ExtensionFrame.createExtensionFrame;
+import static io.rsocket.framing.Frame.FRAME_TYPE_SHIFT;
+import static io.rsocket.framing.KeepaliveFrame.createKeepaliveFrame;
+import static io.rsocket.framing.LeaseFrame.createLeaseFrame;
+import static io.rsocket.framing.MetadataPushFrame.createMetadataPushFrame;
+import static io.rsocket.framing.PayloadFrame.createPayloadFrame;
+import static io.rsocket.framing.RequestChannelFrame.createRequestChannelFrame;
+import static io.rsocket.framing.RequestFireAndForgetFrame.createRequestFireAndForgetFrame;
+import static io.rsocket.framing.RequestNFrame.createRequestNFrame;
+import static io.rsocket.framing.RequestResponseFrame.createRequestResponseFrame;
+import static io.rsocket.framing.RequestStreamFrame.createRequestStreamFrame;
+import static io.rsocket.framing.ResumeFrame.createResumeFrame;
+import static io.rsocket.framing.ResumeOkFrame.createResumeOkFrame;
+import static io.rsocket.framing.SetupFrame.createSetupFrame;
+
+import io.netty.buffer.ByteBuf;
+import java.util.Objects;
+
+/**
+ * A factory for creating RSocket frames from {@link ByteBuf}s.
+ *
+ * @see Frame
+ * Types
+ */
+public final class FrameFactory {
+
+ private FrameFactory() {}
+
+ /**
+ * Returns a strongly-type {@link Frame} created from a {@link ByteBuf}.
+ *
+ * @param byteBuf the {@code ByteBuf} to create the {@link Frame} from
+ * @return the strongly-typed {@link Frame}
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static Frame createFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ FrameType frameType = getFrameType(byteBuf);
+ switch (frameType) {
+ case SETUP:
+ return createSetupFrame(byteBuf);
+ case LEASE:
+ return createLeaseFrame(byteBuf);
+ case KEEPALIVE:
+ return createKeepaliveFrame(byteBuf);
+ case REQUEST_RESPONSE:
+ return createRequestResponseFrame(byteBuf);
+ case REQUEST_FNF:
+ return createRequestFireAndForgetFrame(byteBuf);
+ case REQUEST_STREAM:
+ return createRequestStreamFrame(byteBuf);
+ case REQUEST_CHANNEL:
+ return createRequestChannelFrame(byteBuf);
+ case REQUEST_N:
+ return createRequestNFrame(byteBuf);
+ case CANCEL:
+ return createCancelFrame(byteBuf);
+ case PAYLOAD:
+ return createPayloadFrame(byteBuf);
+ case ERROR:
+ return createErrorFrame(byteBuf);
+ case METADATA_PUSH:
+ return createMetadataPushFrame(byteBuf);
+ case RESUME:
+ return createResumeFrame(byteBuf);
+ case RESUME_OK:
+ return createResumeOkFrame(byteBuf);
+ case EXT:
+ return createExtensionFrame(byteBuf);
+ default:
+ throw new IllegalArgumentException(
+ String.format("Cannot create frame for type %s", frameType));
+ }
+ };
+
+ private static FrameType getFrameType(ByteBuf byteBuf) {
+ int encodedType = byteBuf.getUnsignedShort(0) >> FRAME_TYPE_SHIFT;
+ return FrameType.fromEncodedType(encodedType);
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/FrameLengthFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/FrameLengthFrame.java
new file mode 100644
index 000000000..26073eb10
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/FrameLengthFrame.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.framing.LengthUtils.getLengthAsUnsignedMedium;
+import static io.rsocket.util.NumberUtils.MEDIUM_BYTES;
+import static io.rsocket.util.RecyclerFactory.createRecycler;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.netty.util.Recycler;
+import io.netty.util.Recycler.Handle;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * An RSocket frame with a frame length.
+ *
+ * @see Framing
+ * Format
+ */
+public final class FrameLengthFrame extends AbstractRecyclableFrame {
+
+ private static final int FRAME_LENGTH_BYTES = MEDIUM_BYTES;
+
+ private static final Recycler RECYCLER = createRecycler(FrameLengthFrame::new);
+
+ private FrameLengthFrame(Handle handle) {
+ super(handle);
+ }
+
+ /**
+ * Creates the frame with a frame length.
+ *
+ * @param byteBuf the {@link ByteBuf} representing the frame
+ * @return the frame with a frame length
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static FrameLengthFrame createFrameLengthFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return RECYCLER.get().setByteBuf(byteBuf.retain());
+ }
+
+ /**
+ * Creates the frame with a frame length.
+ *
+ * @param byteBufAllocator the {@link ByteBufAllocator} to use
+ * @param frame the frame to prepend the frame length to
+ * @return the frame with a frame length
+ * @throws NullPointerException if {@code byteBufAllocator} or {@code frame} is {@code null}
+ */
+ public static FrameLengthFrame createFrameLengthFrame(
+ ByteBufAllocator byteBufAllocator, Frame frame) {
+
+ Objects.requireNonNull(byteBufAllocator, "byteBufAllocator must not be null");
+ Objects.requireNonNull(frame, "frame must not be null");
+
+ ByteBuf frameLengthByteBuf =
+ frame.mapFrame(
+ frameByteBuf -> {
+ ByteBuf byteBuf =
+ byteBufAllocator
+ .buffer(FRAME_LENGTH_BYTES)
+ .writeMedium(getLengthAsUnsignedMedium(frameByteBuf));
+
+ return Unpooled.wrappedBuffer(byteBuf, frameByteBuf.retain());
+ });
+
+ return RECYCLER.get().setByteBuf(frameLengthByteBuf);
+ }
+
+ /**
+ * Returns the frame length.
+ *
+ * @return the frame length
+ */
+ public int getFrameLength() {
+ return getByteBuf().getUnsignedMedium(0);
+ }
+
+ /**
+ * Exposes the {@link Frame} without the frame length as a {@link ByteBuf} for mapping to a
+ * different type.
+ *
+ * @param function the function to transform the {@link Frame} without the frame length as a
+ * {@link ByteBuf} to a different type
+ * @param the different type
+ * @return the {@link Frame} without the frame length as a {@link ByteBuf} mapped to a different
+ * type
+ * @throws NullPointerException if {@code function} is {@code null}
+ */
+ public T mapFrameWithoutFrameLength(Function function) {
+ Objects.requireNonNull(function, "function must not be null");
+
+ return function.apply(getFrameWithoutFrameLength());
+ }
+
+ @Override
+ public String toString() {
+ return "FrameLengthFrame{"
+ + "frameLength="
+ + getFrameLength()
+ + ", frameWithoutFrameLength="
+ + mapFrameWithoutFrameLength(ByteBufUtil::hexDump)
+ + '}';
+ }
+
+ private ByteBuf getFrameWithoutFrameLength() {
+ ByteBuf byteBuf = getByteBuf();
+ return byteBuf.slice(FRAME_LENGTH_BYTES, byteBuf.readableBytes() - FRAME_LENGTH_BYTES);
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/FrameType.java b/rsocket-core/src/main/java/io/rsocket/framing/FrameType.java
new file mode 100644
index 000000000..2feaf9436
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/FrameType.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import io.rsocket.Frame;
+import java.util.Arrays;
+import java.util.EnumSet;
+
+/**
+ * Types of {@link Frame} that can be sent.
+ *
+ * @see Frame
+ * Types
+ */
+public enum FrameType {
+
+ /** Reserved. */
+ RESERVED(0x00),
+
+ // CONNECTION
+
+ /**
+ * Sent by client to initiate protocol processing.
+ *
+ * @see Setup
+ * Frame
+ */
+ SETUP(0x01, EnumSet.of(Flags.CAN_HAVE_DATA, Flags.CAN_HAVE_METADATA)),
+
+ /**
+ * Sent by Responder to grant the ability to send requests.
+ *
+ * @see Lease
+ * Frame
+ */
+ LEASE(0x02, EnumSet.of(Flags.CAN_HAVE_METADATA)),
+
+ /**
+ * Connection keepalive.
+ *
+ * @see Keepalive
+ * Frame
+ */
+ KEEPALIVE(0x03, EnumSet.of(Flags.CAN_HAVE_DATA)),
+
+ // START REQUEST
+
+ /**
+ * Request single response.
+ *
+ * @see Request
+ * Response Frame
+ */
+ REQUEST_RESPONSE(
+ 0x04,
+ EnumSet.of(
+ Flags.CAN_HAVE_DATA,
+ Flags.CAN_HAVE_METADATA,
+ Flags.IS_FRAGMENTABLE,
+ Flags.IS_REQUEST_TYPE)),
+
+ /**
+ * A single one-way message.
+ *
+ * @see Request
+ * Fire-and-Forget Frame
+ */
+ REQUEST_FNF(
+ 0x05,
+ EnumSet.of(
+ Flags.CAN_HAVE_DATA,
+ Flags.CAN_HAVE_METADATA,
+ Flags.IS_FRAGMENTABLE,
+ Flags.IS_REQUEST_TYPE)),
+
+ /**
+ * Request a completable stream.
+ *
+ * @see Request
+ * Stream Frame
+ */
+ REQUEST_STREAM(
+ 0x06,
+ EnumSet.of(
+ Flags.CAN_HAVE_METADATA,
+ Flags.CAN_HAVE_DATA,
+ Flags.HAS_INITIAL_REQUEST_N,
+ Flags.IS_FRAGMENTABLE,
+ Flags.IS_REQUEST_TYPE)),
+
+ /**
+ * Request a completable stream in both directions.
+ *
+ * @see Request
+ * Channel Frame
+ */
+ REQUEST_CHANNEL(
+ 0x07,
+ EnumSet.of(
+ Flags.CAN_HAVE_METADATA,
+ Flags.CAN_HAVE_DATA,
+ Flags.HAS_INITIAL_REQUEST_N,
+ Flags.IS_FRAGMENTABLE,
+ Flags.IS_REQUEST_TYPE)),
+
+ // DURING REQUEST
+
+ /**
+ * Request N more items with Reactive Streams semantics.
+ *
+ * @see RequestN
+ * Frame
+ */
+ REQUEST_N(0x08),
+
+ /**
+ * Cancel outstanding request.
+ *
+ * @see Cancel
+ * Frame
+ */
+ CANCEL(0x09),
+
+ // RESPONSE
+
+ /**
+ * Payload on a stream. For example, response to a request, or message on a channel.
+ *
+ * @see Payload
+ * Frame
+ */
+ PAYLOAD(0x0A, EnumSet.of(Flags.CAN_HAVE_DATA, Flags.CAN_HAVE_METADATA, Flags.IS_FRAGMENTABLE)),
+
+ /**
+ * Error at connection or application level.
+ *
+ * @see Error
+ * Frame
+ */
+ ERROR(0x0B, EnumSet.of(Flags.CAN_HAVE_DATA)),
+
+ // METADATA
+
+ /**
+ * Asynchronous Metadata frame.
+ *
+ * @see Metadata
+ * Push Frame
+ */
+ METADATA_PUSH(0x0C, EnumSet.of(Flags.CAN_HAVE_METADATA)),
+
+ // RESUMPTION
+
+ /**
+ * Replaces SETUP for Resuming Operation (optional).
+ *
+ * @see Resume
+ * Frame
+ */
+ RESUME(0x0D),
+
+ /**
+ * Sent in response to a RESUME if resuming operation possible (optional).
+ *
+ * @see Resume OK
+ * Frame
+ */
+ RESUME_OK(0x0E),
+
+ // SYNTHETIC PAYLOAD TYPES
+
+ /** A {@link #PAYLOAD} frame with {@code NEXT} flag set. */
+ NEXT(0xA0, EnumSet.of(Flags.CAN_HAVE_DATA, Flags.CAN_HAVE_METADATA, Flags.IS_FRAGMENTABLE)),
+
+ /** A {@link #PAYLOAD} frame with {@code COMPLETE} flag set. */
+ COMPLETE(0xB0),
+
+ /** A {@link #PAYLOAD} frame with {@code NEXT} and {@code COMPLETE} flags set. */
+ NEXT_COMPLETE(
+ 0xC0, EnumSet.of(Flags.CAN_HAVE_DATA, Flags.CAN_HAVE_METADATA, Flags.IS_FRAGMENTABLE)),
+
+ /**
+ * Used To Extend more frame types as well as extensions.
+ *
+ * @see Extension
+ * Frame
+ */
+ EXT(0x3F, EnumSet.of(Flags.CAN_HAVE_DATA, Flags.CAN_HAVE_METADATA));
+
+ /** The size of the encoded frame type */
+ static final int ENCODED_SIZE = 6;
+
+ private static final FrameType[] FRAME_TYPES_BY_ENCODED_TYPE;
+
+ static {
+ FRAME_TYPES_BY_ENCODED_TYPE = new FrameType[getMaximumEncodedType() + 1];
+
+ for (FrameType frameType : values()) {
+ FRAME_TYPES_BY_ENCODED_TYPE[frameType.encodedType] = frameType;
+ }
+ }
+
+ private final int encodedType;
+
+ private final EnumSet flags;
+
+ FrameType(int encodedType) {
+ this(encodedType, EnumSet.noneOf(Flags.class));
+ }
+
+ FrameType(int encodedType, EnumSet flags) {
+ this.encodedType = encodedType;
+ this.flags = flags;
+ }
+
+ /**
+ * Returns the {@code FrameType} that matches the specified {@code encodedType}.
+ *
+ * @param encodedType the encoded type
+ * @return the {@code FrameType} that matches the specified {@code encodedType}
+ */
+ public static FrameType fromEncodedType(int encodedType) {
+ FrameType frameType = FRAME_TYPES_BY_ENCODED_TYPE[encodedType];
+
+ if (frameType == null) {
+ throw new IllegalArgumentException(String.format("Frame type %d is unknown", encodedType));
+ }
+
+ return frameType;
+ }
+
+ /**
+ * Whether the frame type can have data.
+ *
+ * @return whether the frame type can have data
+ */
+ public boolean canHaveData() {
+ return this.flags.contains(Flags.CAN_HAVE_DATA);
+ }
+
+ /**
+ * Whether the frame type can have metadata
+ *
+ * @return whether the frame type can have metadata
+ */
+ public boolean canHaveMetadata() {
+ return this.flags.contains(Flags.CAN_HAVE_METADATA);
+ }
+
+ /**
+ * Returns the encoded type.
+ *
+ * @return the encoded type
+ */
+ public int getEncodedType() {
+ return encodedType;
+ }
+
+ /**
+ * Whether the frame type starts with an initial {@code requestN}.
+ *
+ * @return wether the frame type starts with an initial {@code requestN}
+ */
+ public boolean hasInitialRequestN() {
+ return this.flags.contains(Flags.HAS_INITIAL_REQUEST_N);
+ }
+
+ /**
+ * Whether the frame type is fragmentable.
+ *
+ * @return whether the frame type is fragmentable
+ */
+ public boolean isFragmentable() {
+ return this.flags.contains(Flags.IS_FRAGMENTABLE);
+ }
+
+ /**
+ * Whether the frame type is a request type.
+ *
+ * @return whether the frame type is a request type
+ */
+ public boolean isRequestType() {
+ return this.flags.contains(Flags.IS_REQUEST_TYPE);
+ }
+
+ private static int getMaximumEncodedType() {
+ return Arrays.stream(values()).mapToInt(frameType -> frameType.encodedType).max().orElse(0);
+ }
+
+ private enum Flags {
+ CAN_HAVE_DATA,
+
+ CAN_HAVE_METADATA,
+
+ HAS_INITIAL_REQUEST_N,
+
+ IS_FRAGMENTABLE,
+
+ IS_REQUEST_TYPE;
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/KeepaliveFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/KeepaliveFrame.java
new file mode 100644
index 000000000..0dc90f1f6
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/KeepaliveFrame.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.framing.FrameType.KEEPALIVE;
+import static io.rsocket.util.RecyclerFactory.createRecycler;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.util.Recycler;
+import io.netty.util.Recycler.Handle;
+import java.util.Objects;
+import reactor.util.annotation.Nullable;
+
+/**
+ * An RSocket {@code KEEPALIVE} frame.
+ *
+ * @see Keeplive
+ * Frame
+ */
+public final class KeepaliveFrame extends AbstractRecyclableDataFrame {
+
+ private static final int FLAG_RESPOND = 1 << 7;
+
+ private static final int OFFSET_LAST_RECEIVED_POSITION = FRAME_TYPE_AND_FLAGS_BYTES;
+
+ private static final int OFFSET_DATA = OFFSET_LAST_RECEIVED_POSITION + Long.BYTES;
+
+ private static final Recycler RECYCLER = createRecycler(KeepaliveFrame::new);
+
+ private KeepaliveFrame(Handle handle) {
+ super(handle);
+ }
+
+ /**
+ * Creates the {@code KEEPALIVE} frame.
+ *
+ * @param byteBuf the {@link ByteBuf} representing the frame
+ * @return the {@code KEEPALIVE} frame.
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static KeepaliveFrame createKeepaliveFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return RECYCLER.get().setByteBuf(byteBuf.retain());
+ }
+
+ /**
+ * Creates the {@code KEEPALIVE} frame.
+ *
+ * @param byteBufAllocator the {@link ByteBufAllocator} to use
+ * @param respond whether to set the Respond flag
+ * @param lastReceivedPosition the last received position
+ * @param data the frame data
+ * @return the {@code KEEPALIVE} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ */
+ public static KeepaliveFrame createKeepaliveFrame(
+ ByteBufAllocator byteBufAllocator,
+ boolean respond,
+ long lastReceivedPosition,
+ @Nullable ByteBuf data) {
+
+ ByteBuf byteBuf =
+ createFrameTypeAndFlags(byteBufAllocator, KEEPALIVE).writeLong(lastReceivedPosition);
+
+ if (respond) {
+ byteBuf = setFlag(byteBuf, FLAG_RESPOND);
+ }
+
+ byteBuf = appendData(byteBuf, data);
+
+ return RECYCLER.get().setByteBuf(byteBuf);
+ }
+
+ /**
+ * Returns the last received position.
+ *
+ * @return the last received position
+ */
+ public long getLastReceivedPosition() {
+ return getByteBuf().getLong(OFFSET_LAST_RECEIVED_POSITION);
+ }
+
+ @Override
+ public ByteBuf getUnsafeData() {
+ return getData(OFFSET_DATA);
+ }
+
+ /**
+ * Returns whether the respond flag is set.
+ *
+ * @return whether the respond flag is set
+ */
+ public boolean isRespondFlagSet() {
+ return isFlagSet(FLAG_RESPOND);
+ }
+
+ @Override
+ public String toString() {
+ return "KeepaliveFrame{"
+ + "respond="
+ + isRespondFlagSet()
+ + ", lastReceivedPosition="
+ + getLastReceivedPosition()
+ + ", data="
+ + mapData(ByteBufUtil::hexDump)
+ + '}';
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/LeaseFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/LeaseFrame.java
new file mode 100644
index 000000000..763fbc60f
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/LeaseFrame.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.framing.FrameType.LEASE;
+import static io.rsocket.util.RecyclerFactory.createRecycler;
+import static java.lang.Math.toIntExact;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.util.Recycler;
+import io.netty.util.Recycler.Handle;
+import io.rsocket.util.NumberUtils;
+import java.time.Duration;
+import java.util.Objects;
+import reactor.util.annotation.Nullable;
+
+/**
+ * An RSocket {@code LEASE} frame.
+ *
+ * @see Lease
+ * Frame
+ */
+public final class LeaseFrame extends AbstractRecyclableMetadataFrame {
+
+ private static final int OFFSET_TIME_TO_LIVE = FRAME_TYPE_AND_FLAGS_BYTES;
+
+ private static final int OFFSET_NUMBER_OF_REQUESTS = OFFSET_TIME_TO_LIVE + Integer.BYTES;
+
+ private static final int OFFSET_METADATA = OFFSET_NUMBER_OF_REQUESTS + Integer.BYTES;
+
+ private static final Recycler RECYCLER = createRecycler(LeaseFrame::new);
+
+ private LeaseFrame(Handle handle) {
+ super(handle);
+ }
+
+ /**
+ * Creates the {@code LEASE} frame.
+ *
+ * @param byteBuf the {@link ByteBuf} representing the frame
+ * @return the {@code LEASE} frame.
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static LeaseFrame createLeaseFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return RECYCLER.get().setByteBuf(byteBuf.retain());
+ }
+
+ /**
+ * Creates the {@code LEASE} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param timeToLive the validity of lease from time of reception
+ * @param numberOfRequests the number of requests that may be sent until the next lease
+ * @param metadata the metadata
+ * @return the {@code LEASE} frame
+ * @throws IllegalArgumentException if {@code timeToLive} is not a positive duration
+ * @throws IllegalArgumentException if {@code numberOfRequests} is not positive
+ * @throws NullPointerException if {@code byteBufAllocator} or {@code timeToLive} is {@code null}
+ * @throws IllegalArgumentException if {@code timeToLive} is not a positive duration or {@code
+ * numberOfRequests} is not positive
+ */
+ public static LeaseFrame createLeaseFrame(
+ ByteBufAllocator byteBufAllocator,
+ Duration timeToLive,
+ int numberOfRequests,
+ @Nullable ByteBuf metadata) {
+
+ Objects.requireNonNull(byteBufAllocator, "byteBufAllocator must not be null");
+ Objects.requireNonNull(timeToLive, "timeToLive must not be null");
+ NumberUtils.requirePositive(timeToLive.toMillis(), "timeToLive must be a positive duration");
+ NumberUtils.requirePositive(numberOfRequests, "numberOfRequests must be positive");
+
+ ByteBuf byteBuf =
+ createFrameTypeAndFlags(byteBufAllocator, LEASE)
+ .writeInt(toIntExact(timeToLive.toMillis()))
+ .writeInt(numberOfRequests);
+
+ byteBuf = appendMetadata(byteBuf, metadata);
+
+ return RECYCLER.get().setByteBuf(byteBuf);
+ }
+
+ /**
+ * Returns the number of requests
+ *
+ * @return the number of requests
+ */
+ public int getNumberOfRequests() {
+ return getByteBuf().getInt(OFFSET_NUMBER_OF_REQUESTS);
+ }
+
+ /**
+ * Returns the time to live.
+ *
+ * @return the time to live
+ */
+ public Duration getTimeToLive() {
+ return Duration.ofMillis(getByteBuf().getInt(OFFSET_TIME_TO_LIVE));
+ }
+
+ @Override
+ public @Nullable ByteBuf getUnsafeMetadata() {
+ return getMetadata(OFFSET_METADATA);
+ }
+
+ @Override
+ public String toString() {
+ return "LeaseFrame{"
+ + "timeToLive="
+ + getTimeToLive()
+ + ", numberOfRequests="
+ + getNumberOfRequests()
+ + ", metadata="
+ + mapMetadata(ByteBufUtil::hexDump)
+ + +'}';
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/LengthUtils.java b/rsocket-core/src/main/java/io/rsocket/framing/LengthUtils.java
new file mode 100644
index 000000000..2374e8f81
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/LengthUtils.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.util.NumberUtils.requireUnsignedByte;
+import static io.rsocket.util.NumberUtils.requireUnsignedMedium;
+import static io.rsocket.util.NumberUtils.requireUnsignedShort;
+
+import io.netty.buffer.ByteBuf;
+import java.util.Objects;
+
+/** Utilities for working with {@code ByteBuf} lengths */
+final class LengthUtils {
+
+ private LengthUtils() {}
+
+ /**
+ * Returns the length of a {@link ByteBuf} as an unsigned {@code byte}.
+ *
+ * @param byteBuf the {@link ByteBuf} to get the length of
+ * @return the length of a {@link ByteBuf} as an unsigned {@code byte}
+ */
+ static int getLengthAsUnsignedByte(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return requireUnsignedByte(byteBuf.readableBytes());
+ }
+
+ /**
+ * Returns the length of a {@link ByteBuf} as an unsigned {@code medium}
+ *
+ * @param byteBuf the {@link ByteBuf} to get the length of
+ * @return the length of a {@link ByteBuf} as an unsigned {@code medium}
+ */
+ static int getLengthAsUnsignedMedium(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return requireUnsignedMedium(byteBuf.readableBytes());
+ }
+
+ /**
+ * Returns the length of a {@link ByteBuf} as an unsigned {@code short}
+ *
+ * @param byteBuf the {@link ByteBuf} to get the length of
+ * @return the length of a {@link ByteBuf} as an unsigned {@code short}
+ */
+ static int getLengthAsUnsignedShort(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return requireUnsignedShort(byteBuf.readableBytes());
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/MetadataAndDataFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/MetadataAndDataFrame.java
new file mode 100644
index 000000000..a7f904b7b
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/MetadataAndDataFrame.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+/** An RSocket frame that only metadata and data. */
+public interface MetadataAndDataFrame extends MetadataFrame, DataFrame {}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/MetadataFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/MetadataFrame.java
new file mode 100644
index 000000000..817b819c9
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/MetadataFrame.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import io.netty.buffer.ByteBuf;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+import reactor.util.annotation.Nullable;
+
+/** An RSocket frame that only contains metadata. */
+public interface MetadataFrame extends Frame {
+
+ /**
+ * Returns the metadata as a UTF-8 {@link String}. If the Metadata flag is not set, returns {@link
+ * Optional#empty()}.
+ *
+ * @return optionally, the metadata as a UTF-8 {@link String}
+ */
+ default Optional getMetadataAsUtf8() {
+ return Optional.ofNullable(getUnsafeMetadataAsUtf8());
+ }
+
+ /**
+ * Returns the length of the metadata in the frame. If the Metadata flag is not set, returns
+ * {@link Optional#empty()}.
+ *
+ * @return optionally, the length of the metadata in the frame
+ */
+ default Optional getMetadataLength() {
+ return Optional.ofNullable(getUnsafeMetadataLength());
+ }
+
+ /**
+ * Returns the metadata directly. If the Metadata flag is not set, returns {@code null}.
+ *
+ * Note: this metadata will be outside of the {@link Frame}'s lifecycle and may be
+ * released at any time. It is highly recommended that you {@link ByteBuf#retain()} the metadata
+ * if you store it.
+ *
+ * @return the metadata directly, or {@code null} if the Metadata flag is not set
+ * @see #mapMetadata(Function)
+ */
+ @Nullable
+ ByteBuf getUnsafeMetadata();
+
+ /**
+ * Returns the metadata as a UTF-8 {@link String}. If the Metadata flag is not set, returns {@code
+ * null}.
+ *
+ * @return the metadata as a UTF-8 {@link String} or {@code null} if the Metadata flag is not set.
+ * @see #getMetadataAsUtf8()
+ */
+ default @Nullable String getUnsafeMetadataAsUtf8() {
+ ByteBuf byteBuf = getUnsafeMetadata();
+ return byteBuf == null ? null : byteBuf.toString(UTF_8);
+ }
+
+ /**
+ * Returns the length of the metadata in the frame directly. If the Metadata flag is not set,
+ * returns {@code null}.
+ *
+ * @return the length of the metadata in frame directly, or {@code null} if the Metadata flag is
+ * not set
+ * @see #getMetadataLength()
+ */
+ default @Nullable Integer getUnsafeMetadataLength() {
+ ByteBuf byteBuf = getUnsafeMetadata();
+ return byteBuf == null ? null : byteBuf.readableBytes();
+ }
+
+ /**
+ * Exposes the metadata for mapping to a different type. If the Metadata flag is not set, returns
+ * {@link Optional#empty()}.
+ *
+ * @param function the function to transform the metadata to a different type
+ * @param the different type
+ * @return optionally, the metadata mapped to a different type
+ * @throws NullPointerException if {@code function} is {@code null}
+ */
+ default Optional mapMetadata(Function function) {
+ Objects.requireNonNull(function, "function must not be null");
+
+ return Optional.ofNullable(getUnsafeMetadata()).map(function);
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/MetadataPushFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/MetadataPushFrame.java
new file mode 100644
index 000000000..8b982455d
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/MetadataPushFrame.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.framing.FrameType.METADATA_PUSH;
+import static io.rsocket.util.RecyclerFactory.createRecycler;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.util.Recycler;
+import io.netty.util.Recycler.Handle;
+import java.util.Objects;
+import reactor.util.annotation.Nullable;
+
+/**
+ * An RSocket {@code METADATA_PUSH} frame.
+ *
+ * @see Metadata
+ * Push Frame
+ */
+public final class MetadataPushFrame extends AbstractRecyclableMetadataFrame {
+
+ private static final int OFFSET_METADATA = FRAME_TYPE_AND_FLAGS_BYTES;
+
+ private static final Recycler RECYCLER =
+ createRecycler(MetadataPushFrame::new);
+
+ private MetadataPushFrame(Handle handle) {
+ super(handle);
+ }
+
+ /**
+ * Creates the {@code METADATA_PUSH} frame.
+ *
+ * @param byteBuf the {@link ByteBuf} representing the frame
+ * @return the {@code METADATA_PUSH} frame.
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static MetadataPushFrame createMetadataPushFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return RECYCLER.get().setByteBuf(byteBuf.retain());
+ }
+
+ /**
+ * Creates the {@code METADATA_PUSH} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param metadata the metadata
+ * @return the {@code METADATA_PUSH} frame
+ * @throws NullPointerException if {@code byteBufAllocator} or {@code metadata} is {@code null}
+ */
+ public static MetadataPushFrame createMetadataPushFrame(
+ ByteBufAllocator byteBufAllocator, String metadata) {
+
+ return createMetadataPushFrame(
+ byteBufAllocator, getUtf8AsByteBufRequired(metadata, "metadata must not be null"));
+ }
+
+ /**
+ * Creates the {@code METADATA_PUSH} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param metadata the metadata
+ * @return the {@code METADATA_PUSH} frame
+ * @throws NullPointerException if {@code byteBufAllocator} or {@code metadata} is {@code null}
+ */
+ public static MetadataPushFrame createMetadataPushFrame(
+ ByteBufAllocator byteBufAllocator, ByteBuf metadata) {
+
+ Objects.requireNonNull(metadata, "metadata must not be null");
+
+ ByteBuf byteBuf = createFrameTypeAndFlags(byteBufAllocator, METADATA_PUSH);
+ byteBuf = appendMetadata(byteBuf, metadata);
+
+ return RECYCLER.get().setByteBuf(byteBuf);
+ }
+
+ @Override
+ public @Nullable ByteBuf getUnsafeMetadata() {
+ return getMetadata(OFFSET_METADATA);
+ }
+
+ @Override
+ public String toString() {
+ return "MetadataPushFrame{" + "metadata=" + mapMetadata(ByteBufUtil::hexDump) + '}';
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/PayloadFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/PayloadFrame.java
new file mode 100644
index 000000000..50b6e42b2
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/PayloadFrame.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.util.RecyclerFactory.createRecycler;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.util.Recycler;
+import io.netty.util.Recycler.Handle;
+import java.util.Objects;
+import reactor.util.annotation.Nullable;
+
+/**
+ * An RSocket {@code PAYLOAD} frame.
+ *
+ * @see Payload
+ * Frame
+ */
+public final class PayloadFrame extends AbstractRecyclableFragmentableFrame {
+
+ private static final int FLAG_COMPLETE = 1 << 6;
+
+ private static final int FLAG_NEXT = 1 << 5;
+
+ private static final int OFFSET_METADATA_LENGTH = FRAME_TYPE_AND_FLAGS_BYTES;
+
+ private static final Recycler RECYCLER = createRecycler(PayloadFrame::new);
+
+ private PayloadFrame(Handle handle) {
+ super(handle);
+ }
+
+ /**
+ * Creates the {@code PAYLOAD} frame.
+ *
+ * @param byteBuf the {@link ByteBuf} representing the frame
+ * @return the {@code PAYLOAD} frame.
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static PayloadFrame createPayloadFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return RECYCLER.get().setByteBuf(byteBuf.retain());
+ }
+
+ /**
+ * Creates the {@code PAYLOAD} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param follows whether to set the Follows flag
+ * @param complete respond whether to set the Complete flag
+ * @param metadata the metadata
+ * @param data the data
+ * @return the {@code PAYLOAD} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ */
+ public static PayloadFrame createPayloadFrame(
+ ByteBufAllocator byteBufAllocator,
+ boolean follows,
+ boolean complete,
+ @Nullable String metadata,
+ @Nullable String data) {
+
+ return createPayloadFrame(
+ byteBufAllocator, follows, complete, getUtf8AsByteBuf(metadata), getUtf8AsByteBuf(data));
+ }
+
+ /**
+ * Creates the {@code PAYLOAD} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param follows respond whether to set the Follows flag
+ * @param complete respond whether to set the Complete flag
+ * @param metadata the metadata
+ * @param data the data
+ * @return the {@code PAYLOAD} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ */
+ public static PayloadFrame createPayloadFrame(
+ ByteBufAllocator byteBufAllocator,
+ boolean follows,
+ boolean complete,
+ @Nullable ByteBuf metadata,
+ @Nullable ByteBuf data) {
+
+ if (!complete && (data == null)) {
+ throw new IllegalArgumentException(
+ "Payload frame must either be complete, have data, or both");
+ }
+
+ ByteBuf byteBuf = createFrameTypeAndFlags(byteBufAllocator, FrameType.PAYLOAD);
+
+ if (follows) {
+ byteBuf = setFollowsFlag(byteBuf);
+ }
+
+ if (complete) {
+ byteBuf = setFlag(byteBuf, FLAG_COMPLETE);
+ }
+
+ byteBuf = appendMetadata(byteBufAllocator, byteBuf, metadata);
+
+ if (data != null) {
+ byteBuf = setFlag(byteBuf, FLAG_NEXT);
+ }
+
+ byteBuf = appendData(byteBuf, data);
+
+ return RECYCLER.get().setByteBuf(byteBuf);
+ }
+
+ @Override
+ public PayloadFrame createFragment(
+ ByteBufAllocator byteBufAllocator, @Nullable ByteBuf metadata, @Nullable ByteBuf data) {
+
+ return createPayloadFrame(byteBufAllocator, true, isCompleteFlagSet(), metadata, data);
+ }
+
+ @Override
+ public PayloadFrame createNonFragment(
+ ByteBufAllocator byteBufAllocator, @Nullable ByteBuf metadata, @Nullable ByteBuf data) {
+
+ return createPayloadFrame(byteBufAllocator, false, isCompleteFlagSet(), metadata, data);
+ }
+
+ @Override
+ public ByteBuf getUnsafeData() {
+ return getData(OFFSET_METADATA_LENGTH);
+ }
+
+ @Override
+ public @Nullable ByteBuf getUnsafeMetadata() {
+ return getMetadata(OFFSET_METADATA_LENGTH);
+ }
+
+ /**
+ * Returns whether the Complete flag is set.
+ *
+ * @return whether the Complete flag is set
+ */
+ public boolean isCompleteFlagSet() {
+ return isFlagSet(FLAG_COMPLETE);
+ }
+
+ /**
+ * Returns whether the Next flag is set.
+ *
+ * @return whether the Next flag is set
+ */
+ public boolean isNextFlagSet() {
+ return isFlagSet(FLAG_NEXT);
+ }
+
+ @Override
+ public String toString() {
+ return "PayloadFrame{"
+ + "follows="
+ + isFollowsFlagSet()
+ + ", complete="
+ + isCompleteFlagSet()
+ + ", next="
+ + isNextFlagSet()
+ + ", metadata="
+ + mapMetadata(ByteBufUtil::hexDump)
+ + ", data="
+ + mapData(ByteBufUtil::hexDump)
+ + '}';
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/RequestChannelFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/RequestChannelFrame.java
new file mode 100644
index 000000000..f1e74bd62
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/RequestChannelFrame.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.util.RecyclerFactory.createRecycler;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.util.Recycler;
+import io.netty.util.Recycler.Handle;
+import io.rsocket.util.NumberUtils;
+import java.util.Objects;
+import reactor.util.annotation.Nullable;
+
+/**
+ * An RSocket {@code REQUEST_CHANNEL} frame.
+ *
+ * @see Request
+ * Channel Frame
+ */
+public final class RequestChannelFrame
+ extends AbstractRecyclableFragmentableFrame {
+
+ private static final int FLAG_COMPLETE = 1 << 6;
+
+ private static final int OFFSET_INITIAL_REQUEST_N = FRAME_TYPE_AND_FLAGS_BYTES;
+
+ private static final int OFFSET_METADATA_LENGTH = OFFSET_INITIAL_REQUEST_N + Integer.BYTES;
+
+ private static final Recycler RECYCLER =
+ createRecycler(RequestChannelFrame::new);
+
+ private RequestChannelFrame(Handle handle) {
+ super(handle);
+ }
+
+ /**
+ * Creates the {@code REQUEST_CHANNEL} frame.
+ *
+ * @param byteBuf the {@link ByteBuf} representing the frame
+ * @return the {@code REQUEST_CHANNEL} frame.
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static RequestChannelFrame createRequestChannelFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return RECYCLER.get().setByteBuf(byteBuf.retain());
+ }
+
+ /**
+ * Creates the {@code REQUEST_CHANNEL} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param follows whether to set the Follows flag
+ * @param complete whether to set the Complete flag
+ * @param initialRequestN the initial requestN
+ * @param metadata the metadata
+ * @param data the data
+ * @return the {@code REQUEST_CHANNEL} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ */
+ public static RequestChannelFrame createRequestChannelFrame(
+ ByteBufAllocator byteBufAllocator,
+ boolean follows,
+ boolean complete,
+ int initialRequestN,
+ @Nullable String metadata,
+ @Nullable String data) {
+
+ return createRequestChannelFrame(
+ byteBufAllocator,
+ follows,
+ complete,
+ initialRequestN,
+ getUtf8AsByteBuf(metadata),
+ getUtf8AsByteBuf(data));
+ }
+
+ /**
+ * Creates the {@code REQUEST_CHANNEL} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param follows whether to set the Follows flag
+ * @param complete whether to set the Complete flag
+ * @param initialRequestN the initial requestN
+ * @param metadata the metadata
+ * @param data the data
+ * @return the {@code REQUEST_CHANNEL} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ * @throws IllegalArgumentException if {@code initialRequestN} is not positive
+ */
+ public static RequestChannelFrame createRequestChannelFrame(
+ ByteBufAllocator byteBufAllocator,
+ boolean follows,
+ boolean complete,
+ int initialRequestN,
+ @Nullable ByteBuf metadata,
+ @Nullable ByteBuf data) {
+
+ NumberUtils.requirePositive(initialRequestN, "initialRequestN must be positive");
+
+ ByteBuf byteBuf = createFrameTypeAndFlags(byteBufAllocator, FrameType.REQUEST_CHANNEL);
+
+ if (follows) {
+ byteBuf = setFollowsFlag(byteBuf);
+ }
+
+ if (complete) {
+ byteBuf = setFlag(byteBuf, FLAG_COMPLETE);
+ }
+
+ byteBuf = byteBuf.writeInt(initialRequestN);
+ byteBuf = appendMetadata(byteBufAllocator, byteBuf, metadata);
+ byteBuf = appendData(byteBuf, data);
+
+ return RECYCLER.get().setByteBuf(byteBuf);
+ }
+
+ @Override
+ public RequestChannelFrame createFragment(
+ ByteBufAllocator byteBufAllocator, @Nullable ByteBuf metadata, @Nullable ByteBuf data) {
+
+ return createRequestChannelFrame(
+ byteBufAllocator, true, isCompleteFlagSet(), getInitialRequestN(), metadata, data);
+ }
+
+ @Override
+ public RequestChannelFrame createNonFragment(
+ ByteBufAllocator byteBufAllocator, @Nullable ByteBuf metadata, @Nullable ByteBuf data) {
+
+ return createRequestChannelFrame(
+ byteBufAllocator, false, isCompleteFlagSet(), getInitialRequestN(), metadata, data);
+ }
+
+ /**
+ * Returns the initial requestN.
+ *
+ * @return the initial requestN
+ */
+ public int getInitialRequestN() {
+ return getByteBuf().getInt(OFFSET_INITIAL_REQUEST_N);
+ }
+
+ @Override
+ public ByteBuf getUnsafeData() {
+ return getData(OFFSET_METADATA_LENGTH);
+ }
+
+ @Override
+ public @Nullable ByteBuf getUnsafeMetadata() {
+ return getMetadata(OFFSET_METADATA_LENGTH);
+ }
+
+ /**
+ * Returns whether the Complete flag is set.
+ *
+ * @return whether the Complete flag is set
+ */
+ public boolean isCompleteFlagSet() {
+ return isFlagSet(FLAG_COMPLETE);
+ }
+
+ @Override
+ public String toString() {
+ return "RequestChannelFrame{"
+ + "follows="
+ + isFollowsFlagSet()
+ + ", complete="
+ + isCompleteFlagSet()
+ + ", initialRequestN="
+ + getInitialRequestN()
+ + ", metadata="
+ + mapMetadata(ByteBufUtil::hexDump)
+ + ", data="
+ + mapData(ByteBufUtil::hexDump)
+ + '}';
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/RequestFireAndForgetFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/RequestFireAndForgetFrame.java
new file mode 100644
index 000000000..3749ee329
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/RequestFireAndForgetFrame.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.util.RecyclerFactory.createRecycler;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.util.Recycler;
+import io.netty.util.Recycler.Handle;
+import java.util.Objects;
+import reactor.util.annotation.Nullable;
+
+/**
+ * An RSocket {@code REQUEST_FNF} frame.
+ *
+ * @see Request
+ * Fire and Forget Frame
+ */
+public final class RequestFireAndForgetFrame
+ extends AbstractRecyclableFragmentableFrame {
+
+ private static final int OFFSET_METADATA_LENGTH = FRAME_TYPE_AND_FLAGS_BYTES;
+
+ private static final Recycler RECYCLER =
+ createRecycler(RequestFireAndForgetFrame::new);
+
+ private RequestFireAndForgetFrame(Handle handle) {
+ super(handle);
+ }
+
+ /**
+ * Creates the {@code REQUEST_FNF} frame.
+ *
+ * @param byteBuf the {@link ByteBuf} representing the frame
+ * @return the {@code REQUEST_FNF} frame.
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static RequestFireAndForgetFrame createRequestFireAndForgetFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return RECYCLER.get().setByteBuf(byteBuf.retain());
+ }
+
+ /**
+ * Creates the {@code REQUEST_FNF} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param follows whether to set the Follows flag
+ * @param metadata the metadata
+ * @param data the data
+ * @return the {@code REQUEST_FNF} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ */
+ public static RequestFireAndForgetFrame createRequestFireAndForgetFrame(
+ ByteBufAllocator byteBufAllocator,
+ boolean follows,
+ @Nullable String metadata,
+ @Nullable String data) {
+
+ return createRequestFireAndForgetFrame(
+ byteBufAllocator, follows, getUtf8AsByteBuf(metadata), getUtf8AsByteBuf(data));
+ }
+
+ /**
+ * Creates the {@code REQUEST_FNF} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param follows whether to set the Follows flag
+ * @param metadata the metadata
+ * @param data the data
+ * @return the {@code REQUEST_FNF} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ */
+ public static RequestFireAndForgetFrame createRequestFireAndForgetFrame(
+ ByteBufAllocator byteBufAllocator,
+ boolean follows,
+ @Nullable ByteBuf metadata,
+ @Nullable ByteBuf data) {
+
+ ByteBuf byteBuf = createFrameTypeAndFlags(byteBufAllocator, FrameType.REQUEST_FNF);
+
+ if (follows) {
+ byteBuf = setFollowsFlag(byteBuf);
+ }
+
+ byteBuf = appendMetadata(byteBufAllocator, byteBuf, metadata);
+ byteBuf = appendData(byteBuf, data);
+
+ return RECYCLER.get().setByteBuf(byteBuf);
+ }
+
+ @Override
+ public RequestFireAndForgetFrame createFragment(
+ ByteBufAllocator byteBufAllocator, @Nullable ByteBuf metadata, @Nullable ByteBuf data) {
+
+ return createRequestFireAndForgetFrame(byteBufAllocator, true, metadata, data);
+ }
+
+ @Override
+ public RequestFireAndForgetFrame createNonFragment(
+ ByteBufAllocator byteBufAllocator, @Nullable ByteBuf metadata, @Nullable ByteBuf data) {
+
+ return createRequestFireAndForgetFrame(byteBufAllocator, false, metadata, data);
+ }
+
+ @Override
+ public ByteBuf getUnsafeData() {
+ return getData(OFFSET_METADATA_LENGTH);
+ }
+
+ @Override
+ public @Nullable ByteBuf getUnsafeMetadata() {
+ return getMetadata(OFFSET_METADATA_LENGTH);
+ }
+
+ @Override
+ public String toString() {
+ return "RequestFireAndForgetFrame{"
+ + "follows="
+ + isFollowsFlagSet()
+ + ", metadata="
+ + mapMetadata(ByteBufUtil::hexDump)
+ + ", data="
+ + mapData(ByteBufUtil::hexDump)
+ + '}';
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/RequestNFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/RequestNFrame.java
new file mode 100644
index 000000000..d8c89e7b6
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/RequestNFrame.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.framing.FrameType.REQUEST_N;
+import static io.rsocket.util.RecyclerFactory.createRecycler;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.util.Recycler;
+import io.netty.util.Recycler.Handle;
+import io.rsocket.util.NumberUtils;
+import java.util.Objects;
+
+/**
+ * An RSocket {@code REQUEST_N} frame.
+ *
+ * @see RequestN
+ * Frame
+ */
+public final class RequestNFrame extends AbstractRecyclableFrame {
+
+ private static final int OFFSET_REQUEST_N = FRAME_TYPE_AND_FLAGS_BYTES;
+
+ private static final Recycler RECYCLER = createRecycler(RequestNFrame::new);
+
+ private RequestNFrame(Handle handle) {
+ super(handle);
+ }
+
+ /**
+ * Creates the {@code REQUEST_N} frame.
+ *
+ * @param byteBuf the {@link ByteBuf} representing the frame
+ * @return the {@code REQUEST_N} frame.
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static RequestNFrame createRequestNFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return RECYCLER.get().setByteBuf(byteBuf.retain());
+ }
+
+ /**
+ * Creates the {@code REQUEST_N} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param requestN the size of the request. Must be positive.
+ * @return the {@code REQUEST_N} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ */
+ public static RequestNFrame createRequestNFrame(ByteBufAllocator byteBufAllocator, int requestN) {
+ NumberUtils.requirePositive(requestN, "requestN must be positive");
+
+ ByteBuf byteBuf = createFrameTypeAndFlags(byteBufAllocator, REQUEST_N).writeInt(requestN);
+
+ return RECYCLER.get().setByteBuf(byteBuf);
+ }
+
+ @Override
+ public String toString() {
+ return "RequestNFrame{" + "requestN=" + getRequestN() + '}';
+ }
+
+ /**
+ * Returns the size of the request.
+ *
+ * @return the size of the request
+ */
+ int getRequestN() {
+ return getByteBuf().getInt(OFFSET_REQUEST_N);
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/RequestResponseFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/RequestResponseFrame.java
new file mode 100644
index 000000000..e0b0d9291
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/RequestResponseFrame.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.util.RecyclerFactory.createRecycler;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.util.Recycler;
+import io.netty.util.Recycler.Handle;
+import java.util.Objects;
+import reactor.util.annotation.Nullable;
+
+/**
+ * An RSocket {@code REQUEST_RESPONSE} frame.
+ *
+ * @see Request
+ * Response Frame
+ */
+public final class RequestResponseFrame
+ extends AbstractRecyclableFragmentableFrame {
+
+ private static final int OFFSET_METADATA_LENGTH = FRAME_TYPE_AND_FLAGS_BYTES;
+
+ private static final Recycler RECYCLER =
+ createRecycler(RequestResponseFrame::new);
+
+ private RequestResponseFrame(Handle handle) {
+ super(handle);
+ }
+
+ /**
+ * Creates the {@code REQUEST_RESPONSE} frame.
+ *
+ * @param byteBuf the {@link ByteBuf} representing the frame
+ * @return the {@code REQUEST_RESPONSE} frame.
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static RequestResponseFrame createRequestResponseFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return RECYCLER.get().setByteBuf(byteBuf.retain());
+ }
+
+ /**
+ * Creates the {@code REQUEST_RESPONSE} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param follows whether to set the Follows flag
+ * @param metadata the metadata
+ * @param data the data
+ * @return the {@code REQUEST_RESPONSE} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ */
+ public static RequestResponseFrame createRequestResponseFrame(
+ ByteBufAllocator byteBufAllocator,
+ boolean follows,
+ @Nullable String metadata,
+ @Nullable String data) {
+
+ return createRequestResponseFrame(
+ byteBufAllocator, follows, getUtf8AsByteBuf(metadata), getUtf8AsByteBuf(data));
+ }
+
+ /**
+ * Creates the {@code REQUEST_RESPONSE} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param follows whether to set the Follows flag
+ * @param metadata the metadata
+ * @param data the data
+ * @return the {@code REQUEST_RESPONSE} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ */
+ public static RequestResponseFrame createRequestResponseFrame(
+ ByteBufAllocator byteBufAllocator,
+ boolean follows,
+ @Nullable ByteBuf metadata,
+ @Nullable ByteBuf data) {
+
+ ByteBuf byteBuf = createFrameTypeAndFlags(byteBufAllocator, FrameType.REQUEST_RESPONSE);
+
+ if (follows) {
+ byteBuf = setFollowsFlag(byteBuf);
+ }
+
+ byteBuf = appendMetadata(byteBufAllocator, byteBuf, metadata);
+ byteBuf = appendData(byteBuf, data);
+
+ return RECYCLER.get().setByteBuf(byteBuf);
+ }
+
+ @Override
+ public RequestResponseFrame createFragment(
+ ByteBufAllocator byteBufAllocator, @Nullable ByteBuf metadata, @Nullable ByteBuf data) {
+
+ return createRequestResponseFrame(byteBufAllocator, true, metadata, data);
+ }
+
+ @Override
+ public RequestResponseFrame createNonFragment(
+ ByteBufAllocator byteBufAllocator, @Nullable ByteBuf metadata, @Nullable ByteBuf data) {
+
+ return createRequestResponseFrame(byteBufAllocator, false, metadata, data);
+ }
+
+ @Override
+ public ByteBuf getUnsafeData() {
+ return getData(OFFSET_METADATA_LENGTH);
+ }
+
+ @Override
+ public @Nullable ByteBuf getUnsafeMetadata() {
+ return getMetadata(OFFSET_METADATA_LENGTH);
+ }
+
+ @Override
+ public String toString() {
+ return "RequestResponseFrame{"
+ + "follows="
+ + isFollowsFlagSet()
+ + ", metadata="
+ + mapMetadata(ByteBufUtil::hexDump)
+ + ", data="
+ + mapData(ByteBufUtil::hexDump)
+ + '}';
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/RequestStreamFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/RequestStreamFrame.java
new file mode 100644
index 000000000..f372c631a
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/RequestStreamFrame.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.util.RecyclerFactory.createRecycler;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.util.Recycler;
+import io.netty.util.Recycler.Handle;
+import io.rsocket.util.NumberUtils;
+import java.util.Objects;
+import reactor.util.annotation.Nullable;
+
+/**
+ * An RSocket {@code REQUEST_STREAM} frame.
+ *
+ * @see Request
+ * Stream Frame
+ */
+public final class RequestStreamFrame
+ extends AbstractRecyclableFragmentableFrame {
+
+ private static final int OFFSET_INITIAL_REQUEST_N = FRAME_TYPE_AND_FLAGS_BYTES;
+
+ private static final int OFFSET_METADATA_LENGTH = OFFSET_INITIAL_REQUEST_N + Integer.BYTES;
+
+ private static final Recycler RECYCLER =
+ createRecycler(RequestStreamFrame::new);
+
+ private RequestStreamFrame(Handle handle) {
+ super(handle);
+ }
+
+ /**
+ * Creates the {@code REQUEST_STREAM} frame.
+ *
+ * @param byteBuf the {@link ByteBuf} representing the frame
+ * @return the {@code REQUEST_STREAM} frame.
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static RequestStreamFrame createRequestStreamFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return RECYCLER.get().setByteBuf(byteBuf.retain());
+ }
+
+ /**
+ * Creates the {@code REQUEST_STREAM} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param follows whether to set the Follows flag
+ * @param initialRequestN the initial requestN
+ * @param metadata the metadata
+ * @param data the data
+ * @return the {@code REQUEST_STREAM} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ */
+ public static RequestStreamFrame createRequestStreamFrame(
+ ByteBufAllocator byteBufAllocator,
+ boolean follows,
+ int initialRequestN,
+ @Nullable String metadata,
+ @Nullable String data) {
+
+ return createRequestStreamFrame(
+ byteBufAllocator,
+ follows,
+ initialRequestN,
+ getUtf8AsByteBuf(metadata),
+ getUtf8AsByteBuf(data));
+ }
+
+ /**
+ * Creates the {@code REQUEST_STREAM} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param follows whether to set the Follows flag
+ * @param initialRequestN the initial requestN
+ * @param metadata the metadata
+ * @param data the data
+ * @return the {@code REQUEST_STREAM} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ * @throws IllegalArgumentException if {@code initialRequestN} is not positive
+ */
+ public static RequestStreamFrame createRequestStreamFrame(
+ ByteBufAllocator byteBufAllocator,
+ boolean follows,
+ int initialRequestN,
+ @Nullable ByteBuf metadata,
+ @Nullable ByteBuf data) {
+
+ NumberUtils.requirePositive(initialRequestN, "initialRequestN must be positive");
+
+ ByteBuf byteBuf = createFrameTypeAndFlags(byteBufAllocator, FrameType.REQUEST_STREAM);
+
+ if (follows) {
+ byteBuf = setFollowsFlag(byteBuf);
+ }
+
+ byteBuf = byteBuf.writeInt(initialRequestN);
+ byteBuf = appendMetadata(byteBufAllocator, byteBuf, metadata);
+ byteBuf = appendData(byteBuf, data);
+
+ return RECYCLER.get().setByteBuf(byteBuf);
+ }
+
+ @Override
+ public RequestStreamFrame createFragment(
+ ByteBufAllocator byteBufAllocator, @Nullable ByteBuf metadata, @Nullable ByteBuf data) {
+
+ return createRequestStreamFrame(byteBufAllocator, true, getInitialRequestN(), metadata, data);
+ }
+
+ @Override
+ public RequestStreamFrame createNonFragment(
+ ByteBufAllocator byteBufAllocator, @Nullable ByteBuf metadata, @Nullable ByteBuf data) {
+
+ return createRequestStreamFrame(byteBufAllocator, false, getInitialRequestN(), metadata, data);
+ }
+
+ /**
+ * Returns the initial requestN.
+ *
+ * @return the initial requestN
+ */
+ public int getInitialRequestN() {
+ return getByteBuf().getInt(OFFSET_INITIAL_REQUEST_N);
+ }
+
+ @Override
+ public ByteBuf getUnsafeData() {
+ return getData(OFFSET_METADATA_LENGTH);
+ }
+
+ @Override
+ public @Nullable ByteBuf getUnsafeMetadata() {
+ return getMetadata(OFFSET_METADATA_LENGTH);
+ }
+
+ @Override
+ public String toString() {
+ return "RequestStreamFrame{"
+ + "follows="
+ + isFollowsFlagSet()
+ + ", initialRequestN="
+ + getInitialRequestN()
+ + ", metadata="
+ + mapMetadata(ByteBufUtil::hexDump)
+ + ", data="
+ + mapData(ByteBufUtil::hexDump)
+ + '}';
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/ResumeFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/ResumeFrame.java
new file mode 100644
index 000000000..c575d2ba2
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/ResumeFrame.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.framing.FrameType.RESUME;
+import static io.rsocket.framing.LengthUtils.getLengthAsUnsignedShort;
+import static io.rsocket.util.NumberUtils.requireUnsignedShort;
+import static io.rsocket.util.RecyclerFactory.createRecycler;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.netty.util.Recycler;
+import io.netty.util.Recycler.Handle;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * An RSocket {@code RESUME} frame.
+ *
+ * @see Resume
+ * Frame
+ */
+public final class ResumeFrame extends AbstractRecyclableFrame {
+
+ private static final int OFFSET_MAJOR_VERSION = FRAME_TYPE_AND_FLAGS_BYTES;
+
+ private static final int OFFSET_MINOR_VERSION = OFFSET_MAJOR_VERSION + Short.BYTES;
+
+ private static final int OFFSET_TOKEN_LENGTH = OFFSET_MINOR_VERSION + Short.BYTES;
+
+ private static final int OFFSET_RESUME_IDENTIFICATION_TOKEN = OFFSET_TOKEN_LENGTH + Short.BYTES;
+
+ private static final Recycler RECYCLER = createRecycler(ResumeFrame::new);
+
+ private ResumeFrame(Handle handle) {
+ super(handle);
+ }
+
+ /**
+ * Creates the {@code RESUME} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param resumeIdentificationToken the resume identification token
+ * @param lastReceivedServerPosition the last received server position
+ * @param firstAvailableClientPosition the first available client position
+ * @return the {@code RESUME} frame
+ * @throws NullPointerException if {@code byteBufAllocator} or {@code resumeIdentificationToken}
+ * is {@code null}
+ */
+ public static ResumeFrame createResumeFrame(
+ ByteBufAllocator byteBufAllocator,
+ String resumeIdentificationToken,
+ long lastReceivedServerPosition,
+ long firstAvailableClientPosition) {
+
+ return createResumeFrame(
+ byteBufAllocator,
+ getUtf8AsByteBufRequired(
+ resumeIdentificationToken, "resumeIdentificationToken must not be null"),
+ lastReceivedServerPosition,
+ firstAvailableClientPosition);
+ }
+
+ /**
+ * Creates the {@code RESUME} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param resumeIdentificationToken the resume identification token
+ * @param lastReceivedServerPosition the last received server position
+ * @param firstAvailableClientPosition the first available client position
+ * @return the {@code RESUME} frame
+ * @throws NullPointerException if {@code byteBufAllocator} or {@code resumeIdentificationToken}
+ * is {@code null}
+ */
+ public static ResumeFrame createResumeFrame(
+ ByteBufAllocator byteBufAllocator,
+ ByteBuf resumeIdentificationToken,
+ long lastReceivedServerPosition,
+ long firstAvailableClientPosition) {
+
+ return createResumeFrame(
+ byteBufAllocator,
+ 1,
+ 0,
+ resumeIdentificationToken,
+ lastReceivedServerPosition,
+ firstAvailableClientPosition);
+ }
+
+ /**
+ * Creates the {@code RESUME} frame.
+ *
+ * @param byteBuf the {@link ByteBuf} representing the frame
+ * @return the {@code RESUME} frame.
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static ResumeFrame createResumeFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return RECYCLER.get().setByteBuf(byteBuf.retain());
+ }
+
+ /**
+ * Creates the {@code RESUME} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param majorVersion the major version of the protocol
+ * @param minorVersion the minor version of the protocol
+ * @param resumeIdentificationToken the resume identification token
+ * @param lastReceivedServerPosition the last received server position
+ * @param firstAvailableClientPosition the first available client position
+ * @return the {@code RESUME} frame
+ * @throws NullPointerException if {@code byteBufAllocator} or {@code resumeIdentificationToken}
+ * is {@code null}
+ */
+ public static ResumeFrame createResumeFrame(
+ ByteBufAllocator byteBufAllocator,
+ int majorVersion,
+ int minorVersion,
+ ByteBuf resumeIdentificationToken,
+ long lastReceivedServerPosition,
+ long firstAvailableClientPosition) {
+
+ Objects.requireNonNull(resumeIdentificationToken, "resumeIdentificationToken must not be null");
+
+ ByteBuf byteBuf =
+ createFrameTypeAndFlags(byteBufAllocator, RESUME)
+ .writeShort(requireUnsignedShort(majorVersion))
+ .writeShort(requireUnsignedShort(minorVersion));
+
+ byteBuf = byteBuf.writeShort(getLengthAsUnsignedShort(resumeIdentificationToken));
+ byteBuf =
+ Unpooled.wrappedBuffer(
+ byteBuf, resumeIdentificationToken.retain(), byteBufAllocator.buffer());
+
+ byteBuf = byteBuf.writeLong(lastReceivedServerPosition).writeLong(firstAvailableClientPosition);
+
+ return RECYCLER.get().setByteBuf(byteBuf);
+ }
+
+ /**
+ * Returns the first available client position.
+ *
+ * @return the first available client position
+ */
+ public long getFirstAvailableClientPosition() {
+ return getByteBuf().getLong(getFirstAvailableClientPositionOffset());
+ }
+
+ /**
+ * Returns the last received server position.
+ *
+ * @return the last received server position
+ */
+ public long getLastReceivedServerPosition() {
+ return getByteBuf().getLong(getLastReceivedServerPositionOffset());
+ }
+
+ /**
+ * Returns the major version of the protocol.
+ *
+ * @return the major version of the protocol
+ */
+ public int getMajorVersion() {
+ return getByteBuf().getUnsignedShort(OFFSET_MAJOR_VERSION);
+ }
+
+ /**
+ * Returns the minor version of the protocol.
+ *
+ * @return the minor version of the protocol
+ */
+ public int getMinorVersion() {
+ return getByteBuf().getUnsignedShort(OFFSET_MINOR_VERSION);
+ }
+
+ /**
+ * Returns the resume identification token as a UTF-8 {@link String}.
+ *
+ * @return the resume identification token as a UTF-8 {@link String}
+ */
+ public String getResumeIdentificationTokenAsUtf8() {
+ return mapResumeIdentificationToken(byteBuf -> byteBuf.toString(UTF_8));
+ }
+
+ /**
+ * Returns the resume identification token directly.
+ *
+ * Note: this resume identification token will be outside of the {@link Frame}'s
+ * lifecycle and may be released at any time. It is highly recommended that you {@link
+ * ByteBuf#retain()} the resume identification token if you store it.
+ *
+ * @return the resume identification token directly
+ * @see #mapResumeIdentificationToken(Function)
+ */
+ public ByteBuf getUnsafeResumeIdentificationToken() {
+ return getByteBuf().slice(OFFSET_RESUME_IDENTIFICATION_TOKEN, getTokenLength());
+ }
+
+ /**
+ * Exposes the resume identification token for mapping to a different type.
+ *
+ * @param function the function to transform the resume identification token to a different type
+ * @param the different type
+ * @return the resume identification token mapped to a different type
+ * @throws NullPointerException if {@code function} is {@code null}
+ */
+ public T mapResumeIdentificationToken(Function function) {
+ Objects.requireNonNull(function, "function must not be null");
+
+ return function.apply(getUnsafeResumeIdentificationToken());
+ }
+
+ @Override
+ public String toString() {
+ return "ResumeFrame{"
+ + "majorVersion="
+ + getMajorVersion()
+ + ", minorVersion="
+ + getMinorVersion()
+ + ", resumeIdentificationToken="
+ + mapResumeIdentificationToken(ByteBufUtil::hexDump)
+ + ", lastReceivedServerPosition="
+ + getLastReceivedServerPosition()
+ + ", firstAvailableClientPosition="
+ + getFirstAvailableClientPosition()
+ + '}';
+ }
+
+ private int getFirstAvailableClientPositionOffset() {
+ return getLastReceivedServerPositionOffset() + Long.BYTES;
+ }
+
+ private int getLastReceivedServerPositionOffset() {
+ return OFFSET_RESUME_IDENTIFICATION_TOKEN + getTokenLength();
+ }
+
+ private int getTokenLength() {
+ return getByteBuf().getUnsignedShort(OFFSET_TOKEN_LENGTH);
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/ResumeOkFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/ResumeOkFrame.java
new file mode 100644
index 000000000..5fbf2b98d
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/ResumeOkFrame.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.framing.FrameType.RESUME_OK;
+import static io.rsocket.util.RecyclerFactory.createRecycler;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.util.Recycler;
+import io.netty.util.Recycler.Handle;
+import java.util.Objects;
+
+/**
+ * An RSocket {@code RESUME_OK} frame.
+ *
+ * @see Resume
+ * OK Frame
+ */
+public final class ResumeOkFrame extends AbstractRecyclableFrame {
+
+ private static final int OFFSET_LAST_RECEIVED_CLIENT_POSITION = FRAME_TYPE_AND_FLAGS_BYTES;
+
+ private static final Recycler RECYCLER = createRecycler(ResumeOkFrame::new);
+
+ private ResumeOkFrame(Handle handle) {
+ super(handle);
+ }
+
+ /**
+ * Creates the {@code RESUME_OK} frame.
+ *
+ * @param byteBuf the {@link ByteBuf} representing the frame
+ * @return the {@code RESUME_OK} frame.
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static ResumeOkFrame createResumeOkFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return RECYCLER.get().setByteBuf(byteBuf.retain());
+ }
+
+ /**
+ * Creates the {@code RESUME_OK} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param lastReceivedClientPosition the last received server position
+ * @return the {@code RESUME_OK} frame
+ * @throws NullPointerException if {@code byteBufAllocator} is {@code null}
+ */
+ public static ResumeOkFrame createResumeOkFrame(
+ ByteBufAllocator byteBufAllocator, long lastReceivedClientPosition) {
+
+ ByteBuf byteBuf =
+ createFrameTypeAndFlags(byteBufAllocator, RESUME_OK).writeLong(lastReceivedClientPosition);
+
+ return RECYCLER.get().setByteBuf(byteBuf);
+ }
+
+ /**
+ * Returns the last received client position.
+ *
+ * @return the last received client position
+ */
+ public long getLastReceivedClientPosition() {
+ return getByteBuf().getLong(OFFSET_LAST_RECEIVED_CLIENT_POSITION);
+ }
+
+ @Override
+ public String toString() {
+ return "ResumeOkFrame{" + "lastReceivedClientPosition=" + getLastReceivedClientPosition() + '}';
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/SetupFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/SetupFrame.java
new file mode 100644
index 000000000..b58f2a943
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/SetupFrame.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.framing.LengthUtils.getLengthAsUnsignedByte;
+import static io.rsocket.framing.LengthUtils.getLengthAsUnsignedShort;
+import static io.rsocket.util.NumberUtils.requireUnsignedShort;
+import static io.rsocket.util.RecyclerFactory.createRecycler;
+import static java.lang.Math.toIntExact;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.netty.util.Recycler;
+import io.netty.util.Recycler.Handle;
+import io.rsocket.util.NumberUtils;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+import reactor.util.annotation.Nullable;
+
+/**
+ * An RSocket {@code SETUP} frame.
+ *
+ * @see Setup
+ * Frame
+ */
+public final class SetupFrame extends AbstractRecyclableMetadataAndDataFrame {
+
+ private static final int FLAG_LEASE = 1 << 6;
+
+ private static final int FLAG_RESUME_ENABLED = 1 << 7;
+
+ private static final int OFFSET_MAJOR_VERSION = FRAME_TYPE_AND_FLAGS_BYTES;
+
+ private static final int OFFSET_MINOR_VERSION = OFFSET_MAJOR_VERSION + Short.BYTES;
+
+ private static final int OFFSET_KEEPALIVE_INTERVAL = OFFSET_MINOR_VERSION + Short.BYTES;
+
+ private static final int OFFSET_MAX_LIFETIME = OFFSET_KEEPALIVE_INTERVAL + Integer.BYTES;
+
+ private static final int OFFSET_RESUME_IDENTIFICATION_TOKEN_LENGTH =
+ OFFSET_MAX_LIFETIME + Integer.BYTES;
+
+ private static final Recycler RECYCLER = createRecycler(SetupFrame::new);
+
+ private SetupFrame(Handle handle) {
+ super(handle);
+ }
+
+ /**
+ * Creates the {@code SETUP} frame.
+ *
+ * @param byteBuf the {@link ByteBuf} representing the frame
+ * @return the {@code SETUP} frame.
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static SetupFrame createSetupFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return RECYCLER.get().setByteBuf(byteBuf.retain());
+ }
+
+ /**
+ * Creates the {@code SETUP} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param lease whether to set the Lease flag
+ * @param keepAliveInterval the time between {@code KEEPALIVE} frames
+ * @param maxLifetime the time between {@code KEEPALIVE} frames before the server is assumed to be
+ * dead
+ * @param resumeIdentificationToken the resume identification token
+ * @param metadataMimeType metadata MIME-type encoding
+ * @param dataMimeType data MIME-type encoding
+ * @param metadata the {@code metadata}
+ * @param data the {@code data}
+ * @return the {@code SETUP} frame
+ * @throws NullPointerException if {@code byteBufAllocator}, {@code keepAliveInterval}, {@code
+ * maxLifetime}, {@code metadataMimeType}, or {@code dataMimeType} is {@code null}
+ * @throws IllegalArgumentException if {@code keepAliveInterval} or {@code maxLifetime} is not a
+ * positive duration
+ */
+ public static SetupFrame createSetupFrame(
+ ByteBufAllocator byteBufAllocator,
+ boolean lease,
+ Duration keepAliveInterval,
+ Duration maxLifetime,
+ @Nullable String resumeIdentificationToken,
+ String metadataMimeType,
+ String dataMimeType,
+ @Nullable String metadata,
+ @Nullable String data) {
+
+ return createSetupFrame(
+ byteBufAllocator,
+ lease,
+ keepAliveInterval,
+ maxLifetime,
+ getUtf8AsByteBuf(resumeIdentificationToken),
+ metadataMimeType,
+ dataMimeType,
+ getUtf8AsByteBuf(metadata),
+ getUtf8AsByteBuf(data));
+ }
+
+ /**
+ * Creates the {@code SETUP} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param lease whether to set the Lease flag
+ * @param keepAliveInterval the time between {@code KEEPALIVE} frames
+ * @param maxLifetime the time between {@code KEEPALIVE} frames before the server is assumed to be
+ * dead
+ * @param resumeIdentificationToken the resume identification token
+ * @param metadataMimeType metadata MIME-type encoding
+ * @param dataMimeType data MIME-type encoding
+ * @param metadata the {@code metadata}
+ * @param data the {@code data}
+ * @return the {@code SETUP} frame
+ * @throws NullPointerException if {@code byteBufAllocator}, {@code keepAliveInterval}, {@code
+ * maxLifetime}, {@code metadataMimeType}, or {@code dataMimeType} is {@code null}
+ * @throws IllegalArgumentException if {@code keepAliveInterval} or {@code maxLifetime} is not a
+ * positive duration
+ */
+ public static SetupFrame createSetupFrame(
+ ByteBufAllocator byteBufAllocator,
+ boolean lease,
+ Duration keepAliveInterval,
+ Duration maxLifetime,
+ @Nullable ByteBuf resumeIdentificationToken,
+ String metadataMimeType,
+ String dataMimeType,
+ @Nullable ByteBuf metadata,
+ @Nullable ByteBuf data) {
+
+ return createSetupFrame(
+ byteBufAllocator,
+ lease,
+ 1,
+ 0,
+ keepAliveInterval,
+ maxLifetime,
+ resumeIdentificationToken,
+ metadataMimeType,
+ dataMimeType,
+ metadata,
+ data);
+ }
+
+ /**
+ * Creates the {@code SETUP} frame.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param lease whether to set the Lease flag
+ * @param majorVersion the major version of the protocol
+ * @param minorVersion the minor version of the protocol
+ * @param keepAliveInterval the time between {@code KEEPALIVE} frames
+ * @param maxLifetime the time between {@code KEEPALIVE} frames before the server is assumed to be
+ * dead
+ * @param resumeIdentificationToken the resume identification token
+ * @param metadataMimeType metadata MIME-type encoding
+ * @param dataMimeType data MIME-type encoding
+ * @param metadata the {@code metadata}
+ * @param data the {@code data}
+ * @return the {@code SETUP} frame
+ * @throws NullPointerException if {@code byteBufAllocator}, {@code keepAliveInterval}, {@code
+ * maxLifetime}, {@code metadataMimeType}, or {@code dataMimeType} is {@code null}
+ * @throws IllegalArgumentException if {@code keepAliveInterval} or {@code maxLifetime} is not a
+ * positive duration
+ */
+ public static SetupFrame createSetupFrame(
+ ByteBufAllocator byteBufAllocator,
+ boolean lease,
+ int majorVersion,
+ int minorVersion,
+ Duration keepAliveInterval,
+ Duration maxLifetime,
+ @Nullable ByteBuf resumeIdentificationToken,
+ String metadataMimeType,
+ String dataMimeType,
+ @Nullable ByteBuf metadata,
+ @Nullable ByteBuf data) {
+
+ Objects.requireNonNull(keepAliveInterval, "keepAliveInterval must not be null");
+ NumberUtils.requirePositive(
+ keepAliveInterval.toMillis(), "keepAliveInterval must be a positive duration");
+ Objects.requireNonNull(maxLifetime, "maxLifetime must not be null");
+ NumberUtils.requirePositive(maxLifetime.toMillis(), "maxLifetime must be a positive duration");
+
+ ByteBuf metadataMimeTypeByteBuf =
+ getUtf8AsByteBufRequired(metadataMimeType, "metadataMimeType must not be null");
+ ByteBuf dataMimeTypeByteBuf =
+ getUtf8AsByteBufRequired(dataMimeType, "dataMimeType must not be null");
+
+ ByteBuf byteBuf = createFrameTypeAndFlags(byteBufAllocator, FrameType.SETUP);
+
+ if (lease) {
+ byteBuf = setFlag(byteBuf, FLAG_LEASE);
+ }
+
+ byteBuf =
+ byteBuf
+ .writeShort(requireUnsignedShort(majorVersion))
+ .writeShort(requireUnsignedShort(minorVersion))
+ .writeInt(toIntExact(keepAliveInterval.toMillis()))
+ .writeInt(toIntExact(maxLifetime.toMillis()));
+
+ if (resumeIdentificationToken != null) {
+ byteBuf =
+ setFlag(byteBuf, FLAG_RESUME_ENABLED)
+ .writeShort(getLengthAsUnsignedShort(resumeIdentificationToken));
+ byteBuf =
+ Unpooled.wrappedBuffer(
+ byteBuf, resumeIdentificationToken.retain(), byteBufAllocator.buffer());
+ }
+
+ byteBuf = byteBuf.writeByte(getLengthAsUnsignedByte(metadataMimeTypeByteBuf));
+ byteBuf = Unpooled.wrappedBuffer(byteBuf, metadataMimeTypeByteBuf, byteBufAllocator.buffer());
+
+ byteBuf = byteBuf.writeByte(getLengthAsUnsignedByte(dataMimeTypeByteBuf));
+ byteBuf = Unpooled.wrappedBuffer(byteBuf, dataMimeTypeByteBuf, byteBufAllocator.buffer());
+
+ byteBuf = appendMetadata(byteBufAllocator, byteBuf, metadata);
+ byteBuf = appendData(byteBuf, data);
+
+ return RECYCLER.get().setByteBuf(byteBuf);
+ }
+
+ /**
+ * Returns the data MIME-type, decoded at {@link StandardCharsets#UTF_8}.
+ *
+ * @return the data MIME-type, decoded as {@link StandardCharsets#UTF_8}
+ */
+ public String getDataMimeType() {
+ return getDataMimeType(UTF_8);
+ }
+
+ /**
+ * Returns the data MIME-type.
+ *
+ * @param charset the {@link Charset} to decode the data MIME-type with
+ * @return the data MIME-type
+ */
+ public String getDataMimeType(Charset charset) {
+ return getByteBuf().slice(getDataMimeTypeOffset(), getDataMimeTypeLength()).toString(charset);
+ }
+
+ /**
+ * Returns the keep alive interval.
+ *
+ * @return the keep alive interval
+ */
+ public Duration getKeepAliveInterval() {
+ return Duration.ofMillis(getByteBuf().getInt(OFFSET_KEEPALIVE_INTERVAL));
+ }
+
+ /**
+ * Returns the major version of the protocol.
+ *
+ * @return the major version of the protocol
+ */
+ public int getMajorVersion() {
+ return getByteBuf().getUnsignedShort(OFFSET_MAJOR_VERSION);
+ }
+
+ /**
+ * Returns the max lifetime.
+ *
+ * @return the max lifetime
+ */
+ public Duration getMaxLifetime() {
+ return Duration.ofMillis(getByteBuf().getInt(OFFSET_MAX_LIFETIME));
+ }
+
+ /**
+ * Returns the metadata MIME-type, decoded at {@link StandardCharsets#UTF_8}.
+ *
+ * @return the metadata MIME-type, decoded as {@link StandardCharsets#UTF_8}
+ */
+ public String getMetadataMimeType() {
+ return getMetadataMimeType(UTF_8);
+ }
+
+ /**
+ * Returns the metadata MIME-type.
+ *
+ * @param charset the {@link Charset} to decode the metadata MIME-type with
+ * @return the metadata MIME-type
+ */
+ public String getMetadataMimeType(Charset charset) {
+ return getByteBuf()
+ .slice(getMetadataMimeTypeOffset(), getMetadataMimeTypeLength())
+ .toString(charset);
+ }
+
+ /**
+ * Returns the minor version of the protocol.
+ *
+ * @return the minor version of the protocol
+ */
+ public int getMinorVersion() {
+ return getByteBuf().getUnsignedShort(OFFSET_MINOR_VERSION);
+ }
+
+ /**
+ * Returns the resume identification token as a UTF-8 {@link String}. If the Resume Enabled flag
+ * is not set, returns {@link Optional#empty()}.
+ *
+ * @return optionally, the resume identification token as a UTF-8 {@link String}
+ */
+ public Optional getResumeIdentificationTokenAsUtf8() {
+ return Optional.ofNullable(getUnsafeResumeIdentificationTokenAsUtf8());
+ }
+
+ @Override
+ public ByteBuf getUnsafeData() {
+ return getData(getMetadataLengthOffset());
+ }
+
+ @Override
+ public @Nullable ByteBuf getUnsafeMetadata() {
+ return getMetadata(getMetadataLengthOffset());
+ }
+
+ /**
+ * Returns the resume identification token directly. If the Resume Enabled flag is not set,
+ * returns {@code null}.
+ *
+ * Note: this resume identification token will be outside of the {@link Frame}'s
+ * lifecycle and may be released at any time. It is highly recommended that you {@link
+ * ByteBuf#retain()} the resume identification token if you store it.
+ *
+ * @return the resume identification token directly, or {@code null} if the Resume Enabled flag is
+ * not set
+ * @see #mapResumeIdentificationToken(Function)
+ */
+ public @Nullable ByteBuf getUnsafeResumeIdentificationToken() {
+ if (!isFlagSet(FLAG_RESUME_ENABLED)) {
+ return null;
+ }
+
+ ByteBuf byteBuf = getByteBuf();
+ return byteBuf.slice(
+ getResumeIdentificationTokenOffset(), getResumeIdentificationTokenLength());
+ }
+
+ /**
+ * Returns the resume identification token as a UTF-8 {@link String}. If the Resume Enabled flag
+ * is not set, returns {@code null}.
+ *
+ * @return the resume identification token as a UTF-8 {@link String} or {@code null} if the Resume
+ * Enabled flag is not set.
+ * @see #getResumeIdentificationTokenAsUtf8()
+ */
+ public @Nullable String getUnsafeResumeIdentificationTokenAsUtf8() {
+ ByteBuf byteBuf = getUnsafeResumeIdentificationToken();
+ return byteBuf == null ? null : byteBuf.toString(UTF_8);
+ }
+
+ /**
+ * Returns whether the lease flag is set.
+ *
+ * @return whether the lease flag is set
+ */
+ public boolean isLeaseFlagSet() {
+ return isFlagSet(FLAG_LEASE);
+ }
+
+ /**
+ * Exposes the resume identification token for mapping to a different type. If the Resume Enabled
+ * flag is not set, returns {@link Optional#empty()}.
+ *
+ * @param function the function to transform the resume identification token to a different type
+ * @param the different type
+ * @return optionally, the resume identification token mapped to a different type
+ * @throws NullPointerException if {@code function} is {@code null}
+ */
+ public Optional mapResumeIdentificationToken(Function function) {
+ Objects.requireNonNull(function, "function must not be null");
+
+ return Optional.ofNullable(getUnsafeResumeIdentificationToken()).map(function);
+ }
+
+ @Override
+ public String toString() {
+ return "SetupFrame{"
+ + "lease="
+ + isLeaseFlagSet()
+ + ", majorVersion="
+ + getMajorVersion()
+ + ", minorVersion="
+ + getMinorVersion()
+ + ", keepAliveInterval="
+ + getKeepAliveInterval()
+ + ", maxLifetime="
+ + getMaxLifetime()
+ + ", resumeIdentificationToken="
+ + mapResumeIdentificationToken(ByteBufUtil::hexDump)
+ + ", metadataMimeType="
+ + getMetadataMimeType()
+ + ", dataMimeType="
+ + getDataMimeType()
+ + ", metadata="
+ + mapMetadata(ByteBufUtil::hexDump)
+ + ", data="
+ + mapData(ByteBufUtil::hexDump)
+ + '}';
+ }
+
+ private int getDataMimeTypeLength() {
+ return getByteBuf().getUnsignedByte(getDataMimeTypeLengthOffset());
+ }
+
+ private int getDataMimeTypeLengthOffset() {
+ return getMetadataMimeTypeOffset() + getMetadataMimeTypeLength();
+ }
+
+ private int getDataMimeTypeOffset() {
+ return getDataMimeTypeLengthOffset() + Byte.BYTES;
+ }
+
+ private int getMetadataLengthOffset() {
+ return getDataMimeTypeOffset() + getDataMimeTypeLength();
+ }
+
+ private int getMetadataMimeTypeLength() {
+ return getByteBuf().getUnsignedByte(getMetadataMimeTypeLengthOffset());
+ }
+
+ private int getMetadataMimeTypeLengthOffset() {
+ return getResumeIdentificationTokenOffset() + getResumeIdentificationTokenLength();
+ }
+
+ private int getMetadataMimeTypeOffset() {
+ return getMetadataMimeTypeLengthOffset() + Byte.BYTES;
+ }
+
+ private int getResumeIdentificationTokenLength() {
+ if (isFlagSet(FLAG_RESUME_ENABLED)) {
+ return getByteBuf().getUnsignedShort(OFFSET_RESUME_IDENTIFICATION_TOKEN_LENGTH);
+ } else {
+ return 0;
+ }
+ }
+
+ private int getResumeIdentificationTokenOffset() {
+ if (isFlagSet(FLAG_RESUME_ENABLED)) {
+ return OFFSET_RESUME_IDENTIFICATION_TOKEN_LENGTH + Short.BYTES;
+ } else {
+ return OFFSET_RESUME_IDENTIFICATION_TOKEN_LENGTH;
+ }
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/StreamIdFrame.java b/rsocket-core/src/main/java/io/rsocket/framing/StreamIdFrame.java
new file mode 100644
index 000000000..a47db16b4
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/StreamIdFrame.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.util.RecyclerFactory.createRecycler;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.netty.util.Recycler;
+import io.netty.util.Recycler.Handle;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * An RSocket frame with a stream id.
+ *
+ * @see Frame
+ * Header Format
+ */
+public final class StreamIdFrame extends AbstractRecyclableFrame {
+
+ private static final Recycler RECYCLER = createRecycler(StreamIdFrame::new);
+
+ private static final int STREAM_ID_BYTES = Integer.BYTES;
+
+ private StreamIdFrame(Handle handle) {
+ super(handle);
+ }
+
+ /**
+ * Creates the frame with a stream id.
+ *
+ * @param byteBuf the {@link ByteBuf} representing the frame
+ * @return the frame with a stream id
+ * @throws NullPointerException if {@code byteBuf} is {@code null}
+ */
+ public static StreamIdFrame createStreamIdFrame(ByteBuf byteBuf) {
+ Objects.requireNonNull(byteBuf, "byteBuf must not be null");
+
+ return RECYCLER.get().setByteBuf(byteBuf.retain());
+ }
+
+ /**
+ * Creates the frame with a stream id.
+ *
+ * @param byteBufAllocator the {@code ByteBufAllocator} to use
+ * @param streamId the stream id
+ * @param frame the frame to prepend the stream id to
+ * @return the frame with a stream id
+ * @throws NullPointerException if {@code byteBufAllocator} or {@code frame} is {@code null}
+ */
+ public static StreamIdFrame createStreamIdFrame(
+ ByteBufAllocator byteBufAllocator, int streamId, Frame frame) {
+
+ Objects.requireNonNull(byteBufAllocator, "byteBufAllocator must not be null");
+ Objects.requireNonNull(frame, "frame must not be null");
+
+ ByteBuf streamIdByteBuf =
+ frame.mapFrame(
+ frameByteBuf -> {
+ ByteBuf byteBuf = byteBufAllocator.buffer(STREAM_ID_BYTES).writeInt(streamId);
+
+ return Unpooled.wrappedBuffer(byteBuf, frameByteBuf.retain());
+ });
+
+ return RECYCLER.get().setByteBuf(streamIdByteBuf);
+ }
+
+ /**
+ * Returns the stream id.
+ *
+ * @return the stream id
+ */
+ public int getStreamId() {
+ return getByteBuf().getInt(0);
+ }
+
+ /**
+ * Exposes the {@link Frame} without the stream id as a {@link ByteBuf} for mapping to a different
+ * type.
+ *
+ * @param function the function to transform the {@link Frame} without the stream id as a {@link
+ * ByteBuf} to a different type
+ * @param the different type
+ * @return the {@link Frame} without the stream id as a {@link ByteBuf} mapped to a different type
+ * @throws NullPointerException if {@code function} is {@code null}
+ */
+ public T mapFrameWithoutStreamId(Function function) {
+ Objects.requireNonNull(function, "function must not be null");
+
+ return function.apply(getFrameWithoutStreamId());
+ }
+
+ @Override
+ public String toString() {
+ return "StreamIdFrame{"
+ + "streamId="
+ + getStreamId()
+ + ", frameWithoutStreamId="
+ + mapFrameWithoutStreamId(ByteBufUtil::hexDump)
+ + '}';
+ }
+
+ private ByteBuf getFrameWithoutStreamId() {
+ ByteBuf byteBuf = getByteBuf();
+ return byteBuf.slice(STREAM_ID_BYTES, byteBuf.readableBytes() - STREAM_ID_BYTES);
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/framing/package-info.java b/rsocket-core/src/main/java/io/rsocket/framing/package-info.java
new file mode 100644
index 000000000..7b6ed9d69
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/framing/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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.
+ */
+
+/**
+ * Protocol framing types
+ *
+ * @see Framing
+ */
+@NonNullApi
+package io.rsocket.framing;
+
+import reactor.util.annotation.NonNullApi;
diff --git a/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java b/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java
index a27fa7ab2..4db60d835 100644
--- a/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java
+++ b/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java
@@ -19,7 +19,7 @@
import io.rsocket.Closeable;
import io.rsocket.DuplexConnection;
import io.rsocket.Frame;
-import io.rsocket.FrameType;
+import io.rsocket.framing.FrameType;
import io.rsocket.plugins.DuplexConnectionInterceptor.Type;
import io.rsocket.plugins.PluginRegistry;
import org.reactivestreams.Publisher;
diff --git a/rsocket-core/src/main/java/io/rsocket/resume/ResumeUtil.java b/rsocket-core/src/main/java/io/rsocket/resume/ResumeUtil.java
index b64dfc05b..b34c9d424 100644
--- a/rsocket-core/src/main/java/io/rsocket/resume/ResumeUtil.java
+++ b/rsocket-core/src/main/java/io/rsocket/resume/ResumeUtil.java
@@ -17,7 +17,7 @@
package io.rsocket.resume;
import io.rsocket.Frame;
-import io.rsocket.FrameType;
+import io.rsocket.framing.FrameType;
import io.rsocket.frame.FrameHeaderFlyweight;
public class ResumeUtil {
@@ -26,7 +26,7 @@ public static boolean isTracked(FrameType frameType) {
case REQUEST_CHANNEL:
case REQUEST_STREAM:
case REQUEST_RESPONSE:
- case FIRE_AND_FORGET:
+ case REQUEST_FNF:
// case METADATA_PUSH:
case REQUEST_N:
case CANCEL:
diff --git a/rsocket-core/src/main/java/io/rsocket/util/AbstractionLeakingFrameUtils.java b/rsocket-core/src/main/java/io/rsocket/util/AbstractionLeakingFrameUtils.java
new file mode 100644
index 000000000..c7215da36
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/util/AbstractionLeakingFrameUtils.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.util;
+
+import static io.rsocket.framing.FrameLengthFrame.createFrameLengthFrame;
+import static io.rsocket.framing.StreamIdFrame.createStreamIdFrame;
+import static io.rsocket.util.DisposableUtil.disposeQuietly;
+
+import io.netty.buffer.ByteBufAllocator;
+import io.rsocket.Frame;
+import io.rsocket.framing.FrameFactory;
+import io.rsocket.framing.FrameLengthFrame;
+import io.rsocket.framing.StreamIdFrame;
+import java.util.Objects;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+public final class AbstractionLeakingFrameUtils {
+
+ private AbstractionLeakingFrameUtils() {}
+
+ /**
+ * Returns a {@link Tuple2} of the stream id, and the frame. This strips the frame length and
+ * stream id header from the abstraction leaking frame.
+ *
+ * @param abstractionLeakingFrame the abstraction leaking frame
+ * @return a {@link Tuple2} of the stream id, and the frame
+ * @throws NullPointerException if {@code abstractionLeakingFrame} is {@code null}
+ */
+ public static Tuple2 fromAbstractionLeakingFrame(
+ Frame abstractionLeakingFrame) {
+
+ Objects.requireNonNull(abstractionLeakingFrame, "abstractionLeakingFrame must not be null");
+
+ FrameLengthFrame frameLengthFrame = null;
+ StreamIdFrame streamIdFrame = null;
+
+ try {
+ frameLengthFrame = createFrameLengthFrame(abstractionLeakingFrame.content());
+ streamIdFrame =
+ frameLengthFrame.mapFrameWithoutFrameLength(StreamIdFrame::createStreamIdFrame);
+
+ io.rsocket.framing.Frame frame =
+ streamIdFrame.mapFrameWithoutStreamId(FrameFactory::createFrame);
+
+ return Tuples.of(streamIdFrame.getStreamId(), frame);
+ } finally {
+ disposeQuietly(frameLengthFrame, streamIdFrame);
+ abstractionLeakingFrame.release();
+ }
+ }
+
+ /**
+ * Returns an abstraction leaking frame with the stream id and frame. This adds the frame length
+ * and stream id header to the frame.
+ *
+ * @param byteBufAllocator the {@link ByteBufAllocator} to use
+ * @param streamId the stream id
+ * @param frame the frame
+ * @return an abstraction leaking frame with the stream id and frame
+ * @throws NullPointerException if {@code byteBufAllocator} or {@code frame} is {@code null}
+ */
+ public static Frame toAbstractionLeakingFrame(
+ ByteBufAllocator byteBufAllocator, int streamId, io.rsocket.framing.Frame frame) {
+
+ Objects.requireNonNull(byteBufAllocator, "byteBufAllocator must not be null");
+ Objects.requireNonNull(frame, "frame must not be null");
+
+ StreamIdFrame streamIdFrame = null;
+ FrameLengthFrame frameLengthFrame = null;
+
+ try {
+ streamIdFrame = createStreamIdFrame(byteBufAllocator, streamId, frame);
+ frameLengthFrame = createFrameLengthFrame(byteBufAllocator, streamIdFrame);
+
+ return frameLengthFrame.mapFrame(byteBuf -> Frame.from(byteBuf.retain()));
+ } finally {
+ disposeQuietly(frame, streamIdFrame, frameLengthFrame);
+ }
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/util/DisposableUtil.java b/rsocket-core/src/main/java/io/rsocket/util/DisposableUtil.java
new file mode 100644
index 000000000..92dd1d82c
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/util/DisposableUtil.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.util;
+
+import java.util.Arrays;
+import reactor.core.Disposable;
+
+/** Utilities for working with the {@link Disposable} type. */
+public final class DisposableUtil {
+
+ private DisposableUtil() {}
+
+ /**
+ * Calls the {@link Disposable#dispose()} method if the instance is not null. If any exceptions
+ * are thrown during disposal, suppress them.
+ *
+ * @param disposables the {@link Disposable}s to dispose
+ */
+ public static void disposeQuietly(Disposable... disposables) {
+ Arrays.stream(disposables)
+ .forEach(
+ disposable -> {
+ try {
+ disposable.dispose();
+ } catch (RuntimeException e) {
+ // Suppress any exceptions during disposal
+ }
+ });
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/util/NumberUtils.java b/rsocket-core/src/main/java/io/rsocket/util/NumberUtils.java
new file mode 100644
index 000000000..672c852d0
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/util/NumberUtils.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.util;
+
+import java.util.Objects;
+
+public final class NumberUtils {
+
+ /** The size of a medium in {@code byte}s. */
+ public static final int MEDIUM_BYTES = 3;
+
+ private static final int UNSIGNED_BYTE_SIZE = 8;
+
+ private static final int UNSIGNED_BYTE_MAX_VALUE = (1 << UNSIGNED_BYTE_SIZE) - 1;
+
+ private static final int UNSIGNED_MEDIUM_SIZE = 24;
+
+ private static final int UNSIGNED_MEDIUM_MAX_VALUE = (1 << UNSIGNED_MEDIUM_SIZE) - 1;
+
+ private static final int UNSIGNED_SHORT_SIZE = 16;
+
+ private static final int UNSIGNED_SHORT_MAX_VALUE = (1 << UNSIGNED_SHORT_SIZE) - 1;
+
+ private NumberUtils() {}
+
+ /**
+ * Requires that a {@code long} is greater than zero.
+ *
+ * @param l the {@code long} to test
+ * @param message detail message to be used in the event that a {@link IllegalArgumentException}
+ * is thrown
+ * @return the {@code long} if greater than zero
+ * @throws IllegalArgumentException if {@code l} is less than or equal to zero
+ */
+ public static long requirePositive(long l, String message) {
+ Objects.requireNonNull(message, "message must not be null");
+
+ if (l <= 0) {
+ throw new IllegalArgumentException(message);
+ }
+
+ return l;
+ }
+
+ /**
+ * Requires that an {@code int} is greater than zero.
+ *
+ * @param i the {@code int} to test
+ * @param message detail message to be used in the event that a {@link IllegalArgumentException}
+ * is thrown
+ * @return the {@code int} if greater than zero
+ * @throws IllegalArgumentException if {@code i} is less than or equal to zero
+ */
+ public static int requirePositive(int i, String message) {
+ Objects.requireNonNull(message, "message must not be null");
+
+ if (i <= 0) {
+ throw new IllegalArgumentException(message);
+ }
+
+ return i;
+ }
+
+ /**
+ * Requires that an {@code int} can be represented as an unsigned {@code byte}.
+ *
+ * @param i the {@code int} to test
+ * @return the {@code int} if it can be represented as an unsigned {@code byte}
+ * @throws IllegalArgumentException if {@code i} cannot be represented as an unsigned {@code byte}
+ */
+ public static int requireUnsignedByte(int i) {
+ if (i > UNSIGNED_BYTE_MAX_VALUE) {
+ throw new IllegalArgumentException(
+ String.format("%d is larger than %d bits", i, UNSIGNED_BYTE_SIZE));
+ }
+
+ return i;
+ }
+
+ /**
+ * Requires that an {@code int} can be represented as an unsigned {@code medium}.
+ *
+ * @param i the {@code int} to test
+ * @return the {@code int} if it can be represented as an unsigned {@code medium}
+ * @throws IllegalArgumentException if {@code i} cannot be represented as an unsigned {@code
+ * medium}
+ */
+ public static int requireUnsignedMedium(int i) {
+ if (i > UNSIGNED_MEDIUM_MAX_VALUE) {
+ throw new IllegalArgumentException(
+ String.format("%d is larger than %d bits", i, UNSIGNED_MEDIUM_SIZE));
+ }
+
+ return i;
+ }
+
+ /**
+ * Requires that an {@code int} can be represented as an unsigned {@code short}.
+ *
+ * @param i the {@code int} to test
+ * @return the {@code int} if it can be represented as an unsigned {@code short}
+ * @throws IllegalArgumentException if {@code i} cannot be represented as an unsigned {@code
+ * short}
+ */
+ public static int requireUnsignedShort(int i) {
+ if (i > UNSIGNED_SHORT_MAX_VALUE) {
+ throw new IllegalArgumentException(
+ String.format("%d is larger than %d bits", i, UNSIGNED_SHORT_SIZE));
+ }
+
+ return i;
+ }
+}
diff --git a/rsocket-core/src/main/java/io/rsocket/util/RecyclerFactory.java b/rsocket-core/src/main/java/io/rsocket/util/RecyclerFactory.java
new file mode 100644
index 000000000..30385195c
--- /dev/null
+++ b/rsocket-core/src/main/java/io/rsocket/util/RecyclerFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.util;
+
+import io.netty.util.Recycler;
+import io.netty.util.Recycler.Handle;
+import java.util.Objects;
+import java.util.function.Function;
+
+/** A factory for creating {@link Recycler}s. */
+public final class RecyclerFactory {
+
+ /**
+ * Creates a new {@link Recycler}.
+ *
+ * @param newObjectCreator the {@link Function} to create a new object
+ * @param the type being recycled.
+ * @return the {@link Recycler}
+ * @throws NullPointerException if {@code newObjectCreator} is {@code null}
+ */
+ public static Recycler createRecycler(Function, T> newObjectCreator) {
+ Objects.requireNonNull(newObjectCreator, "newObjectCreator must not be null");
+
+ return new Recycler() {
+
+ @Override
+ protected T newObject(Handle handle) {
+ return newObjectCreator.apply(handle);
+ }
+ };
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/FrameTest.java b/rsocket-core/src/test/java/io/rsocket/FrameTest.java
index 9d6f6a684..b5a5f9ef8 100644
--- a/rsocket-core/src/test/java/io/rsocket/FrameTest.java
+++ b/rsocket-core/src/test/java/io/rsocket/FrameTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import io.rsocket.frame.FrameHeaderFlyweight;
+import io.rsocket.framing.FrameType;
import io.rsocket.util.DefaultPayload;
import org.junit.Test;
diff --git a/rsocket-core/src/test/java/io/rsocket/RSocketClientTest.java b/rsocket-core/src/test/java/io/rsocket/RSocketClientTest.java
index 390198da5..3499ef934 100644
--- a/rsocket-core/src/test/java/io/rsocket/RSocketClientTest.java
+++ b/rsocket-core/src/test/java/io/rsocket/RSocketClientTest.java
@@ -16,7 +16,7 @@
package io.rsocket;
-import static io.rsocket.FrameType.*;
+import static io.rsocket.framing.FrameType.*;
import static io.rsocket.test.util.TestSubscriber.anyPayload;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
@@ -32,6 +32,7 @@
import io.rsocket.exceptions.ApplicationErrorException;
import io.rsocket.exceptions.RejectedSetupException;
import io.rsocket.frame.RequestFrameFlyweight;
+import io.rsocket.framing.FrameType;
import io.rsocket.test.util.TestSubscriber;
import io.rsocket.util.DefaultPayload;
import io.rsocket.util.EmptyPayload;
diff --git a/rsocket-core/src/test/java/io/rsocket/RSocketServerTest.java b/rsocket-core/src/test/java/io/rsocket/RSocketServerTest.java
index 383950d91..db1ca2d65 100644
--- a/rsocket-core/src/test/java/io/rsocket/RSocketServerTest.java
+++ b/rsocket-core/src/test/java/io/rsocket/RSocketServerTest.java
@@ -23,6 +23,7 @@
import static org.hamcrest.Matchers.is;
import io.netty.buffer.Unpooled;
+import io.rsocket.framing.FrameType;
import io.rsocket.test.util.TestDuplexConnection;
import io.rsocket.test.util.TestSubscriber;
import io.rsocket.util.DefaultPayload;
diff --git a/rsocket-core/src/test/java/io/rsocket/exceptions/ApplicationErrorExceptionTest.java b/rsocket-core/src/test/java/io/rsocket/exceptions/ApplicationErrorExceptionTest.java
index 0831d7202..35b30b951 100644
--- a/rsocket-core/src/test/java/io/rsocket/exceptions/ApplicationErrorExceptionTest.java
+++ b/rsocket-core/src/test/java/io/rsocket/exceptions/ApplicationErrorExceptionTest.java
@@ -16,7 +16,8 @@
package io.rsocket.exceptions;
-final class ApplicationErrorExceptionTest implements RSocketExceptionTest {
+final class ApplicationErrorExceptionTest
+ implements RSocketExceptionTest {
@Override
public ApplicationErrorException getException(String message) {
diff --git a/rsocket-core/src/test/java/io/rsocket/exceptions/ExceptionsTest.java b/rsocket-core/src/test/java/io/rsocket/exceptions/ExceptionsTest.java
index 4c8f41dc3..31c387fca 100644
--- a/rsocket-core/src/test/java/io/rsocket/exceptions/ExceptionsTest.java
+++ b/rsocket-core/src/test/java/io/rsocket/exceptions/ExceptionsTest.java
@@ -39,14 +39,6 @@
final class ExceptionsTest {
- @DisplayName("from throws NullPointerException with null frame")
- @Test
- void fromWithNullFrame() {
- assertThatNullPointerException()
- .isThrownBy(() -> Exceptions.from(null))
- .withMessage("frame must not be null");
- }
-
@DisplayName("from returns ApplicationErrorException")
@Test
void fromApplicationException() {
@@ -87,6 +79,16 @@ void fromConnectionErrorException() {
.withFailMessage("test-message");
}
+ @DisplayName("from returns IllegalArgumentException if error frame has illegal error code")
+ @Test
+ void fromIllegalErrorFrame() {
+ ByteBuf byteBuf = createErrorFrame(0x00000000, "test-message");
+
+ assertThat(Exceptions.from(Frame.from(byteBuf)))
+ .isInstanceOf(IllegalArgumentException.class)
+ .withFailMessage("Invalid Error frame: %d, '%s'", 0, "test-message");
+ }
+
@DisplayName("from returns InvalidException")
@Test
void fromInvalidException() {
@@ -147,14 +149,12 @@ void fromUnsupportedSetupException() {
.withFailMessage("test-message");
}
- @DisplayName("from returns IllegalArgumentException if error frame has illegal error code")
+ @DisplayName("from throws NullPointerException with null frame")
@Test
- void fromIllegalErrorFrame() {
- ByteBuf byteBuf = createErrorFrame(0x00000000, "test-message");
-
- assertThat(Exceptions.from(Frame.from(byteBuf)))
- .isInstanceOf(IllegalArgumentException.class)
- .withFailMessage("Invalid Error frame: %d, '%s'", 0, "test-message");
+ void fromWithNullFrame() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> Exceptions.from(null))
+ .withMessage("frame must not be null");
}
private ByteBuf createErrorFrame(int errorCode, String message) {
diff --git a/rsocket-core/src/test/java/io/rsocket/exceptions/RSocketExceptionTest.java b/rsocket-core/src/test/java/io/rsocket/exceptions/RSocketExceptionTest.java
index 1e034b9de..8c39e8250 100644
--- a/rsocket-core/src/test/java/io/rsocket/exceptions/RSocketExceptionTest.java
+++ b/rsocket-core/src/test/java/io/rsocket/exceptions/RSocketExceptionTest.java
@@ -24,12 +24,6 @@
interface RSocketExceptionTest {
- T getException(String message);
-
- T getException(String message, Throwable cause);
-
- int getSpecifiedErrorCode();
-
@DisplayName("constructor throws NullPointerException with null message")
@Test
default void constructorWithNullMessage() {
@@ -51,4 +45,10 @@ default void constructorWithNullMessageAndCause() {
default void errorCodeReturnsSpecifiedValue() {
assertThat(getException("test-message").errorCode()).isEqualTo(getSpecifiedErrorCode());
}
+
+ T getException(String message, Throwable cause);
+
+ T getException(String message);
+
+ int getSpecifiedErrorCode();
}
diff --git a/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationDuplexConnectionTest.java b/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationDuplexConnectionTest.java
index 09d9828aa..7ceb3b20e 100644
--- a/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationDuplexConnectionTest.java
+++ b/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationDuplexConnectionTest.java
@@ -16,131 +16,354 @@
package io.rsocket.fragmentation;
-import static org.mockito.Mockito.any;
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.PayloadFrame.createPayloadFrame;
+import static io.rsocket.framing.RequestStreamFrame.createRequestStreamFrame;
+import static io.rsocket.framing.TestFrames.createTestCancelFrame;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static io.rsocket.util.AbstractionLeakingFrameUtils.toAbstractionLeakingFrame;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+import static org.mockito.Mockito.RETURNS_SMART_NULLS;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import io.netty.buffer.ByteBuf;
import io.rsocket.DuplexConnection;
import io.rsocket.Frame;
-import io.rsocket.FrameType;
-import io.rsocket.util.DefaultPayload;
-import java.nio.ByteBuffer;
-import java.util.concurrent.ThreadLocalRandom;
-import org.junit.Test;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
import org.reactivestreams.Publisher;
-import reactor.core.publisher.EmitterProcessor;
import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
-/** */
-public class FragmentationDuplexConnectionTest {
+final class FragmentationDuplexConnectionTest {
+
+ private final DuplexConnection delegate = mock(DuplexConnection.class, RETURNS_SMART_NULLS);
+
+ @SuppressWarnings("unchecked")
+ private final ArgumentCaptor> publishers =
+ ArgumentCaptor.forClass(Publisher.class);
+
+ @DisplayName("constructor throws NullPointerException with invalid maxFragmentLength")
@Test
- public void testSendOneWithFragmentation() {
- DuplexConnection mockConnection = mock(DuplexConnection.class);
- when(mockConnection.send(any()))
- .then(
- invocation -> {
- Publisher frames = invocation.getArgument(0);
+ void constructorInvalidMaxFragmentSize() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> new FragmentationDuplexConnection(DEFAULT, delegate, 0))
+ .withMessage("maxFragmentSize must be positive");
+ }
- StepVerifier.create(frames).expectNextCount(16).verifyComplete();
+ @DisplayName("constructor throws NullPointerException with null byteBufAllocator")
+ @Test
+ void constructorNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> new FragmentationDuplexConnection(null, delegate, 2))
+ .withMessage("byteBufAllocator must not be null");
+ }
- return Mono.empty();
- });
- when(mockConnection.sendOne(any(Frame.class))).thenReturn(Mono.empty());
+ @DisplayName("constructor throws NullPointerException with null delegate")
+ @Test
+ void constructorNullDelegate() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> new FragmentationDuplexConnection(DEFAULT, null, 2))
+ .withMessage("delegate must not be null");
+ }
- ByteBuffer data = createRandomBytes(16);
- ByteBuffer metadata = createRandomBytes(16);
+ @DisplayName("reassembles data")
+ @Test
+ void reassembleData() {
+ ByteBuf data = getRandomByteBuf(6);
Frame frame =
- Frame.Request.from(1, FrameType.REQUEST_RESPONSE, DefaultPayload.create(data, metadata), 1);
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createRequestStreamFrame(DEFAULT, false, 1, null, data));
+
+ Frame fragment1 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createRequestStreamFrame(DEFAULT, true, 1, null, data.slice(0, 2)));
- FragmentationDuplexConnection duplexConnection =
- new FragmentationDuplexConnection(mockConnection, 2);
+ Frame fragment2 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, true, false, null, data.slice(2, 2)));
- StepVerifier.create(duplexConnection.sendOne(frame)).verifyComplete();
+ Frame fragment3 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, false, false, null, data.slice(4, 2)));
+
+ when(delegate.receive()).thenReturn(Flux.just(fragment1, fragment2, fragment3));
+
+ new FragmentationDuplexConnection(DEFAULT, delegate, 2)
+ .receive()
+ .as(StepVerifier::create)
+ .expectNext(frame)
+ .verifyComplete();
}
+ @DisplayName("reassembles metadata")
@Test
- public void testShouldNotFragment() {
- DuplexConnection mockConnection = mock(DuplexConnection.class);
- when(mockConnection.sendOne(any(Frame.class))).thenReturn(Mono.empty());
+ void reassembleMetadata() {
+ ByteBuf metadata = getRandomByteBuf(6);
+
+ Frame frame =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createRequestStreamFrame(DEFAULT, false, 1, metadata, null));
- ByteBuffer data = createRandomBytes(16);
- ByteBuffer metadata = createRandomBytes(16);
+ Frame fragment1 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createRequestStreamFrame(DEFAULT, true, 1, metadata.slice(0, 2), null));
- Frame frame = Frame.Cancel.from(1);
+ Frame fragment2 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, true, true, metadata.slice(2, 2), null));
- FragmentationDuplexConnection duplexConnection =
- new FragmentationDuplexConnection(mockConnection, 2);
+ Frame fragment3 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, false, true, metadata.slice(4, 2), null));
- StepVerifier.create(duplexConnection.sendOne(frame)).verifyComplete();
+ when(delegate.receive()).thenReturn(Flux.just(fragment1, fragment2, fragment3));
- verify(mockConnection, times(1)).sendOne(frame);
+ new FragmentationDuplexConnection(DEFAULT, delegate, 2)
+ .receive()
+ .as(StepVerifier::create)
+ .expectNext(frame)
+ .verifyComplete();
}
+ @DisplayName("reassembles metadata and data")
@Test
- public void testShouldFragmentMultiple() {
- DuplexConnection mockConnection = mock(DuplexConnection.class);
- when(mockConnection.send(any()))
- .then(
- invocation -> {
- Publisher frames = invocation.getArgument(0);
+ void reassembleMetadataAndData() {
+ ByteBuf metadata = getRandomByteBuf(5);
+ ByteBuf data = getRandomByteBuf(5);
+
+ Frame frame =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createRequestStreamFrame(DEFAULT, false, 1, metadata, data));
- StepVerifier.create(frames).expectNextCount(16).verifyComplete();
+ Frame fragment1 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createRequestStreamFrame(DEFAULT, true, 1, metadata.slice(0, 2), null));
- return Mono.empty();
- });
- when(mockConnection.sendOne(any(Frame.class))).thenReturn(Mono.empty());
+ Frame fragment2 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, true, true, metadata.slice(2, 2), null));
- ByteBuffer data = createRandomBytes(16);
- ByteBuffer metadata = createRandomBytes(16);
+ Frame fragment3 =
+ toAbstractionLeakingFrame(
+ DEFAULT,
+ 1,
+ createPayloadFrame(DEFAULT, true, false, metadata.slice(4, 1), data.slice(0, 1)));
- Frame frame1 =
- Frame.Request.from(1, FrameType.REQUEST_RESPONSE, DefaultPayload.create(data, metadata), 1);
- Frame frame2 =
- Frame.Request.from(2, FrameType.REQUEST_RESPONSE, DefaultPayload.create(data, metadata), 1);
- Frame frame3 =
- Frame.Request.from(3, FrameType.REQUEST_RESPONSE, DefaultPayload.create(data, metadata), 1);
+ Frame fragment4 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, true, false, null, data.slice(1, 2)));
- FragmentationDuplexConnection duplexConnection =
- new FragmentationDuplexConnection(mockConnection, 2);
+ Frame fragment5 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, false, false, null, data.slice(3, 2)));
- StepVerifier.create(duplexConnection.send(Flux.just(frame1, frame2, frame3))).verifyComplete();
+ when(delegate.receive())
+ .thenReturn(Flux.just(fragment1, fragment2, fragment3, fragment4, fragment5));
- verify(mockConnection, times(3)).send(any());
+ new FragmentationDuplexConnection(DEFAULT, delegate, 2)
+ .receive()
+ .as(StepVerifier::create)
+ .expectNext(frame)
+ .verifyComplete();
}
+ @DisplayName("does not reassemble a non-fragment frame")
@Test
- public void testReassembleFragmentFrame() {
- ByteBuffer data = createRandomBytes(16);
- ByteBuffer metadata = createRandomBytes(16);
+ void reassembleNonFragment() {
Frame frame =
- Frame.Request.from(
- 1024, FrameType.REQUEST_RESPONSE, DefaultPayload.create(data, metadata), 1);
- FrameFragmenter frameFragmenter = new FrameFragmenter(2);
- Flux fragmentedFrames = frameFragmenter.fragment(frame);
- EmitterProcessor processor = EmitterProcessor.create(128);
- DuplexConnection mockConnection = mock(DuplexConnection.class);
- when(mockConnection.receive()).then(answer -> processor);
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, false, true, (ByteBuf) null, null));
+
+ when(delegate.receive()).thenReturn(Flux.just(frame));
- FragmentationDuplexConnection duplexConnection =
- new FragmentationDuplexConnection(mockConnection, 2);
+ new FragmentationDuplexConnection(DEFAULT, delegate, 2)
+ .receive()
+ .as(StepVerifier::create)
+ .expectNext(frame)
+ .verifyComplete();
+ }
- fragmentedFrames.subscribe(processor);
+ @DisplayName("does not reassemble non fragmentable frame")
+ @Test
+ void reassembleNonFragmentableFrame() {
+ Frame frame = toAbstractionLeakingFrame(DEFAULT, 1, createTestCancelFrame());
- duplexConnection
+ when(delegate.receive()).thenReturn(Flux.just(frame));
+
+ new FragmentationDuplexConnection(DEFAULT, delegate, 2)
.receive()
- .log()
- .doOnNext(c -> System.out.println("here - " + c.toString()))
- .subscribe();
+ .as(StepVerifier::create)
+ .expectNext(frame)
+ .verifyComplete();
+ }
+
+ @DisplayName("fragments data")
+ @Test
+ void sendData() {
+ ByteBuf data = getRandomByteBuf(6);
+
+ Frame frame =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createRequestStreamFrame(DEFAULT, false, 1, null, data));
+
+ Frame fragment1 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createRequestStreamFrame(DEFAULT, true, 1, null, data.slice(0, 2)));
+
+ Frame fragment2 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, true, false, null, data.slice(2, 2)));
+
+ Frame fragment3 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, false, false, null, data.slice(4, 2)));
+
+ new FragmentationDuplexConnection(DEFAULT, delegate, 2).sendOne(frame);
+ verify(delegate).send(publishers.capture());
+
+ StepVerifier.create(Flux.from(publishers.getValue()))
+ .expectNext(fragment1)
+ .expectNext(fragment2)
+ .expectNext(fragment3)
+ .verifyComplete();
+ }
+
+ @DisplayName("does not fragment with size equal to maxFragmentLength")
+ @Test
+ void sendEqualToMaxFragmentLength() {
+ Frame frame =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, false, false, null, getRandomByteBuf(2)));
+
+ new FragmentationDuplexConnection(DEFAULT, delegate, 2).sendOne(frame);
+ verify(delegate).send(publishers.capture());
+
+ StepVerifier.create(Flux.from(publishers.getValue())).expectNext(frame).verifyComplete();
}
- private ByteBuffer createRandomBytes(int size) {
- byte[] bytes = new byte[size];
- ThreadLocalRandom.current().nextBytes(bytes);
- return ByteBuffer.wrap(bytes);
+ @DisplayName("does not fragment an already-fragmented frame")
+ @Test
+ void sendFragment() {
+ Frame frame =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, true, true, (ByteBuf) null, null));
+
+ new FragmentationDuplexConnection(DEFAULT, delegate, 2).sendOne(frame);
+ verify(delegate).send(publishers.capture());
+
+ StepVerifier.create(Flux.from(publishers.getValue())).expectNext(frame).verifyComplete();
+ }
+
+ @DisplayName("does not fragment with size smaller than maxFragmentLength")
+ @Test
+ void sendLessThanMaxFragmentLength() {
+ Frame frame =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, false, false, null, getRandomByteBuf(1)));
+
+ new FragmentationDuplexConnection(DEFAULT, delegate, 2).sendOne(frame);
+ verify(delegate).send(publishers.capture());
+
+ StepVerifier.create(Flux.from(publishers.getValue())).expectNext(frame).verifyComplete();
+ }
+
+ @DisplayName("fragments metadata")
+ @Test
+ void sendMetadata() {
+ ByteBuf metadata = getRandomByteBuf(6);
+
+ Frame frame =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createRequestStreamFrame(DEFAULT, false, 1, metadata, null));
+
+ Frame fragment1 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createRequestStreamFrame(DEFAULT, true, 1, metadata.slice(0, 2), null));
+
+ Frame fragment2 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, true, true, metadata.slice(2, 2), null));
+
+ Frame fragment3 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, false, true, metadata.slice(4, 2), null));
+
+ new FragmentationDuplexConnection(DEFAULT, delegate, 2).sendOne(frame);
+ verify(delegate).send(publishers.capture());
+
+ StepVerifier.create(Flux.from(publishers.getValue()))
+ .expectNext(fragment1)
+ .expectNext(fragment2)
+ .expectNext(fragment3)
+ .verifyComplete();
+ }
+
+ @DisplayName("fragments metadata and data")
+ @Test
+ void sendMetadataAndData() {
+ ByteBuf metadata = getRandomByteBuf(5);
+ ByteBuf data = getRandomByteBuf(5);
+
+ Frame frame =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createRequestStreamFrame(DEFAULT, false, 1, metadata, data));
+
+ Frame fragment1 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createRequestStreamFrame(DEFAULT, true, 1, metadata.slice(0, 2), null));
+
+ Frame fragment2 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, true, true, metadata.slice(2, 2), null));
+
+ Frame fragment3 =
+ toAbstractionLeakingFrame(
+ DEFAULT,
+ 1,
+ createPayloadFrame(DEFAULT, true, false, metadata.slice(4, 1), data.slice(0, 1)));
+
+ Frame fragment4 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, true, false, null, data.slice(1, 2)));
+
+ Frame fragment5 =
+ toAbstractionLeakingFrame(
+ DEFAULT, 1, createPayloadFrame(DEFAULT, false, false, null, data.slice(3, 2)));
+
+ new FragmentationDuplexConnection(DEFAULT, delegate, 2).sendOne(frame);
+ verify(delegate).send(publishers.capture());
+
+ StepVerifier.create(Flux.from(publishers.getValue()))
+ .expectNext(fragment1)
+ .expectNext(fragment2)
+ .expectNext(fragment3)
+ .expectNext(fragment4)
+ .expectNext(fragment5)
+ .verifyComplete();
+ }
+
+ @DisplayName("does not fragment non-fragmentable frame")
+ @Test
+ void sendNonFragmentable() {
+ Frame frame = toAbstractionLeakingFrame(DEFAULT, 1, createTestCancelFrame());
+
+ new FragmentationDuplexConnection(DEFAULT, delegate, 2).sendOne(frame);
+ verify(delegate).send(publishers.capture());
+
+ StepVerifier.create(Flux.from(publishers.getValue())).expectNext(frame).verifyComplete();
+ }
+
+ @DisplayName("send throws NullPointerException with null frames")
+ @Test
+ void sendNullFrames() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> new FragmentationDuplexConnection(DEFAULT, delegate, 2).send(null))
+ .withMessage("frames must not be null");
}
}
diff --git a/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameFragmenterTest.java b/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameFragmenterTest.java
index f1df5cc17..d69492f3e 100644
--- a/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameFragmenterTest.java
+++ b/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameFragmenterTest.java
@@ -16,70 +16,161 @@
package io.rsocket.fragmentation;
-import io.rsocket.Frame;
-import io.rsocket.FrameType;
-import io.rsocket.util.DefaultPayload;
-import java.nio.ByteBuffer;
-import java.util.concurrent.ThreadLocalRandom;
-import org.junit.Test;
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.PayloadFrame.createPayloadFrame;
+import static io.rsocket.framing.RequestStreamFrame.createRequestStreamFrame;
+import static io.rsocket.framing.TestFrames.createTestCancelFrame;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.rsocket.framing.CancelFrame;
+import io.rsocket.framing.PayloadFrame;
+import io.rsocket.framing.RequestStreamFrame;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
import reactor.test.StepVerifier;
-public class FrameFragmenterTest {
+final class FrameFragmenterTest {
+
+ @DisplayName("constructor throws NullPointerException with null ByteBufAllocator")
+ @Test
+ void constructorNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> new FrameFragmenter(null, 2))
+ .withMessage("byteBufAllocator must not be null");
+ }
+
+ @DisplayName("fragments data")
@Test
- public void testFragmentWithMetadataAndData() {
- ByteBuffer data = createRandomBytes(16);
- ByteBuffer metadata = createRandomBytes(16);
+ void fragmentData() {
+ ByteBuf data = getRandomByteBuf(6);
+
+ RequestStreamFrame frame = createRequestStreamFrame(DEFAULT, false, 1, null, data);
+
+ RequestStreamFrame fragment1 =
+ createRequestStreamFrame(DEFAULT, true, 1, null, data.slice(0, 2));
- Frame from =
- Frame.Request.from(1, FrameType.REQUEST_RESPONSE, DefaultPayload.create(data, metadata), 1);
+ PayloadFrame fragment2 = createPayloadFrame(DEFAULT, true, false, null, data.slice(2, 2));
- FrameFragmenter frameFragmenter = new FrameFragmenter(2);
+ PayloadFrame fragment3 = createPayloadFrame(DEFAULT, false, false, null, data.slice(4, 2));
- StepVerifier.create(frameFragmenter.fragment(from)).expectNextCount(16).verifyComplete();
+ new FrameFragmenter(DEFAULT, 2)
+ .fragment(frame)
+ .as(StepVerifier::create)
+ .expectNext(fragment1)
+ .expectNext(fragment2)
+ .expectNext(fragment3)
+ .verifyComplete();
}
+ @DisplayName("does not fragment with size equal to maxFragmentLength")
@Test
- public void testFragmentWithMetadataAndDataWithOddData() {
- ByteBuffer data = createRandomBytes(17);
- ByteBuffer metadata = createRandomBytes(17);
-
- Frame from =
- Frame.Request.from(1, FrameType.REQUEST_RESPONSE, DefaultPayload.create(data, metadata), 1);
+ void fragmentEqualToMaxFragmentLength() {
+ PayloadFrame frame = createPayloadFrame(DEFAULT, false, false, null, getRandomByteBuf(2));
+
+ new FrameFragmenter(DEFAULT, 2)
+ .fragment(frame)
+ .as(StepVerifier::create)
+ .expectNext(frame)
+ .verifyComplete();
+ }
- FrameFragmenter frameFragmenter = new FrameFragmenter(2);
+ @DisplayName("does not fragment an already-fragmented frame")
+ @Test
+ void fragmentFragment() {
+ PayloadFrame frame = createPayloadFrame(DEFAULT, true, true, (ByteBuf) null, null);
+
+ new FrameFragmenter(DEFAULT, 2)
+ .fragment(frame)
+ .as(StepVerifier::create)
+ .expectNext(frame)
+ .verifyComplete();
+ }
- StepVerifier.create(frameFragmenter.fragment(from)).expectNextCount(17).verifyComplete();
+ @DisplayName("does not fragment with size smaller than maxFragmentLength")
+ @Test
+ void fragmentLessThanMaxFragmentLength() {
+ PayloadFrame frame = createPayloadFrame(DEFAULT, false, false, null, getRandomByteBuf(1));
+
+ new FrameFragmenter(DEFAULT, 2)
+ .fragment(frame)
+ .as(StepVerifier::create)
+ .expectNext(frame)
+ .verifyComplete();
}
+ @DisplayName("fragments metadata")
@Test
- public void testFragmentWithMetadataOnly() {
- ByteBuffer data = ByteBuffer.allocate(0);
- ByteBuffer metadata = createRandomBytes(16);
+ void fragmentMetadata() {
+ ByteBuf metadata = getRandomByteBuf(6);
- Frame from =
- Frame.Request.from(1, FrameType.REQUEST_RESPONSE, DefaultPayload.create(data, metadata), 1);
+ RequestStreamFrame frame = createRequestStreamFrame(DEFAULT, false, 1, metadata, null);
- FrameFragmenter frameFragmenter = new FrameFragmenter(2);
+ RequestStreamFrame fragment1 =
+ createRequestStreamFrame(DEFAULT, true, 1, metadata.slice(0, 2), null);
- StepVerifier.create(frameFragmenter.fragment(from)).expectNextCount(8).verifyComplete();
+ PayloadFrame fragment2 = createPayloadFrame(DEFAULT, true, true, metadata.slice(2, 2), null);
+
+ PayloadFrame fragment3 = createPayloadFrame(DEFAULT, false, true, metadata.slice(4, 2), null);
+
+ new FrameFragmenter(DEFAULT, 2)
+ .fragment(frame)
+ .as(StepVerifier::create)
+ .expectNext(fragment1)
+ .expectNext(fragment2)
+ .expectNext(fragment3)
+ .verifyComplete();
}
+ @DisplayName("fragments metadata and data")
@Test
- public void testFragmentWithDataOnly() {
- ByteBuffer data = createRandomBytes(16);
- ByteBuffer metadata = ByteBuffer.allocate(0);
+ void fragmentMetadataAndData() {
+ ByteBuf metadata = getRandomByteBuf(5);
+ ByteBuf data = getRandomByteBuf(5);
+
+ RequestStreamFrame frame = createRequestStreamFrame(DEFAULT, false, 1, metadata, data);
+
+ RequestStreamFrame fragment1 =
+ createRequestStreamFrame(DEFAULT, true, 1, metadata.slice(0, 2), null);
+
+ PayloadFrame fragment2 = createPayloadFrame(DEFAULT, true, true, metadata.slice(2, 2), null);
- Frame from =
- Frame.Request.from(1, FrameType.REQUEST_RESPONSE, DefaultPayload.create(data, metadata), 1);
+ PayloadFrame fragment3 =
+ createPayloadFrame(DEFAULT, true, false, metadata.slice(4, 1), data.slice(0, 1));
- FrameFragmenter frameFragmenter = new FrameFragmenter(2);
+ PayloadFrame fragment4 = createPayloadFrame(DEFAULT, true, false, null, data.slice(1, 2));
- StepVerifier.create(frameFragmenter.fragment(from)).expectNextCount(8).verifyComplete();
+ PayloadFrame fragment5 = createPayloadFrame(DEFAULT, false, false, null, data.slice(3, 2));
+
+ new FrameFragmenter(DEFAULT, 2)
+ .fragment(frame)
+ .as(StepVerifier::create)
+ .expectNext(fragment1)
+ .expectNext(fragment2)
+ .expectNext(fragment3)
+ .expectNext(fragment4)
+ .expectNext(fragment5)
+ .verifyComplete();
}
- private ByteBuffer createRandomBytes(int size) {
- byte[] bytes = new byte[size];
- ThreadLocalRandom.current().nextBytes(bytes);
- return ByteBuffer.wrap(bytes);
+ @DisplayName("does not fragment non-fragmentable frame")
+ @Test
+ void fragmentNonFragmentable() {
+ CancelFrame frame = createTestCancelFrame();
+
+ new FrameFragmenter(DEFAULT, 2)
+ .fragment(frame)
+ .as(StepVerifier::create)
+ .expectNext(frame)
+ .verifyComplete();
+ }
+
+ @DisplayName("fragment throws NullPointerException with null frame")
+ @Test
+ void fragmentWithNullFrame() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> new FrameFragmenter(DEFAULT, 2).fragment(null))
+ .withMessage("frame must not be null");
}
}
diff --git a/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameReassemblerTest.java b/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameReassemblerTest.java
index 705a82446..05be0aad4 100644
--- a/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameReassemblerTest.java
+++ b/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameReassemblerTest.java
@@ -16,126 +16,124 @@
package io.rsocket.fragmentation;
-import io.rsocket.Frame;
-import io.rsocket.FrameType;
-import io.rsocket.util.DefaultPayload;
-import java.nio.ByteBuffer;
-import java.util.concurrent.ThreadLocalRandom;
-import org.junit.Test;
-
-/** */
-public class FrameReassemblerTest {
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.fragmentation.FrameReassembler.createFrameReassembler;
+import static io.rsocket.framing.PayloadFrame.createPayloadFrame;
+import static io.rsocket.framing.RequestStreamFrame.createRequestStreamFrame;
+import static io.rsocket.framing.TestFrames.createTestCancelFrame;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.rsocket.framing.CancelFrame;
+import io.rsocket.framing.PayloadFrame;
+import io.rsocket.framing.RequestStreamFrame;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+final class FrameReassemblerTest {
+
+ @DisplayName("createFrameReassembler throws NullPointerException")
@Test
- public void testAppend() {
- ByteBuffer data = createRandomBytes(16);
- ByteBuffer metadata = createRandomBytes(16);
-
- Frame from =
- Frame.Request.from(
- 1024, FrameType.REQUEST_RESPONSE, DefaultPayload.create(data, metadata), 1);
- FrameFragmenter frameFragmenter = new FrameFragmenter(2);
- FrameReassembler reassembler = new FrameReassembler(from);
- frameFragmenter.fragment(from).subscribe(reassembler::append);
+ void createFrameReassemblerNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createFrameReassembler(null))
+ .withMessage("byteBufAllocator must not be null");
}
- private ByteBuffer createRandomBytes(int size) {
- byte[] bytes = new byte[size];
- ThreadLocalRandom.current().nextBytes(bytes);
- return ByteBuffer.wrap(bytes);
- }
- /*
- ByteBuffer data = createRandomBytes(16);
- ByteBuffer metadata = createRandomBytes(16);
-
- Frame from = Frame.Request.from(1024, FrameType.REQUEST_RESPONSE, DefaultPayload.create(data, metadata), 1);
+ @DisplayName("reassembles data")
+ @Test
+ void reassembleData() {
+ ByteBuf data = getRandomByteBuf(6);
- FrameFragmenter frameFragmenter = new FrameFragmenter(2);
+ RequestStreamFrame frame = createRequestStreamFrame(DEFAULT, false, 1, null, data);
- FrameReassembler reassembler = new FrameReassembler(2);
+ RequestStreamFrame fragment1 =
+ createRequestStreamFrame(DEFAULT, true, 1, null, data.slice(0, 2));
- frameFragmenter
- .fragment(from)
- .log()
- .doOnNext(reassembler::append)
- .blockLast();
+ PayloadFrame fragment2 = createPayloadFrame(DEFAULT, true, false, null, data.slice(2, 2));
- Frame reassemble = reassembler.reassemble();
+ PayloadFrame fragment3 = createPayloadFrame(DEFAULT, false, false, null, data.slice(4, 2));
- Assert.assertEquals(reassemble.getStreamId(), from.getStreamId());
- Assert.assertEquals(reassemble.getType(), from.getType());
+ FrameReassembler frameReassembler = createFrameReassembler(DEFAULT);
- ByteBuffer reassembleData = reassemble.getData();
- ByteBuffer reassembleMetadata = reassemble.getMetadata();
+ assertThat(frameReassembler.reassemble(fragment1)).isNull();
+ assertThat(frameReassembler.reassemble(fragment2)).isNull();
+ assertThat(frameReassembler.reassemble(fragment3)).isEqualTo(frame);
+ }
- Assert.assertTrue(reassembleData.hasRemaining());
- Assert.assertTrue(reassembleMetadata.hasRemaining());
+ @DisplayName("reassembles metadata")
+ @Test
+ void reassembleMetadata() {
+ ByteBuf metadata = getRandomByteBuf(6);
- while (reassembleData.hasRemaining()) {
- Assert.assertEquals(reassembleData.get(), data.get());
- }
+ RequestStreamFrame frame = createRequestStreamFrame(DEFAULT, false, 1, metadata, null);
- while (reassembleMetadata.hasRemaining()) {
- Assert.assertEquals(reassembleMetadata.get(), metadata.get());
- }
- }
+ RequestStreamFrame fragment1 =
+ createRequestStreamFrame(DEFAULT, true, 1, metadata.slice(0, 2), null);
- @Test
- public void testReassmembleAndClear() {
- ByteBuffer data = createRandomBytes(16);
- ByteBuffer metadata = createRandomBytes(16);
+ PayloadFrame fragment2 = createPayloadFrame(DEFAULT, true, true, metadata.slice(2, 2), null);
- Frame request = Frame.Request.from(1024, FrameType.REQUEST_RESPONSE, DefaultPayload.createdata, metadata), 1);
+ PayloadFrame fragment3 = createPayloadFrame(DEFAULT, false, true, metadata.slice(4, 2), null);
- FrameFragmenter frameFragmenter = new FrameFragmenter(2);
+ FrameReassembler frameReassembler = createFrameReassembler(DEFAULT);
- FrameReassembler reassembler = new FrameReassembler(2);
+ assertThat(frameReassembler.reassemble(fragment1)).isNull();
+ assertThat(frameReassembler.reassemble(fragment2)).isNull();
+ assertThat(frameReassembler.reassemble(fragment3)).isEqualTo(frame);
+ }
- Iterable fragments = frameFragmenter
- .fragment(request)
- .log()
- .map(frame -> frame.content().copy())
- .toIterable();
+ @DisplayName("reassembles metadata and data")
+ @Test
+ void reassembleMetadataAndData() {
+ ByteBuf metadata = getRandomByteBuf(5);
+ ByteBuf data = getRandomByteBuf(5);
- fragments
- .forEach(f -> ByteBufUtil.prettyHexDump(f));
+ RequestStreamFrame frame = createRequestStreamFrame(DEFAULT, false, 1, metadata, data);
+ RequestStreamFrame fragment1 =
+ createRequestStreamFrame(DEFAULT, true, 1, metadata.slice(0, 2), null);
- for (int i = 0; i < 5; i++) {
- for (ByteBuf frame : fragments) {
- reassembler
- .append(Frame.from(frame));
- }
+ PayloadFrame fragment2 = createPayloadFrame(DEFAULT, true, true, metadata.slice(2, 2), null);
- Frame reassemble = reassembler.reassemble();
+ PayloadFrame fragment3 =
+ createPayloadFrame(DEFAULT, true, false, metadata.slice(4, 1), data.slice(0, 1));
- Assert.assertEquals(reassemble.getStreamId(), request.getStreamId());
- Assert.assertEquals(reassemble.getType(), reassemble.getType());
+ PayloadFrame fragment4 = createPayloadFrame(DEFAULT, true, false, null, data.slice(1, 2));
- ByteBuffer reassembleData = reassemble.getData();
- ByteBuffer reassembleMetadata = reassemble.getMetadata();
+ PayloadFrame fragment5 = createPayloadFrame(DEFAULT, false, false, null, data.slice(3, 2));
- Assert.assertTrue(reassembleData.hasRemaining());
- Assert.assertTrue(reassembleMetadata.hasRemaining());
+ FrameReassembler frameReassembler = createFrameReassembler(DEFAULT);
- while (reassembleData.hasRemaining()) {
- Assert.assertEquals(reassembleData.get(), data.get());
- }
+ assertThat(frameReassembler.reassemble(fragment1)).isNull();
+ assertThat(frameReassembler.reassemble(fragment2)).isNull();
+ assertThat(frameReassembler.reassemble(fragment3)).isNull();
+ assertThat(frameReassembler.reassemble(fragment4)).isNull();
+ assertThat(frameReassembler.reassemble(fragment5)).isEqualTo(frame);
+ }
- while (reassembleMetadata.hasRemaining()) {
- Assert.assertEquals(reassembleMetadata.get(), metadata.get());
- }
+ @DisplayName("does not reassemble a non-fragment frame")
+ @Test
+ void reassembleNonFragment() {
+ PayloadFrame frame = createPayloadFrame(DEFAULT, false, true, (ByteBuf) null, null);
- }
+ assertThat(createFrameReassembler(DEFAULT).reassemble(frame)).isEqualTo(frame);
}
+ @DisplayName("does not reassemble non fragmentable frame")
@Test
- public void substring() {
- String s = "1234567890";
- String substring = s.substring(0, 5);
- System.out.println(substring);
- String substring1 = s.substring(5, 10);
- System.out.println(substring1);
+ void reassembleNonFragmentableFrame() {
+ CancelFrame frame = createTestCancelFrame();
+
+ assertThat(createFrameReassembler(DEFAULT).reassemble(frame)).isEqualTo(frame);
}
- */
+ @DisplayName("reassemble throws NullPointerException with null frame")
+ @Test
+ void reassembleNullFrame() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createFrameReassembler(DEFAULT).reassemble(null))
+ .withMessage("frame must not be null");
+ }
}
diff --git a/rsocket-core/src/test/java/io/rsocket/frame/FrameHeaderFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/FrameHeaderFlyweightTest.java
index 6d57ddc9c..c3ccb31ba 100644
--- a/rsocket-core/src/test/java/io/rsocket/frame/FrameHeaderFlyweightTest.java
+++ b/rsocket-core/src/test/java/io/rsocket/frame/FrameHeaderFlyweightTest.java
@@ -23,7 +23,7 @@
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
-import io.rsocket.FrameType;
+import io.rsocket.framing.FrameType;
import org.junit.Test;
public class FrameHeaderFlyweightTest {
@@ -119,7 +119,7 @@ public void streamId() {
@Test
public void typeAndFlag() {
- FrameType frameType = FrameType.FIRE_AND_FORGET;
+ FrameType frameType = FrameType.REQUEST_FNF;
int flags = 0b1110110111;
FrameHeaderFlyweight.encode(
byteBuf, 0, flags, frameType, Unpooled.EMPTY_BUFFER, Unpooled.EMPTY_BUFFER);
@@ -144,7 +144,7 @@ public void typeAndFlagTruncated() {
public void missingMetadataLength() {
for (FrameType frameType : FrameType.values()) {
switch (frameType) {
- case UNDEFINED:
+ case RESERVED:
break;
case CANCEL:
case METADATA_PUSH:
diff --git a/rsocket-core/src/test/java/io/rsocket/frame/RequestFrameFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/RequestFrameFlyweightTest.java
index 185cace97..e7d071058 100644
--- a/rsocket-core/src/test/java/io/rsocket/frame/RequestFrameFlyweightTest.java
+++ b/rsocket-core/src/test/java/io/rsocket/frame/RequestFrameFlyweightTest.java
@@ -23,7 +23,7 @@
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.rsocket.Frame;
-import io.rsocket.FrameType;
+import io.rsocket.framing.FrameType;
import io.rsocket.Payload;
import io.rsocket.util.DefaultPayload;
import java.nio.charset.StandardCharsets;
diff --git a/rsocket-core/src/test/java/io/rsocket/frame/SetupFrameFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/SetupFrameFlyweightTest.java
index 52bb9d92f..68d9940a2 100644
--- a/rsocket-core/src/test/java/io/rsocket/frame/SetupFrameFlyweightTest.java
+++ b/rsocket-core/src/test/java/io/rsocket/frame/SetupFrameFlyweightTest.java
@@ -21,7 +21,7 @@
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
-import io.rsocket.FrameType;
+import io.rsocket.framing.FrameType;
import java.nio.charset.StandardCharsets;
import org.junit.Test;
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/ByteBufRepresentation.java b/rsocket-core/src/test/java/io/rsocket/framing/ByteBufRepresentation.java
new file mode 100644
index 000000000..89b2c128a
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/ByteBufRepresentation.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import org.assertj.core.presentation.StandardRepresentation;
+
+public final class ByteBufRepresentation extends StandardRepresentation {
+
+ @Override
+ protected String fallbackToStringOf(Object object) {
+ if (object instanceof ByteBuf) {
+ return ByteBufUtil.prettyHexDump((ByteBuf) object);
+ }
+
+ return super.fallbackToStringOf(object);
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/CancelFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/CancelFrameTest.java
new file mode 100644
index 000000000..325ee9f97
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/CancelFrameTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.CancelFrame.createCancelFrame;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.Unpooled;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+final class CancelFrameTest implements FrameTest {
+
+ @Override
+ public Function getCreateFrameFromByteBuf() {
+ return CancelFrame::createCancelFrame;
+ }
+
+ @Override
+ public Tuple2 getFrame() {
+ ByteBuf byteBuf = Unpooled.buffer(2).writeShort(0b00100100_00000000);
+ CancelFrame frame = createCancelFrame(byteBuf);
+
+ return Tuples.of(frame, byteBuf);
+ }
+
+ @DisplayName("creates CANCEL frame with ByteBufAllocator")
+ @Test
+ void createCancelFrameByteBufAllocator() {
+ ByteBuf expected = Unpooled.buffer(2).writeShort(0b00100100_00000000);
+
+ assertThat(createCancelFrame(DEFAULT).mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("createCancelFrame throws NullPointerException with null byteBufAllocator")
+ @Test
+ void createCancelFrameNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createCancelFrame((ByteBufAllocator) null))
+ .withMessage("byteBufAllocator must not be null");
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/DataFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/DataFrameTest.java
new file mode 100644
index 000000000..87c291a9b
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/DataFrameTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+
+interface DataFrameTest extends FrameTest {
+
+ @DisplayName("return data as UTF-8")
+ @Test
+ default void getDataAsUtf8() {
+ Tuple2 tuple = getFrameWithData();
+ T frame = tuple.getT1();
+ ByteBuf data = tuple.getT2();
+
+ assertThat(frame.getDataAsUtf8()).isEqualTo(data.toString(UTF_8));
+ }
+
+ @DisplayName("returns empty data as UTF-8")
+ @Test
+ default void getDataAsUtf8Empty() {
+ T frame = getFrameWithEmptyData();
+
+ assertThat(frame.getDataAsUtf8()).isEqualTo("");
+ }
+
+ @DisplayName("returns data length")
+ @Test
+ default void getDataLength() {
+ Tuple2 tuple = getFrameWithData();
+ T frame = tuple.getT1();
+ ByteBuf data = tuple.getT2();
+
+ assertThat(frame.getDataLength()).isEqualTo(data.readableBytes());
+ }
+
+ @DisplayName("returns empty data length")
+ @Test
+ default void getDataLengthEmpty() {
+ T frame = getFrameWithEmptyData();
+
+ assertThat(frame.getDataLength()).isEqualTo(0);
+ }
+
+ Tuple2 getFrameWithData();
+
+ T getFrameWithEmptyData();
+
+ @DisplayName("returns unsafe data")
+ @Test
+ default void getUnsafeData() {
+ Tuple2 tuple = getFrameWithData();
+ T frame = tuple.getT1();
+ ByteBuf data = tuple.getT2();
+
+ assertThat(frame.getUnsafeData()).isEqualTo(data);
+ }
+
+ @DisplayName("returns unsafe empty data")
+ @Test
+ default void getUnsafeDataEmpty() {
+ T frame = getFrameWithEmptyData();
+
+ assertThat(frame.getUnsafeData()).isEqualTo(EMPTY_BUFFER);
+ }
+
+ @DisplayName("maps data")
+ @Test
+ default void mapData() {
+ Tuple2 tuple = getFrameWithData();
+ T frame = tuple.getT1();
+ ByteBuf data = tuple.getT2();
+
+ assertThat(frame.mapData(Function.identity())).isEqualTo(data);
+ }
+
+ @DisplayName("maps empty data")
+ @Test
+ default void mapDataEmpty() {
+ T frame = getFrameWithEmptyData();
+
+ assertThat(frame.mapData(Function.identity())).isEqualTo(EMPTY_BUFFER);
+ }
+
+ @DisplayName("mapData throws NullPointerException with null function")
+ @Test
+ default void mapDataNullFunction() {
+ T frame = getFrameWithEmptyData();
+
+ assertThatNullPointerException()
+ .isThrownBy(() -> frame.mapData(null))
+ .withMessage("function must not be null");
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/ErrorFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/ErrorFrameTest.java
new file mode 100644
index 000000000..450f4db33
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/ErrorFrameTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.ErrorFrame.createErrorFrame;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+final class ErrorFrameTest implements DataFrameTest {
+
+ @Override
+ public Function getCreateFrameFromByteBuf() {
+ return ErrorFrame::createErrorFrame;
+ }
+
+ @Override
+ public Tuple2 getFrame() {
+ ByteBuf byteBuf =
+ Unpooled.buffer(8)
+ .writeShort(0b00101100_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeBytes(getRandomByteBuf(2));
+
+ ErrorFrame frame = createErrorFrame(byteBuf);
+
+ return Tuples.of(frame, byteBuf);
+ }
+
+ @Override
+ public Tuple2 getFrameWithData() {
+ ByteBuf data = getRandomByteBuf(2);
+
+ ErrorFrame frame =
+ createErrorFrame(
+ Unpooled.buffer(8)
+ .writeShort(0b00101100_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeBytes(data, 0, data.readableBytes()));
+
+ return Tuples.of(frame, data);
+ }
+
+ @Override
+ public ErrorFrame getFrameWithEmptyData() {
+ return createErrorFrame(
+ Unpooled.buffer(6)
+ .writeShort(0b00101100_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100));
+ }
+
+ @DisplayName("creates KEEPALIVE frame with data")
+ @Test
+ void createErrorFrameData() {
+ ByteBuf data = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(8)
+ .writeShort(0b00101100_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeBytes(data, 0, data.readableBytes());
+
+ assertThat(createErrorFrame(DEFAULT, 100, data).mapFrame(Function.identity()))
+ .isEqualTo(expected);
+ }
+
+ @DisplayName("creates KEEPALIVE frame without data")
+ @Test
+ void createErrorFrameDataNull() {
+ ByteBuf expected =
+ Unpooled.buffer(6)
+ .writeShort(0b00101100_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100);
+
+ assertThat(createErrorFrame(DEFAULT, 100, (ByteBuf) null).mapFrame(Function.identity()))
+ .isEqualTo(expected);
+ }
+
+ @DisplayName("createErrorFrame throws NullPointerException with null byteBufAllocator")
+ @Test
+ void createErrorFrameNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createErrorFrame(null, 0, EMPTY_BUFFER))
+ .withMessage("byteBufAllocator must not be null");
+ }
+
+ @DisplayName("returns error code")
+ @Test
+ void getErrorCode() {
+ ErrorFrame frame =
+ createErrorFrame(
+ Unpooled.buffer(6)
+ .writeShort(0b00001100_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100));
+
+ assertThat(frame.getErrorCode()).isEqualTo(100);
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/ErrorTypeTest.java b/rsocket-core/src/test/java/io/rsocket/framing/ErrorTypeTest.java
new file mode 100644
index 000000000..b8983db3a
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/ErrorTypeTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+final class ErrorTypeTest {
+
+ @DisplayName("APPLICATION_ERROR characteristics")
+ @Test
+ void applicationError() {
+ assertThat(ErrorType.APPLICATION_ERROR).isEqualTo(0x00000201);
+ }
+
+ @DisplayName("CANCELED characteristics")
+ @Test
+ void canceled() {
+ assertThat(ErrorType.CANCELED).isEqualTo(0x00000203);
+ }
+
+ @DisplayName("CONNECTION_CLOSE characteristics")
+ @Test
+ void connectionClose() {
+ assertThat(ErrorType.CONNECTION_CLOSE).isEqualTo(0x00000102);
+ }
+
+ @DisplayName("INVALID_SETUP characteristics")
+ @Test
+ void connectionError() {
+ assertThat(ErrorType.CONNECTION_ERROR).isEqualTo(0x00000101);
+ }
+
+ @DisplayName("INVALID characteristics")
+ @Test
+ void invalid() {
+ assertThat(ErrorType.INVALID).isEqualTo(0x00000204);
+ }
+
+ @DisplayName("INVALID_SETUP characteristics")
+ @Test
+ void invalidSetup() {
+ assertThat(ErrorType.INVALID_SETUP).isEqualTo(0x00000001);
+ }
+
+ @DisplayName("REJECTED characteristics")
+ @Test
+ void rejected() {
+ assertThat(ErrorType.REJECTED).isEqualTo(0x00000202);
+ }
+
+ @DisplayName("REJECTED_RESUME characteristics")
+ @Test
+ void rejectedResume() {
+ assertThat(ErrorType.REJECTED_RESUME).isEqualTo(0x00000004);
+ }
+
+ @DisplayName("REJECTED_SETUP characteristics")
+ @Test
+ void rejectedSetup() {
+ assertThat(ErrorType.REJECTED_SETUP).isEqualTo(0x00000003);
+ }
+
+ @DisplayName("RESERVED characteristics")
+ @Test
+ void reserved() {
+ assertThat(ErrorType.RESERVED).isEqualTo(0x00000000);
+ }
+
+ @DisplayName("UNSUPPORTED_SETUP characteristics")
+ @Test
+ void unsupportedSetup() {
+ assertThat(ErrorType.UNSUPPORTED_SETUP).isEqualTo(0x00000002);
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/ExtensionFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/ExtensionFrameTest.java
new file mode 100644
index 000000000..a7d4fcdec
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/ExtensionFrameTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.ExtensionFrame.createExtensionFrame;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+final class ExtensionFrameTest implements MetadataAndDataFrameTest {
+
+ @Override
+ public Function getCreateFrameFromByteBuf() {
+ return ExtensionFrame::createExtensionFrame;
+ }
+
+ @Override
+ public Tuple2 getFrame() {
+ ByteBuf byteBuf =
+ Unpooled.buffer(13)
+ .writeShort(0b11111101_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(getRandomByteBuf(2))
+ .writeBytes(getRandomByteBuf(2));
+
+ ExtensionFrame frame = createExtensionFrame(byteBuf);
+
+ return Tuples.of(frame, byteBuf);
+ }
+
+ @Override
+ public Tuple2 getFrameWithData() {
+ ByteBuf metadata = getRandomByteBuf(2);
+ ByteBuf data = getRandomByteBuf(2);
+
+ ExtensionFrame frame =
+ createExtensionFrame(
+ Unpooled.buffer(13)
+ .writeShort(0b11111101_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes())
+ .writeBytes(data, 0, data.readableBytes()));
+
+ return Tuples.of(frame, data);
+ }
+
+ @Override
+ public ExtensionFrame getFrameWithEmptyData() {
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ return createExtensionFrame(
+ Unpooled.buffer(11)
+ .writeShort(0b11111101_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes()));
+ }
+
+ @Override
+ public ExtensionFrame getFrameWithEmptyMetadata() {
+ ByteBuf data = getRandomByteBuf(2);
+
+ return createExtensionFrame(
+ Unpooled.buffer(11)
+ .writeShort(0b11111101_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000000)
+ .writeBytes(data, 0, data.readableBytes()));
+ }
+
+ @Override
+ public Tuple2 getFrameWithMetadata() {
+ ByteBuf metadata = getRandomByteBuf(2);
+ ByteBuf data = getRandomByteBuf(2);
+
+ ExtensionFrame frame =
+ createExtensionFrame(
+ Unpooled.buffer(13)
+ .writeShort(0b11111101_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes())
+ .writeBytes(data, 0, data.readableBytes()));
+
+ return Tuples.of(frame, metadata);
+ }
+
+ @Override
+ public ExtensionFrame getFrameWithoutMetadata() {
+ ByteBuf data = getRandomByteBuf(2);
+
+ return createExtensionFrame(
+ Unpooled.buffer(11)
+ .writeShort(0b11111100_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000000)
+ .writeBytes(data, 0, data.readableBytes()));
+ }
+
+ @DisplayName("creates EXT frame with data")
+ @Test
+ void createExtensionFrameData() {
+ ByteBuf data = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(11)
+ .writeShort(0b11111100_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000000)
+ .writeBytes(data, 0, data.readableBytes());
+
+ assertThat(createExtensionFrame(DEFAULT, false, 100, null, data).mapFrame(Function.identity()))
+ .isEqualTo(expected);
+ }
+
+ @DisplayName("creates EXT frame with ignore flag")
+ @Test
+ void createExtensionFrameIgnore() {
+ ByteBuf expected =
+ Unpooled.buffer(9)
+ .writeShort(0b11111110_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000000);
+
+ assertThat(
+ createExtensionFrame(DEFAULT, true, 100, (ByteBuf) null, null)
+ .mapFrame(Function.identity()))
+ .isEqualTo(expected);
+ }
+
+ @DisplayName("creates EXT frame with metadata")
+ @Test
+ void createExtensionFrameMetadata() {
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(11)
+ .writeShort(0b11111101_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes());
+
+ assertThat(
+ createExtensionFrame(DEFAULT, false, 100, metadata, null).mapFrame(Function.identity()))
+ .isEqualTo(expected);
+ }
+
+ @DisplayName("creates EXT frame with metadata and data")
+ @Test
+ void createExtensionFrameMetadataAndData() {
+ ByteBuf metadata = getRandomByteBuf(2);
+ ByteBuf data = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(13)
+ .writeShort(0b11111101_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes())
+ .writeBytes(data, 0, data.readableBytes());
+
+ assertThat(
+ createExtensionFrame(DEFAULT, false, 100, metadata, data).mapFrame(Function.identity()))
+ .isEqualTo(expected);
+ }
+
+ @DisplayName("createExtensionFrame throws NullPointerException with null byteBufAllocator")
+ @Test
+ void createExtensionFrameNullByteBuf() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createExtensionFrame(null, true, 100, (ByteBuf) null, null))
+ .withMessage("byteBufAllocator must not be null");
+ }
+
+ @DisplayName("returns extended type")
+ @Test
+ void getExtendedType() {
+ ExtensionFrame frame =
+ createExtensionFrame(
+ Unpooled.buffer(9)
+ .writeShort(0b11111100_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.getExtendedType()).isEqualTo(100);
+ }
+
+ @DisplayName("tests ignore flag not set")
+ @Test
+ void isIgnoreFlagSetFalse() {
+ ExtensionFrame frame =
+ createExtensionFrame(
+ Unpooled.buffer(11)
+ .writeShort(0b11111100_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.isIgnoreFlagSet()).isFalse();
+ }
+
+ @DisplayName("tests ignore flag set")
+ @Test
+ void isIgnoreFlagSetTrue() {
+ ExtensionFrame frame =
+ createExtensionFrame(
+ Unpooled.buffer(11)
+ .writeShort(0b11111110_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.isIgnoreFlagSet()).isTrue();
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/FragmentableFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/FragmentableFrameTest.java
new file mode 100644
index 000000000..caad306f2
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/FragmentableFrameTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+interface FragmentableFrameTest extends MetadataAndDataFrameTest {
+
+ @DisplayName("creates fragment")
+ @Test
+ default void createFragment() {
+ ByteBuf metadata = getRandomByteBuf(2);
+ ByteBuf data = getRandomByteBuf(2);
+
+ FragmentableFrame frame =
+ getFrameWithoutFollowsFlagSet().createFragment(DEFAULT, metadata, data);
+
+ assertThat(frame.isFollowsFlagSet()).isTrue();
+ assertThat(frame.mapMetadata(Function.identity())).hasValue(metadata);
+ assertThat(frame.mapData(Function.identity())).isEqualTo(data);
+ }
+
+ @DisplayName("createFragment throws NullPointerException with null ByteBufAllocator")
+ @Test
+ default void createInitialFragmentNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> getFrameWithoutFollowsFlagSet().createFragment(null, null, null))
+ .withMessage("byteBufAllocator must not be null");
+ }
+
+ @DisplayName("creates non-fragment")
+ @Test
+ default void createNonFragment() {
+ ByteBuf metadata = getRandomByteBuf(2);
+ ByteBuf data = getRandomByteBuf(2);
+
+ FragmentableFrame frame =
+ getFrameWithoutFollowsFlagSet().createNonFragment(DEFAULT, metadata, data);
+
+ assertThat(frame.isFollowsFlagSet()).isFalse();
+ assertThat(frame.mapMetadata(Function.identity())).hasValue(metadata);
+ assertThat(frame.mapData(Function.identity())).isEqualTo(data);
+ }
+
+ T getFrameWithFollowsFlagSet();
+
+ T getFrameWithoutFollowsFlagSet();
+
+ @DisplayName("tests follows flag not set")
+ @Test
+ default void isFollowFlagSetFalse() {
+ assertThat(getFrameWithoutFollowsFlagSet().isFollowsFlagSet()).isFalse();
+ }
+
+ @DisplayName("tests follows flag set")
+ @Test
+ default void isFollowFlagSetTrue() {
+ assertThat(getFrameWithFollowsFlagSet().isFollowsFlagSet()).isTrue();
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/FrameFactoryTest.java b/rsocket-core/src/test/java/io/rsocket/framing/FrameFactoryTest.java
new file mode 100644
index 000000000..805707177
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/FrameFactoryTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.rsocket.framing.FrameFactory.createFrame;
+import static io.rsocket.framing.TestFrames.createTestCancelFrame;
+import static io.rsocket.framing.TestFrames.createTestErrorFrame;
+import static io.rsocket.framing.TestFrames.createTestExtensionFrame;
+import static io.rsocket.framing.TestFrames.createTestKeepaliveFrame;
+import static io.rsocket.framing.TestFrames.createTestLeaseFrame;
+import static io.rsocket.framing.TestFrames.createTestMetadataPushFrame;
+import static io.rsocket.framing.TestFrames.createTestPayloadFrame;
+import static io.rsocket.framing.TestFrames.createTestRequestChannelFrame;
+import static io.rsocket.framing.TestFrames.createTestRequestFireAndForgetFrame;
+import static io.rsocket.framing.TestFrames.createTestRequestNFrame;
+import static io.rsocket.framing.TestFrames.createTestRequestResponseFrame;
+import static io.rsocket.framing.TestFrames.createTestRequestStreamFrame;
+import static io.rsocket.framing.TestFrames.createTestResumeFrame;
+import static io.rsocket.framing.TestFrames.createTestResumeOkFrame;
+import static io.rsocket.framing.TestFrames.createTestSetupFrame;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+final class FrameFactoryTest {
+
+ @DisplayName("creates CANCEL frame")
+ @Test
+ void createFrameCancel() {
+ createTestCancelFrame()
+ .consumeFrame(byteBuf -> assertThat(createFrame(byteBuf)).isInstanceOf(CancelFrame.class));
+ }
+
+ @DisplayName("creates ERROR frame")
+ @Test
+ void createFrameError() {
+ createTestErrorFrame()
+ .consumeFrame(byteBuf -> assertThat(createFrame(byteBuf)).isInstanceOf(ErrorFrame.class));
+ }
+
+ @DisplayName("creates EXT frame")
+ @Test
+ void createFrameExtension() {
+ createTestExtensionFrame()
+ .consumeFrame(
+ byteBuf -> assertThat(createFrame(byteBuf)).isInstanceOf(ExtensionFrame.class));
+ }
+
+ @DisplayName("creates KEEPALIVE frame")
+ @Test
+ void createFrameKeepalive() {
+ createTestKeepaliveFrame()
+ .consumeFrame(
+ byteBuf -> assertThat(createFrame(byteBuf)).isInstanceOf(KeepaliveFrame.class));
+ }
+
+ @DisplayName("creates METADATA_PUSH frame")
+ @Test
+ void createFrameMetadataPush() {
+ createTestMetadataPushFrame()
+ .consumeFrame(
+ byteBuf -> assertThat(createFrame(byteBuf)).isInstanceOf(MetadataPushFrame.class));
+ }
+
+ @DisplayName("creates PAYLOAD frame")
+ @Test
+ void createFramePayload() {
+ createTestPayloadFrame()
+ .consumeFrame(byteBuf -> assertThat(createFrame(byteBuf)).isInstanceOf(PayloadFrame.class));
+ }
+
+ @DisplayName("creates REQUEST_CHANNEL frame")
+ @Test
+ void createFrameRequestChannel() {
+ createTestRequestChannelFrame()
+ .consumeFrame(
+ byteBuf -> assertThat(createFrame(byteBuf)).isInstanceOf(RequestChannelFrame.class));
+ }
+
+ @DisplayName("creates REQUEST_FNF frame")
+ @Test
+ void createFrameRequestFireAndForget() {
+ createTestRequestFireAndForgetFrame()
+ .consumeFrame(
+ byteBuf ->
+ assertThat(createFrame(byteBuf)).isInstanceOf(RequestFireAndForgetFrame.class));
+ }
+
+ @DisplayName("creates REQUEST_N frame")
+ @Test
+ void createFrameRequestN() {
+ createTestRequestNFrame()
+ .consumeFrame(
+ byteBuf -> assertThat(createFrame(byteBuf)).isInstanceOf(RequestNFrame.class));
+ }
+
+ @DisplayName("creates REQUEST_RESPONSE frame")
+ @Test
+ void createFrameRequestResponse() {
+ createTestRequestResponseFrame()
+ .consumeFrame(
+ byteBuf -> assertThat(createFrame(byteBuf)).isInstanceOf(RequestResponseFrame.class));
+ }
+
+ @DisplayName("creates REQUEST_STREAM frame")
+ @Test
+ void createFrameRequestStream() {
+ createTestRequestStreamFrame()
+ .consumeFrame(
+ byteBuf -> assertThat(createFrame(byteBuf)).isInstanceOf(RequestStreamFrame.class));
+ }
+
+ @DisplayName("creates RESUME frame")
+ @Test
+ void createFrameResume() {
+ createTestResumeFrame()
+ .consumeFrame(byteBuf -> assertThat(createFrame(byteBuf)).isInstanceOf(ResumeFrame.class));
+ }
+
+ @DisplayName("creates RESUME_OK frame")
+ @Test
+ void createFrameResumeOk() {
+ createTestResumeOkFrame()
+ .consumeFrame(
+ byteBuf -> assertThat(createFrame(byteBuf)).isInstanceOf(ResumeOkFrame.class));
+ }
+
+ @DisplayName("creates SETUP frame")
+ @Test
+ void createFrameSetup() {
+ createTestSetupFrame()
+ .consumeFrame(byteBuf -> assertThat(createFrame(byteBuf)).isInstanceOf(SetupFrame.class));
+ }
+
+ @DisplayName("creates LEASE frame")
+ @Test
+ void createLeaseSetup() {
+ createTestLeaseFrame()
+ .consumeFrame(byteBuf -> assertThat(createFrame(byteBuf)).isInstanceOf(LeaseFrame.class));
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/FrameLengthFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/FrameLengthFrameTest.java
new file mode 100644
index 000000000..4de77dcb3
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/FrameLengthFrameTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.FrameLengthFrame.createFrameLengthFrame;
+import static io.rsocket.framing.TestFrames.createTestCancelFrame;
+import static io.rsocket.framing.TestFrames.createTestFrame;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+final class FrameLengthFrameTest implements FrameTest {
+
+ @Override
+ public Function getCreateFrameFromByteBuf() {
+ return FrameLengthFrame::createFrameLengthFrame;
+ }
+
+ @Override
+ public Tuple2 getFrame() {
+ ByteBuf byteBuf =
+ Unpooled.buffer(5)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(getRandomByteBuf(2));
+
+ FrameLengthFrame frame = createFrameLengthFrame(byteBuf);
+
+ return Tuples.of(frame, byteBuf);
+ }
+
+ @DisplayName("creates frame length frame with ByteBufAllocator")
+ @Test
+ void createFrameLengthFrameByteBufAllocator() {
+ ByteBuf frame = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(5)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(frame, 0, frame.readableBytes());
+
+ assertThat(
+ createFrameLengthFrame(DEFAULT, createTestFrame(frame)).mapFrame(Function.identity()))
+ .isEqualTo(expected);
+ }
+
+ @DisplayName("createFrameLengthFrame throws NullPointerException with null byteBufAllocator")
+ @Test
+ void createFrameLengthFrameNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createFrameLengthFrame(null, createTestCancelFrame()))
+ .withMessage("byteBufAllocator must not be null");
+ }
+
+ @DisplayName("returns frame length")
+ @Test
+ void getFrameLength() {
+ FrameLengthFrame frame =
+ createFrameLengthFrame(Unpooled.buffer(3).writeMedium(0b00000000_00000000_01100100));
+
+ assertThat(frame.getFrameLength()).isEqualTo(100);
+ }
+
+ @DisplayName("maps byteBuf without frame length")
+ @Test
+ void mapFrameWithoutFrameLength() {
+ ByteBuf frame = getRandomByteBuf(2);
+
+ FrameLengthFrame frameLengthFrame =
+ createFrameLengthFrame(
+ Unpooled.buffer(5)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(frame, 0, frame.readableBytes()));
+
+ assertThat(frameLengthFrame.mapFrameWithoutFrameLength(Function.identity())).isEqualTo(frame);
+ }
+
+ @DisplayName("mapFrameWithoutFrameLength throws NullPointerException with null function")
+ @Test
+ void mapFrameWithoutFrameLengthNullFunction() {
+ ByteBuf frame = getRandomByteBuf(2);
+
+ FrameLengthFrame frameLengthFrame =
+ createFrameLengthFrame(
+ Unpooled.buffer(5)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(frame, 0, frame.readableBytes()));
+
+ assertThatNullPointerException()
+ .isThrownBy(() -> frameLengthFrame.mapFrameWithoutFrameLength(null))
+ .withMessage("function must not be null");
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/FrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/FrameTest.java
new file mode 100644
index 000000000..ef6f5335b
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/FrameTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+
+interface FrameTest {
+
+ @DisplayName("consumes frame")
+ @Test
+ default void consumeFrame() {
+ Tuple2 tuple = getFrame();
+ T frame = tuple.getT1();
+ ByteBuf byteBuf = tuple.getT2();
+
+ frame.consumeFrame(frameByteBuf -> assertThat(frameByteBuf).isEqualTo(byteBuf));
+ }
+
+ @DisplayName("consumeFrame throws NullPointerException with null function")
+ @Test
+ default void consumeFrameNullFunction() {
+ Tuple2 tuple = getFrame();
+ T frame = tuple.getT1();
+
+ assertThatNullPointerException()
+ .isThrownBy(() -> frame.consumeFrame(null))
+ .withMessage("consumer must not be null");
+ }
+
+ @DisplayName("creates frame from ByteBuf")
+ @Test
+ default void createFrameFromByteBuf() {
+ Tuple2 tuple = getFrame();
+ T frame = tuple.getT1();
+ ByteBuf byteBuf = tuple.getT2();
+
+ assertThat(getCreateFrameFromByteBuf().apply(byteBuf)).isEqualTo(frame);
+ }
+
+ @DisplayName("create frame from ByteBuf throws NullPointerException with null ByteBuf")
+ @Test
+ default void createFrameFromByteBufNullByteBuf() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> getCreateFrameFromByteBuf().apply(null))
+ .withMessage("byteBuf must not be null");
+ }
+
+ Function getCreateFrameFromByteBuf();
+
+ Tuple2 getFrame();
+
+ @DisplayName("maps frame")
+ @Test
+ default void mapFrame() {
+ Tuple2 tuple = getFrame();
+ T frame = tuple.getT1();
+ ByteBuf byteBuf = tuple.getT2();
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(byteBuf);
+ }
+
+ @DisplayName("mapFrame throws NullPointerException with null function")
+ @Test
+ default void mapFrameNullFunction() {
+ Tuple2 tuple = getFrame();
+ T frame = tuple.getT1();
+
+ assertThatNullPointerException()
+ .isThrownBy(() -> frame.mapFrame(null))
+ .withMessage("function must not be null");
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/FrameTypeTest.java b/rsocket-core/src/test/java/io/rsocket/framing/FrameTypeTest.java
new file mode 100644
index 000000000..1364c9038
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/FrameTypeTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+final class FrameTypeTest {
+
+ @DisplayName("CANCEL characteristics")
+ @Test
+ void cancel() {
+ assertThat(FrameType.CANCEL.canHaveData()).isFalse();
+ assertThat(FrameType.CANCEL.canHaveMetadata()).isFalse();
+ assertThat(FrameType.CANCEL.hasInitialRequestN()).isFalse();
+ assertThat(FrameType.CANCEL.getEncodedType()).isEqualTo(0x09);
+ assertThat(FrameType.CANCEL.isFragmentable()).isFalse();
+ assertThat(FrameType.CANCEL.isRequestType()).isFalse();
+ }
+
+ @DisplayName("COMPLETE characteristics")
+ @Test
+ void complete() {
+ assertThat(FrameType.COMPLETE.canHaveData()).isFalse();
+ assertThat(FrameType.COMPLETE.canHaveMetadata()).isFalse();
+ assertThat(FrameType.COMPLETE.hasInitialRequestN()).isFalse();
+ assertThat(FrameType.COMPLETE.getEncodedType()).isEqualTo(0xB0);
+ assertThat(FrameType.COMPLETE.isFragmentable()).isFalse();
+ assertThat(FrameType.COMPLETE.isRequestType()).isFalse();
+ }
+
+ @DisplayName("ERROR characteristics")
+ @Test
+ void error() {
+ assertThat(FrameType.ERROR.canHaveData()).isTrue();
+ assertThat(FrameType.ERROR.canHaveMetadata()).isFalse();
+ assertThat(FrameType.ERROR.getEncodedType()).isEqualTo(0x0B);
+ assertThat(FrameType.ERROR.hasInitialRequestN()).isFalse();
+ assertThat(FrameType.ERROR.isFragmentable()).isFalse();
+ assertThat(FrameType.ERROR.isRequestType()).isFalse();
+ }
+
+ @DisplayName("EXT characteristics")
+ @Test
+ void ext() {
+ assertThat(FrameType.EXT.canHaveData()).isTrue();
+ assertThat(FrameType.EXT.canHaveMetadata()).isTrue();
+ assertThat(FrameType.EXT.hasInitialRequestN()).isFalse();
+ assertThat(FrameType.EXT.getEncodedType()).isEqualTo(0x3F);
+ assertThat(FrameType.EXT.isFragmentable()).isFalse();
+ assertThat(FrameType.EXT.isRequestType()).isFalse();
+ }
+
+ @DisplayName("KEEPALIVE characteristics")
+ @Test
+ void keepAlive() {
+ assertThat(FrameType.KEEPALIVE.canHaveData()).isTrue();
+ assertThat(FrameType.KEEPALIVE.canHaveMetadata()).isFalse();
+ assertThat(FrameType.KEEPALIVE.getEncodedType()).isEqualTo(0x03);
+ assertThat(FrameType.KEEPALIVE.hasInitialRequestN()).isFalse();
+ assertThat(FrameType.KEEPALIVE.isFragmentable()).isFalse();
+ assertThat(FrameType.KEEPALIVE.isRequestType()).isFalse();
+ }
+
+ @DisplayName("LEASE characteristics")
+ @Test
+ void lease() {
+ assertThat(FrameType.LEASE.canHaveData()).isFalse();
+ assertThat(FrameType.LEASE.canHaveMetadata()).isTrue();
+ assertThat(FrameType.LEASE.getEncodedType()).isEqualTo(0x02);
+ assertThat(FrameType.LEASE.hasInitialRequestN()).isFalse();
+ assertThat(FrameType.LEASE.isFragmentable()).isFalse();
+ assertThat(FrameType.LEASE.isRequestType()).isFalse();
+ }
+
+ @DisplayName("METADATA_PUSH characteristics")
+ @Test
+ void metadataPush() {
+ assertThat(FrameType.METADATA_PUSH.canHaveData()).isFalse();
+ assertThat(FrameType.METADATA_PUSH.canHaveMetadata()).isTrue();
+ assertThat(FrameType.METADATA_PUSH.hasInitialRequestN()).isFalse();
+ assertThat(FrameType.METADATA_PUSH.getEncodedType()).isEqualTo(0x0C);
+ assertThat(FrameType.METADATA_PUSH.isFragmentable()).isFalse();
+ assertThat(FrameType.METADATA_PUSH.isRequestType()).isFalse();
+ }
+
+ @DisplayName("NEXT characteristics")
+ @Test
+ void next() {
+ assertThat(FrameType.NEXT.canHaveData()).isTrue();
+ assertThat(FrameType.NEXT.canHaveMetadata()).isTrue();
+ assertThat(FrameType.NEXT.hasInitialRequestN()).isFalse();
+ assertThat(FrameType.NEXT.getEncodedType()).isEqualTo(0xA0);
+ assertThat(FrameType.NEXT.isFragmentable()).isTrue();
+ assertThat(FrameType.NEXT.isRequestType()).isFalse();
+ }
+
+ @DisplayName("NEXT_COMPLETE characteristics")
+ @Test
+ void nextComplete() {
+ assertThat(FrameType.NEXT_COMPLETE.canHaveData()).isTrue();
+ assertThat(FrameType.NEXT_COMPLETE.canHaveMetadata()).isTrue();
+ assertThat(FrameType.NEXT_COMPLETE.hasInitialRequestN()).isFalse();
+ assertThat(FrameType.NEXT_COMPLETE.getEncodedType()).isEqualTo(0xC0);
+ assertThat(FrameType.NEXT_COMPLETE.isFragmentable()).isTrue();
+ assertThat(FrameType.NEXT_COMPLETE.isRequestType()).isFalse();
+ }
+
+ @DisplayName("PAYLOAD characteristics")
+ @Test
+ void payload() {
+ assertThat(FrameType.PAYLOAD.canHaveData()).isTrue();
+ assertThat(FrameType.PAYLOAD.canHaveMetadata()).isTrue();
+ assertThat(FrameType.PAYLOAD.hasInitialRequestN()).isFalse();
+ assertThat(FrameType.PAYLOAD.getEncodedType()).isEqualTo(0x0A);
+ assertThat(FrameType.PAYLOAD.isFragmentable()).isTrue();
+ assertThat(FrameType.PAYLOAD.isRequestType()).isFalse();
+ }
+
+ @DisplayName("REQUEST_CHANNEL characteristics")
+ @Test
+ void requestChannel() {
+ assertThat(FrameType.REQUEST_CHANNEL.canHaveData()).isTrue();
+ assertThat(FrameType.REQUEST_CHANNEL.canHaveMetadata()).isTrue();
+ assertThat(FrameType.REQUEST_CHANNEL.getEncodedType()).isEqualTo(0x07);
+ assertThat(FrameType.REQUEST_CHANNEL.hasInitialRequestN()).isTrue();
+ assertThat(FrameType.REQUEST_CHANNEL.isFragmentable()).isTrue();
+ assertThat(FrameType.REQUEST_CHANNEL.isRequestType()).isTrue();
+ }
+
+ @DisplayName("REQUEST_FNF characteristics")
+ @Test
+ void requestFnf() {
+ assertThat(FrameType.REQUEST_FNF.canHaveData()).isTrue();
+ assertThat(FrameType.REQUEST_FNF.canHaveMetadata()).isTrue();
+ assertThat(FrameType.REQUEST_FNF.getEncodedType()).isEqualTo(0x05);
+ assertThat(FrameType.REQUEST_FNF.hasInitialRequestN()).isFalse();
+ assertThat(FrameType.REQUEST_FNF.isFragmentable()).isTrue();
+ assertThat(FrameType.REQUEST_FNF.isRequestType()).isTrue();
+ }
+
+ @DisplayName("REQUEST_N characteristics")
+ @Test
+ void requestN() {
+ assertThat(FrameType.REQUEST_N.canHaveData()).isFalse();
+ assertThat(FrameType.REQUEST_N.canHaveMetadata()).isFalse();
+ assertThat(FrameType.REQUEST_N.getEncodedType()).isEqualTo(0x08);
+ assertThat(FrameType.REQUEST_N.hasInitialRequestN()).isFalse();
+ assertThat(FrameType.REQUEST_N.isFragmentable()).isFalse();
+ assertThat(FrameType.REQUEST_N.isRequestType()).isFalse();
+ }
+
+ @DisplayName("REQUEST_RESPONSE characteristics")
+ @Test
+ void requestResponse() {
+ assertThat(FrameType.REQUEST_RESPONSE.canHaveData()).isTrue();
+ assertThat(FrameType.REQUEST_RESPONSE.canHaveMetadata()).isTrue();
+ assertThat(FrameType.REQUEST_RESPONSE.getEncodedType()).isEqualTo(0x04);
+ assertThat(FrameType.REQUEST_RESPONSE.hasInitialRequestN()).isFalse();
+ assertThat(FrameType.REQUEST_RESPONSE.isFragmentable()).isTrue();
+ assertThat(FrameType.REQUEST_RESPONSE.isRequestType()).isTrue();
+ }
+
+ @DisplayName("REQUEST_STREAM characteristics")
+ @Test
+ void requestStream() {
+ assertThat(FrameType.REQUEST_STREAM.canHaveData()).isTrue();
+ assertThat(FrameType.REQUEST_STREAM.canHaveMetadata()).isTrue();
+ assertThat(FrameType.REQUEST_STREAM.getEncodedType()).isEqualTo(0x06);
+ assertThat(FrameType.REQUEST_STREAM.hasInitialRequestN()).isTrue();
+ assertThat(FrameType.REQUEST_STREAM.isFragmentable()).isTrue();
+ assertThat(FrameType.REQUEST_STREAM.isRequestType()).isTrue();
+ }
+
+ @DisplayName("RESERVED characteristics")
+ @Test
+ void reserved() {
+ assertThat(FrameType.RESERVED.canHaveData()).isFalse();
+ assertThat(FrameType.RESERVED.canHaveMetadata()).isFalse();
+ assertThat(FrameType.RESERVED.hasInitialRequestN()).isFalse();
+ assertThat(FrameType.RESERVED.getEncodedType()).isEqualTo(0x00);
+ assertThat(FrameType.RESERVED.isFragmentable()).isFalse();
+ assertThat(FrameType.RESERVED.isRequestType()).isFalse();
+ }
+
+ @DisplayName("SETUP characteristics")
+ @Test
+ void setup() {
+ assertThat(FrameType.SETUP.canHaveData()).isTrue();
+ assertThat(FrameType.SETUP.canHaveMetadata()).isTrue();
+ assertThat(FrameType.SETUP.getEncodedType()).isEqualTo(0x01);
+ assertThat(FrameType.SETUP.hasInitialRequestN()).isFalse();
+ assertThat(FrameType.SETUP.isFragmentable()).isFalse();
+ assertThat(FrameType.SETUP.isRequestType()).isFalse();
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/KeepaliveFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/KeepaliveFrameTest.java
new file mode 100644
index 000000000..455285a5e
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/KeepaliveFrameTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.KeepaliveFrame.createKeepaliveFrame;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+final class KeepaliveFrameTest implements DataFrameTest {
+
+ @Override
+ public Function getCreateFrameFromByteBuf() {
+ return KeepaliveFrame::createKeepaliveFrame;
+ }
+
+ @Override
+ public Tuple2 getFrame() {
+ ByteBuf byteBuf =
+ Unpooled.buffer(12)
+ .writeShort(0b00001100_00000000)
+ .writeLong(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_01100100)
+ .writeBytes(getRandomByteBuf(2));
+
+ KeepaliveFrame frame = createKeepaliveFrame(byteBuf);
+
+ return Tuples.of(frame, byteBuf);
+ }
+
+ @Override
+ public Tuple2 getFrameWithData() {
+ ByteBuf data = getRandomByteBuf(2);
+
+ KeepaliveFrame frame =
+ createKeepaliveFrame(
+ Unpooled.buffer(12)
+ .writeShort(0b00001100_00000000)
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_01100100)
+ .writeBytes(data, 0, data.readableBytes()));
+
+ return Tuples.of(frame, data);
+ }
+
+ @Override
+ public KeepaliveFrame getFrameWithEmptyData() {
+ return createKeepaliveFrame(
+ Unpooled.buffer(10)
+ .writeShort(0b00001100_00000000)
+ .writeLong(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_01100100));
+ }
+
+ @DisplayName("creates KEEPALIVE frame with data")
+ @Test
+ void createKeepAliveFrameData() {
+ ByteBuf data = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(12)
+ .writeShort(0b00001100_00000000)
+ .writeLong(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_01100100)
+ .writeBytes(data, 0, data.readableBytes());
+
+ assertThat(createKeepaliveFrame(DEFAULT, false, 100, data).mapFrame(Function.identity()))
+ .isEqualTo(expected);
+ }
+
+ @DisplayName("creates KEEPALIVE frame without data")
+ @Test
+ void createKeepAliveFrameDataNull() {
+ ByteBuf expected =
+ Unpooled.buffer(10)
+ .writeShort(0b00001100_00000000)
+ .writeLong(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_01100100);
+
+ assertThat(createKeepaliveFrame(DEFAULT, false, 100, null).mapFrame(Function.identity()))
+ .isEqualTo(expected);
+ }
+
+ @DisplayName("creates KEEPALIVE frame without respond flag set")
+ @Test
+ void createKeepAliveFrameRespondFalse() {
+ ByteBuf expected =
+ Unpooled.buffer(10)
+ .writeShort(0b00001100_00000000)
+ .writeLong(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_01100100);
+
+ assertThat(createKeepaliveFrame(DEFAULT, false, 100, null).mapFrame(Function.identity()))
+ .isEqualTo(expected);
+ }
+
+ @DisplayName("creates KEEPALIVE frame with respond flag set")
+ @Test
+ void createKeepAliveFrameRespondTrue() {
+ ByteBuf expected =
+ Unpooled.buffer(10)
+ .writeShort(0b00001100_10000000)
+ .writeLong(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_01100100);
+
+ assertThat(createKeepaliveFrame(DEFAULT, true, 100, null).mapFrame(Function.identity()))
+ .isEqualTo(expected);
+ }
+
+ @DisplayName("createKeepaliveFrame throws NullPointerException with null byteBufAllocator")
+ @Test
+ void createKeepaliveFrameNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createKeepaliveFrame(null, true, 100, null))
+ .withMessage("byteBufAllocator must not be null");
+ }
+
+ @DisplayName("returns last received position")
+ @Test
+ void getLastReceivedPosition() {
+ KeepaliveFrame frame =
+ createKeepaliveFrame(
+ Unpooled.buffer(10)
+ .writeShort(0b00001100_00000000)
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_01100100));
+
+ assertThat(frame.getLastReceivedPosition()).isEqualTo(100);
+ }
+
+ @DisplayName("tests respond flag not set")
+ @Test
+ void isRespondFlagSetFalse() {
+ KeepaliveFrame frame =
+ createKeepaliveFrame(
+ Unpooled.buffer(10)
+ .writeShort(0b00001100_00000000)
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_01100100));
+
+ assertThat(frame.isRespondFlagSet()).isFalse();
+ }
+
+ @DisplayName("tests respond flag set")
+ @Test
+ void isRespondFlagSetTrue() {
+ KeepaliveFrame frame =
+ createKeepaliveFrame(
+ Unpooled.buffer(10)
+ .writeShort(0b00001100_10000000)
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_01100100));
+
+ assertThat(frame.isRespondFlagSet()).isTrue();
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/LeaseFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/LeaseFrameTest.java
new file mode 100644
index 000000000..25417f166
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/LeaseFrameTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.LeaseFrame.createLeaseFrame;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.time.Duration;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+final class LeaseFrameTest implements MetadataFrameTest {
+
+ @Override
+ public Function getCreateFrameFromByteBuf() {
+ return LeaseFrame::createLeaseFrame;
+ }
+
+ @Override
+ public Tuple2 getFrame() {
+ ByteBuf byteBuf =
+ Unpooled.buffer(12)
+ .writeShort(0b00001001_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeInt(0b00000000_00000000_00000000_11001000)
+ .writeBytes(getRandomByteBuf(2));
+
+ LeaseFrame frame = createLeaseFrame(byteBuf);
+
+ return Tuples.of(frame, byteBuf);
+ }
+
+ @Override
+ public LeaseFrame getFrameWithEmptyMetadata() {
+ return createLeaseFrame(
+ Unpooled.buffer(10)
+ .writeShort(0b00001001_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeInt(0b00000000_00000000_00000000_11001000));
+ }
+
+ @Override
+ public Tuple2 getFrameWithMetadata() {
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ LeaseFrame frame =
+ createLeaseFrame(
+ Unpooled.buffer(12)
+ .writeShort(0b00001001_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeInt(0b00000000_00000000_00000000_11001000)
+ .writeBytes(metadata, 0, metadata.readableBytes()));
+
+ return Tuples.of(frame, metadata);
+ }
+
+ @Override
+ public LeaseFrame getFrameWithoutMetadata() {
+ return createLeaseFrame(
+ Unpooled.buffer(10)
+ .writeShort(0b00001000_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeInt(0b00000000_00000000_00000000_11001000));
+ }
+
+ @DisplayName("createLeaseFrame throws IllegalArgumentException with invalid numberOfRequests")
+ @Test
+ void createLeaseFrameFrameInvalidNumberOfRequests() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> createLeaseFrame(DEFAULT, Duration.ofMillis(1), 0, null))
+ .withMessage("numberOfRequests must be positive");
+ }
+
+ @DisplayName("createLeaseFrame throws IllegalArgumentException with invalid timeToLive")
+ @Test
+ void createLeaseFrameInvalidTimeToLive() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> createLeaseFrame(DEFAULT, Duration.ofMillis(0), 1, null))
+ .withMessage("timeToLive must be a positive duration");
+ }
+
+ @DisplayName("creates LEASE frame with metadata")
+ @Test
+ void createLeaseFrameMetadata() {
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(12)
+ .writeShort(0b00001001_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeInt(0b00000000_00000000_00000000_11001000)
+ .writeBytes(metadata, 0, metadata.readableBytes());
+
+ assertThat(
+ createLeaseFrame(DEFAULT, Duration.ofMillis(100), 200, metadata)
+ .mapFrame(Function.identity()))
+ .isEqualTo(expected);
+ }
+
+ @DisplayName("creates LEASE frame without metadata")
+ @Test
+ void createLeaseFrameNoMetadata() {
+ ByteBuf expected =
+ Unpooled.buffer(10)
+ .writeShort(0b00001000_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeInt(0b00000000_00000000_00000000_11001000);
+
+ assertThat(
+ createLeaseFrame(DEFAULT, Duration.ofMillis(100), 200, null)
+ .mapFrame(Function.identity()))
+ .isEqualTo(expected);
+ }
+
+ @DisplayName("createLeaseFrame throws NullPointerException with null byteBufAllocator")
+ @Test
+ void createLeaseFrameNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createLeaseFrame(null, Duration.ofMillis(1), 1, null))
+ .withMessage("byteBufAllocator must not be null");
+ }
+
+ @DisplayName("createLeaseFrame throws NullPointerException with null timeToLive")
+ @Test
+ void createLeaseFrameNullTimeToLive() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createLeaseFrame(DEFAULT, null, 1, null))
+ .withMessage("timeToLive must not be null");
+ }
+
+ @DisplayName("returns number of requests")
+ @Test
+ void getNumberOfRequests() {
+ LeaseFrame frame =
+ createLeaseFrame(
+ Unpooled.buffer(10)
+ .writeShort(0b00001000_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeInt(0b00000000_00000000_00000000_11001000));
+
+ assertThat(frame.getNumberOfRequests()).isEqualTo(200);
+ }
+
+ @DisplayName("returns time to live")
+ @Test
+ void getTimeToLive() {
+ LeaseFrame frame =
+ createLeaseFrame(
+ Unpooled.buffer(10)
+ .writeShort(0b00001000_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeInt(0b00000000_00000000_00000000_11001000));
+
+ assertThat(frame.getTimeToLive()).isEqualTo(Duration.ofMillis(100));
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/LengthUtilsTest.java b/rsocket-core/src/test/java/io/rsocket/framing/LengthUtilsTest.java
new file mode 100644
index 000000000..184f785a4
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/LengthUtilsTest.java
@@ -0,0 +1,81 @@
+package io.rsocket.framing;
+
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+final class LengthUtilsTest {
+
+ @DisplayName("getLengthAsUnsignedByte returns length if 255")
+ @Test
+ void getLengthAsUnsignedByte() {
+ assertThat(LengthUtils.getLengthAsUnsignedByte(getRandomByteBuf((1 << 8) - 1))).isEqualTo(255);
+ }
+
+ @DisplayName("getLengthAsUnsignedByte throws NullPointerException with null byteBuf")
+ @Test
+ void getLengthAsUnsignedByteNullByteBuf() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> LengthUtils.getLengthAsUnsignedByte(null))
+ .withMessage("byteBuf must not be null");
+ }
+
+ @DisplayName("getLengthAsUnsignedByte throws IllegalArgumentException if larger than 255")
+ @Test
+ void getLengthAsUnsignedByteOverFlow() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> LengthUtils.getLengthAsUnsignedByte(getRandomByteBuf(1 << 8)))
+ .withMessage("%d is larger than 8 bits", 1 << 8);
+ }
+
+ @DisplayName("getLengthAsUnsignedShort throws NullPointerException with null byteBuf")
+ @Test
+ void getLengthAsUnsignedIntegerNullByteBuf() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> LengthUtils.getLengthAsUnsignedShort(null))
+ .withMessage("byteBuf must not be null");
+ }
+
+ @DisplayName("getLengthAsUnsignedMedium returns length if 16_777_215")
+ @Test
+ void getLengthAsUnsignedMedium() {
+ assertThat(LengthUtils.getLengthAsUnsignedMedium(getRandomByteBuf((1 << 24) - 1)))
+ .isEqualTo(16_777_215);
+ }
+
+ @DisplayName("getLengthAsUnsignedMedium throws NullPointerException with null byteBuf")
+ @Test
+ void getLengthAsUnsignedMediumNullByteBuf() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> LengthUtils.getLengthAsUnsignedMedium(null))
+ .withMessage("byteBuf must not be null");
+ }
+
+ @DisplayName(
+ "getLengthAsUnsignedMedium throws IllegalArgumentException if larger than 16_777_215")
+ @Test
+ void getLengthAsUnsignedMediumOverFlow() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> LengthUtils.getLengthAsUnsignedMedium(getRandomByteBuf(1 << 24)))
+ .withMessage("%d is larger than 24 bits", 1 << 24);
+ }
+
+ @DisplayName("getLengthAsUnsignedShort returns length if 65_535")
+ @Test
+ void getLengthAsUnsignedShort() {
+ assertThat(LengthUtils.getLengthAsUnsignedShort(getRandomByteBuf((1 << 16) - 1)))
+ .isEqualTo(65_535);
+ }
+
+ @DisplayName("getLengthAsUnsignedShort throws IllegalArgumentException if larger than 65_535")
+ @Test
+ void getLengthAsUnsignedShortOverFlow() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> LengthUtils.getLengthAsUnsignedShort(getRandomByteBuf(1 << 16)))
+ .withMessage("%d is larger than 16 bits", 1 << 16);
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/MetadataAndDataFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/MetadataAndDataFrameTest.java
new file mode 100644
index 000000000..5d42b6e2a
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/MetadataAndDataFrameTest.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+interface MetadataAndDataFrameTest
+ extends MetadataFrameTest, DataFrameTest {}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/MetadataFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/MetadataFrameTest.java
new file mode 100644
index 000000000..a45391294
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/MetadataFrameTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+
+interface MetadataFrameTest extends FrameTest {
+
+ T getFrameWithEmptyMetadata();
+
+ Tuple2 getFrameWithMetadata();
+
+ T getFrameWithoutMetadata();
+
+ @DisplayName("returns metadata as UTF-8")
+ @Test
+ default void getMetadataAsUtf8() {
+ Tuple2 tuple = getFrameWithMetadata();
+ T frame = tuple.getT1();
+ ByteBuf metadata = tuple.getT2();
+
+ assertThat(frame.getMetadataAsUtf8()).hasValue(metadata.toString(UTF_8));
+ }
+
+ @DisplayName("returns empty optional metadata as UTF-8")
+ @Test
+ default void getMetadataAsUtf8Empty() {
+ T frame = getFrameWithEmptyMetadata();
+
+ assertThat(frame.getMetadataAsUtf8()).hasValue("");
+ }
+
+ @DisplayName("returns empty optional for metadata as UTF-8")
+ @Test
+ default void getMetadataAsUtf8NoFlag() {
+ T frame = getFrameWithoutMetadata();
+
+ assertThat(frame.getMetadataAsUtf8()).isEmpty();
+ }
+
+ @DisplayName("returns metadata length")
+ @Test
+ default void getMetadataLength() {
+ Tuple2 tuple = getFrameWithMetadata();
+ T frame = tuple.getT1();
+ ByteBuf metadata = tuple.getT2();
+
+ assertThat(frame.getMetadataLength()).hasValue(metadata.readableBytes());
+ }
+
+ @DisplayName("returns empty optional metadata length")
+ @Test
+ default void getMetadataLengthEmpty() {
+ T frame = getFrameWithEmptyMetadata();
+
+ assertThat(frame.getMetadataLength()).hasValue(0);
+ }
+
+ @DisplayName("returns empty optional for metadata length")
+ @Test
+ default void getMetadataLengthNoFlag() {
+ T frame = getFrameWithoutMetadata();
+
+ assertThat(frame.getMetadataLength()).isEmpty();
+ }
+
+ @DisplayName("returns unsafe metadata")
+ @Test
+ default void getUnsafeMetadata() {
+ Tuple2 tuple = getFrameWithMetadata();
+ T frame = tuple.getT1();
+ ByteBuf metadata = tuple.getT2();
+
+ assertThat(frame.getUnsafeMetadata()).isEqualTo(metadata);
+ }
+
+ @DisplayName("returns unsafe metadata as UTF-8")
+ @Test
+ default void getUnsafeMetadataAsUtf8() {
+ Tuple2 tuple = getFrameWithMetadata();
+ T frame = tuple.getT1();
+ ByteBuf metadata = tuple.getT2();
+
+ assertThat(frame.getUnsafeMetadataAsUtf8()).isEqualTo(metadata.toString(UTF_8));
+ }
+
+ @DisplayName("returns empty unsafe metadata as UTF-8")
+ @Test
+ default void getUnsafeMetadataAsUtf8Empty() {
+ T frame = getFrameWithEmptyMetadata();
+
+ assertThat(frame.getUnsafeMetadataAsUtf8()).isEqualTo("");
+ }
+
+ @DisplayName("returns null for unsafe metadata as UTF-8")
+ @Test
+ default void getUnsafeMetadataAsUtf8NoFlag() {
+ T frame = getFrameWithoutMetadata();
+
+ assertThat(frame.getUnsafeMetadataAsUtf8()).isNull();
+ }
+
+ @DisplayName("returns unsafe empty metadata")
+ @Test
+ default void getUnsafeMetadataEmpty() {
+ T frame = getFrameWithEmptyMetadata();
+
+ assertThat(frame.getUnsafeMetadata()).isEqualTo(EMPTY_BUFFER);
+ }
+
+ @DisplayName("returns unsafe metadata length")
+ @Test
+ default void getUnsafeMetadataLength() {
+ Tuple2 tuple = getFrameWithMetadata();
+ T frame = tuple.getT1();
+ ByteBuf metadata = tuple.getT2();
+
+ assertThat(frame.getUnsafeMetadataLength()).isEqualTo(metadata.readableBytes());
+ }
+
+ @DisplayName("returns unsafe empty metadata length")
+ @Test
+ default void getUnsafeMetadataLengthEmpty() {
+ T frame = getFrameWithEmptyMetadata();
+
+ assertThat(frame.getUnsafeMetadataLength()).isEqualTo(0);
+ }
+
+ @DisplayName("returns null for unsafe metadata length")
+ @Test
+ default void getUnsafeMetadataLengthNoFlag() {
+ T frame = getFrameWithoutMetadata();
+
+ assertThat(frame.getUnsafeMetadataLength()).isNull();
+ }
+
+ @DisplayName("returns null for unsafe metadata")
+ @Test
+ default void getUnsafeMetadataNoFlag() {
+ T frame = getFrameWithoutMetadata();
+
+ assertThat(frame.getUnsafeMetadata()).isNull();
+ }
+
+ @DisplayName("maps metadata")
+ @Test
+ default void mapMetadata() {
+ Tuple2 tuple = getFrameWithMetadata();
+ T frame = tuple.getT1();
+ ByteBuf metadata = tuple.getT2();
+
+ assertThat(frame.mapMetadata(Function.identity())).hasValue(metadata);
+ }
+
+ @DisplayName("maps empty metadata")
+ @Test
+ default void mapMetadataEmpty() {
+ T frame = getFrameWithEmptyMetadata();
+
+ assertThat(frame.mapMetadata(Function.identity())).hasValue(EMPTY_BUFFER);
+ }
+
+ @DisplayName("maps empty optional for metadata")
+ @Test
+ default void mapMetadataNoFlag() {
+ T frame = getFrameWithoutMetadata();
+
+ assertThat(frame.mapMetadata(Function.identity())).isEmpty();
+ }
+
+ @DisplayName("mapMetadata throws NullPointerException with null function")
+ @Test
+ default void mapMetadataNullFunction() {
+ T frame = getFrameWithEmptyMetadata();
+
+ assertThatNullPointerException()
+ .isThrownBy(() -> frame.mapMetadata(null))
+ .withMessage("function must not be null");
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/MetadataPushFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/MetadataPushFrameTest.java
new file mode 100644
index 000000000..ec9420f3a
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/MetadataPushFrameTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.MetadataPushFrame.createMetadataPushFrame;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+final class MetadataPushFrameTest implements MetadataFrameTest {
+
+ @Override
+ public Function getCreateFrameFromByteBuf() {
+ return MetadataPushFrame::createMetadataPushFrame;
+ }
+
+ @Override
+ public Tuple2 getFrame() {
+ ByteBuf byteBuf =
+ Unpooled.buffer(4).writeShort(0b00110001_00000000).writeBytes(getRandomByteBuf(2));
+
+ MetadataPushFrame frame = createMetadataPushFrame(byteBuf);
+
+ return Tuples.of(frame, byteBuf);
+ }
+
+ @Override
+ public MetadataPushFrame getFrameWithEmptyMetadata() {
+ return createMetadataPushFrame(Unpooled.buffer(2).writeShort(0b00110001_00000000));
+ }
+
+ @Override
+ public Tuple2 getFrameWithMetadata() {
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ MetadataPushFrame frame =
+ createMetadataPushFrame(
+ Unpooled.buffer(4)
+ .writeShort(0b00110001_00000000)
+ .writeBytes(metadata, 0, metadata.readableBytes()));
+
+ return Tuples.of(frame, metadata);
+ }
+
+ @Override
+ public MetadataPushFrame getFrameWithoutMetadata() {
+ return createMetadataPushFrame(Unpooled.buffer(2).writeShort(0b00110000_00000000));
+ }
+
+ @DisplayName("creates METADATA_PUSH frame with ByteBufAllocator")
+ @Test
+ void createMetadataPushFrameByteBufAllocator() {
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(4)
+ .writeShort(0b00110001_00000000)
+ .writeBytes(metadata, 0, metadata.readableBytes());
+
+ assertThat(createMetadataPushFrame(DEFAULT, metadata).mapFrame(Function.identity()))
+ .isEqualTo(expected);
+ }
+
+ @DisplayName("createMetadataPushFrame throws NullPointerException with null metadata")
+ @Test
+ void createMetadataPushFrameNullMetadata() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createMetadataPushFrame(DEFAULT, (ByteBuf) null))
+ .withMessage("metadata must not be null");
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/PayloadFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/PayloadFrameTest.java
new file mode 100644
index 000000000..67b46be9a
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/PayloadFrameTest.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
+import static io.netty.buffer.Unpooled.buffer;
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.PayloadFrame.createPayloadFrame;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+final class PayloadFrameTest implements FragmentableFrameTest {
+
+ @Override
+ public Function getCreateFrameFromByteBuf() {
+ return PayloadFrame::createPayloadFrame;
+ }
+
+ @Override
+ public Tuple2 getFrame() {
+ ByteBuf byteBuf =
+ buffer(9)
+ .writeShort(0b00101001_00000000)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(getRandomByteBuf(2))
+ .writeBytes(getRandomByteBuf(2));
+
+ PayloadFrame frame = createPayloadFrame(byteBuf);
+
+ return Tuples.of(frame, byteBuf);
+ }
+
+ @Override
+ public Tuple2 getFrameWithData() {
+ ByteBuf data = getRandomByteBuf(2);
+
+ PayloadFrame frame =
+ createPayloadFrame(
+ buffer(7)
+ .writeShort(0b00101000_00000000)
+ .writeMedium(0b00000000_00000000_00000000)
+ .writeBytes(data, 0, data.readableBytes()));
+
+ return Tuples.of(frame, data);
+ }
+
+ @Override
+ public PayloadFrame getFrameWithEmptyData() {
+ return createPayloadFrame(
+ buffer(5).writeShort(0b00101000_00000000).writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public PayloadFrame getFrameWithEmptyMetadata() {
+ return createPayloadFrame(
+ buffer(5).writeShort(0b00101001_00000000).writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public PayloadFrame getFrameWithFollowsFlagSet() {
+ return createPayloadFrame(
+ buffer(5).writeShort(0b00101000_10000000).writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public Tuple2 getFrameWithMetadata() {
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ PayloadFrame frame =
+ createPayloadFrame(
+ buffer(7)
+ .writeShort(0b00101001_00000000)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes()));
+
+ return Tuples.of(frame, metadata);
+ }
+
+ @Override
+ public PayloadFrame getFrameWithoutFollowsFlagSet() {
+ return createPayloadFrame(
+ buffer(5).writeShort(0b00101000_01000000).writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public PayloadFrame getFrameWithoutMetadata() {
+ return createPayloadFrame(
+ buffer(5).writeShort(0b00101000_10000000).writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @DisplayName("createPayloadFrame throws IllegalArgumentException without complete flag or data")
+ @Test
+ void createPayloadFrameNonCompleteNullData() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> createPayloadFrame(DEFAULT, false, false, (ByteBuf) null, null))
+ .withMessage("Payload frame must either be complete, have data, or both");
+ }
+
+ @DisplayName("createPayloadFrame throws NullPointerException with null byteBufAllocator")
+ @Test
+ void createPayloadFrameNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createPayloadFrame(null, false, false, null, EMPTY_BUFFER))
+ .withMessage("byteBufAllocator must not be null");
+ }
+
+ @DisplayName("creates PAYLOAD frame with Complete flag")
+ @Test
+ void createPayloadFrameWithComplete() {
+ ByteBuf expected =
+ buffer(5).writeShort(0b00101000_01000000).writeMedium(0b00000000_00000000_00000000);
+
+ PayloadFrame frame = createPayloadFrame(DEFAULT, false, true, (ByteBuf) null, null);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates PAYLOAD frame with data")
+ @Test
+ void createPayloadFrameWithData() {
+ ByteBuf data = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ buffer(9)
+ .writeShort(0b00101000_00100000)
+ .writeMedium(0b00000000_00000000_00000000)
+ .writeBytes(data, 0, data.readableBytes());
+
+ PayloadFrame frame = createPayloadFrame(DEFAULT, false, false, null, data);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates PAYLOAD frame with Follows flag")
+ @Test
+ void createPayloadFrameWithFollows() {
+ ByteBuf expected =
+ buffer(7).writeShort(0b00101000_10100000).writeMedium(0b00000000_00000000_00000000);
+
+ PayloadFrame frame = createPayloadFrame(DEFAULT, true, false, null, EMPTY_BUFFER);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates PAYLOAD frame with metadata")
+ @Test
+ void createPayloadFrameWithMetadata() {
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ buffer(9)
+ .writeShort(0b00101001_00100000)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes());
+
+ PayloadFrame frame = createPayloadFrame(DEFAULT, false, false, metadata, EMPTY_BUFFER);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates PAYLOAD frame with metadata and data")
+ @Test
+ void createPayloadFrameWithMetadataAnData() {
+ ByteBuf metadata = getRandomByteBuf(2);
+ ByteBuf data = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ buffer(11)
+ .writeShort(0b00101001_00100000)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes())
+ .writeBytes(data, 0, data.readableBytes());
+
+ PayloadFrame frame = createPayloadFrame(DEFAULT, false, false, metadata, data);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("tests complete flag not set")
+ @Test
+ void isCompleteFlagSetFalse() {
+ PayloadFrame frame =
+ createPayloadFrame(
+ buffer(5).writeShort(0b00101000_00000000).writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.isCompleteFlagSet()).isFalse();
+ }
+
+ @DisplayName("tests complete flag set")
+ @Test
+ void isCompleteFlagSetTrue() {
+ PayloadFrame frame =
+ createPayloadFrame(
+ buffer(5).writeShort(0b00101000_01000000).writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.isCompleteFlagSet()).isTrue();
+ }
+
+ @DisplayName("tests next flag set")
+ @Test
+ void isNextFlagNotSet() {
+ PayloadFrame frame =
+ createPayloadFrame(
+ buffer(5).writeShort(0b00101000_00000000).writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.isNextFlagSet()).isFalse();
+ }
+
+ @DisplayName("tests next flag set")
+ @Test
+ void isNextFlagSetTrue() {
+ PayloadFrame frame =
+ createPayloadFrame(
+ buffer(5).writeShort(0b00101000_00100000).writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.isNextFlagSet()).isTrue();
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/RequestChannelFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/RequestChannelFrameTest.java
new file mode 100644
index 000000000..4b0beb67d
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/RequestChannelFrameTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.RequestChannelFrame.createRequestChannelFrame;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+final class RequestChannelFrameTest implements FragmentableFrameTest {
+
+ @Override
+ public Function getCreateFrameFromByteBuf() {
+ return RequestChannelFrame::createRequestChannelFrame;
+ }
+
+ @Override
+ public Tuple2 getFrame() {
+ ByteBuf byteBuf =
+ Unpooled.buffer(11)
+ .writeShort(0b00011101_00000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(getRandomByteBuf(2))
+ .writeBytes(getRandomByteBuf(2));
+
+ RequestChannelFrame frame = createRequestChannelFrame(byteBuf);
+
+ return Tuples.of(frame, byteBuf);
+ }
+
+ @Override
+ public Tuple2 getFrameWithData() {
+ ByteBuf data = getRandomByteBuf(2);
+
+ RequestChannelFrame frame =
+ createRequestChannelFrame(
+ Unpooled.buffer(9)
+ .writeShort(0b00011100_00000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000000)
+ .writeBytes(data, 0, data.readableBytes()));
+
+ return Tuples.of(frame, data);
+ }
+
+ @Override
+ public RequestChannelFrame getFrameWithEmptyData() {
+ return createRequestChannelFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00011100_00000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public RequestChannelFrame getFrameWithEmptyMetadata() {
+ return createRequestChannelFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00011101_00000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public RequestChannelFrame getFrameWithFollowsFlagSet() {
+ return createRequestChannelFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00011100_10000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public Tuple2 getFrameWithMetadata() {
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ RequestChannelFrame frame =
+ createRequestChannelFrame(
+ Unpooled.buffer(9)
+ .writeShort(0b00011101_00000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes()));
+
+ return Tuples.of(frame, metadata);
+ }
+
+ @Override
+ public RequestChannelFrame getFrameWithoutFollowsFlagSet() {
+ return createRequestChannelFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00011100_00000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public RequestChannelFrame getFrameWithoutMetadata() {
+ return createRequestChannelFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00011100_10000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @DisplayName("createRequestChannelFrame throws NullPointerException with null byteBufAllocator")
+ @Test
+ void createRequestChannelFrameNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createRequestChannelFrame(null, false, false, 100, (ByteBuf) null, null))
+ .withMessage("byteBufAllocator must not be null");
+ }
+
+ @DisplayName("creates REQUEST_CHANNEL frame with Complete flag")
+ @Test
+ void createRequestChannelFrameWithComplete() {
+ ByteBuf expected =
+ Unpooled.buffer(9)
+ .writeShort(0b00011100_01000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000000);
+
+ RequestChannelFrame frame =
+ createRequestChannelFrame(DEFAULT, false, true, 100, (ByteBuf) null, null);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates REQUEST_CHANNEL frame with data")
+ @Test
+ void createRequestChannelFrameWithData() {
+ ByteBuf data = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(11)
+ .writeShort(0b00011100_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000000)
+ .writeBytes(data, 0, data.readableBytes());
+
+ RequestChannelFrame frame = createRequestChannelFrame(DEFAULT, false, false, 100, null, data);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates REQUEST_CHANNEL frame with Follows flag")
+ @Test
+ void createRequestChannelFrameWithFollows() {
+ ByteBuf expected =
+ Unpooled.buffer(9)
+ .writeShort(0b00011100_10000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000000);
+
+ RequestChannelFrame frame =
+ createRequestChannelFrame(DEFAULT, true, false, 100, (ByteBuf) null, null);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates REQUEST_CHANNEL frame with metadata")
+ @Test
+ void createRequestChannelFrameWithMetadata() {
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(11)
+ .writeShort(0b00011101_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes());
+
+ RequestChannelFrame frame =
+ createRequestChannelFrame(DEFAULT, false, false, 100, metadata, null);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates REQUEST_CHANNEL frame with metadata and data")
+ @Test
+ void createRequestChannelFrameWithMetadataAnData() {
+ ByteBuf metadata = getRandomByteBuf(2);
+ ByteBuf data = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(13)
+ .writeShort(0b00011101_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes())
+ .writeBytes(data, 0, data.readableBytes());
+
+ RequestChannelFrame frame =
+ createRequestChannelFrame(DEFAULT, false, false, 100, metadata, data);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("returns the initial requestN")
+ @Test
+ void getInitialRequestN() {
+ RequestChannelFrame frame =
+ createRequestChannelFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00011100_10000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.getInitialRequestN()).isEqualTo(1);
+ }
+
+ @DisplayName("tests complete flag not set")
+ @Test
+ void isCompleteFlagSetFalse() {
+ RequestChannelFrame frame =
+ createRequestChannelFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00011100_10000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.isCompleteFlagSet()).isFalse();
+ }
+
+ @DisplayName("tests complete flag set")
+ @Test
+ void isCompleteFlagSetTrue() {
+ RequestChannelFrame frame =
+ createRequestChannelFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00011100_11000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.isCompleteFlagSet()).isTrue();
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/RequestFireAndForgetFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/RequestFireAndForgetFrameTest.java
new file mode 100644
index 000000000..baf409b45
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/RequestFireAndForgetFrameTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.RequestFireAndForgetFrame.createRequestFireAndForgetFrame;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+final class RequestFireAndForgetFrameTest
+ implements FragmentableFrameTest {
+
+ @Override
+ public Function getCreateFrameFromByteBuf() {
+ return RequestFireAndForgetFrame::createRequestFireAndForgetFrame;
+ }
+
+ @Override
+ public Tuple2 getFrame() {
+ ByteBuf byteBuf =
+ Unpooled.buffer(9)
+ .writeShort(0b00010101_00000000)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(getRandomByteBuf(2))
+ .writeBytes(getRandomByteBuf(2));
+
+ RequestFireAndForgetFrame frame = createRequestFireAndForgetFrame(byteBuf);
+
+ return Tuples.of(frame, byteBuf);
+ }
+
+ @Override
+ public Tuple2 getFrameWithData() {
+ ByteBuf data = getRandomByteBuf(2);
+
+ RequestFireAndForgetFrame frame =
+ createRequestFireAndForgetFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00010100_00000000)
+ .writeMedium(0b00000000_00000000_00000000)
+ .writeBytes(data, 0, data.readableBytes()));
+
+ return Tuples.of(frame, data);
+ }
+
+ @Override
+ public RequestFireAndForgetFrame getFrameWithEmptyData() {
+ return createRequestFireAndForgetFrame(
+ Unpooled.buffer(5)
+ .writeShort(0b00010100_00000000)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public RequestFireAndForgetFrame getFrameWithEmptyMetadata() {
+ return createRequestFireAndForgetFrame(
+ Unpooled.buffer(5)
+ .writeShort(0b00010101_00000000)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public RequestFireAndForgetFrame getFrameWithFollowsFlagSet() {
+ return createRequestFireAndForgetFrame(
+ Unpooled.buffer(5)
+ .writeShort(0b00010100_10000000)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public Tuple2 getFrameWithMetadata() {
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ RequestFireAndForgetFrame frame =
+ createRequestFireAndForgetFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00010101_00000000)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes()));
+
+ return Tuples.of(frame, metadata);
+ }
+
+ @Override
+ public RequestFireAndForgetFrame getFrameWithoutFollowsFlagSet() {
+ return createRequestFireAndForgetFrame(
+ Unpooled.buffer(5)
+ .writeShort(0b00010100_00000000)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public RequestFireAndForgetFrame getFrameWithoutMetadata() {
+ return createRequestFireAndForgetFrame(
+ Unpooled.buffer(5)
+ .writeShort(0b00010100_10000000)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @DisplayName(
+ "createRequestFireAndForgetFrame throws NullPointerException with null byteBufAllocator")
+ @Test
+ void createRequestFireAndForgetFrameNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createRequestFireAndForgetFrame(null, false, (ByteBuf) null, null))
+ .withMessage("byteBufAllocator must not be null");
+ }
+
+ @DisplayName("creates REQUEST_FNF frame with data")
+ @Test
+ void createRequestFireAndForgetFrameWithData() {
+ ByteBuf data = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(7)
+ .writeShort(0b00010100_00000000)
+ .writeMedium(0b00000000_00000000_00000000)
+ .writeBytes(data, 0, data.readableBytes());
+
+ RequestFireAndForgetFrame frame = createRequestFireAndForgetFrame(DEFAULT, false, null, data);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates REQUEST_FNF frame with Follows flag")
+ @Test
+ void createRequestFireAndForgetFrameWithFollows() {
+ ByteBuf expected =
+ Unpooled.buffer(5)
+ .writeShort(0b00010100_10000000)
+ .writeMedium(0b00000000_00000000_00000000);
+
+ RequestFireAndForgetFrame frame =
+ createRequestFireAndForgetFrame(DEFAULT, true, (ByteBuf) null, null);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates REQUEST_FNF frame with metadata")
+ @Test
+ void createRequestFireAndForgetFrameWithMetadata() {
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(7)
+ .writeShort(0b00010101_00000000)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes());
+
+ RequestFireAndForgetFrame frame =
+ createRequestFireAndForgetFrame(DEFAULT, false, metadata, null);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates REQUEST_FNF frame with metadata and data")
+ @Test
+ void createRequestFireAndForgetFrameWithMetadataAnData() {
+ ByteBuf metadata = getRandomByteBuf(2);
+ ByteBuf data = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(9)
+ .writeShort(0b00010101_00000000)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes())
+ .writeBytes(data, 0, data.readableBytes());
+
+ RequestFireAndForgetFrame frame =
+ createRequestFireAndForgetFrame(DEFAULT, false, metadata, data);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/RequestNFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/RequestNFrameTest.java
new file mode 100644
index 000000000..b81af3958
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/RequestNFrameTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.RequestNFrame.createRequestNFrame;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+final class RequestNFrameTest implements FrameTest {
+
+ @Override
+ public Function getCreateFrameFromByteBuf() {
+ return RequestNFrame::createRequestNFrame;
+ }
+
+ @Override
+ public Tuple2 getFrame() {
+ ByteBuf byteBuf =
+ Unpooled.buffer(6)
+ .writeShort(0b00100000_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100);
+
+ RequestNFrame frame = createRequestNFrame(byteBuf);
+
+ return Tuples.of(frame, byteBuf);
+ }
+
+ @DisplayName("creates REQUEST_N frame with ByteBufAllocator")
+ @Test
+ void createRequestNFrameByteBufAllocator() {
+ ByteBuf expected =
+ Unpooled.buffer(6)
+ .writeShort(0b00100000_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100);
+
+ assertThat(createRequestNFrame(DEFAULT, 100).mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("createRequestNFrame throws NullPointerException with null byteBufAllocator")
+ @Test
+ void createRequestNFrameNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createRequestNFrame(null, 1))
+ .withMessage("byteBufAllocator must not be null");
+ }
+
+ @DisplayName("createRequestNFrame throws IllegalArgumentException with requestN less then 1")
+ @Test
+ void createRequestNFrameZeroRequestN() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> createRequestNFrame(DEFAULT, 0))
+ .withMessage("requestN must be positive");
+ }
+
+ @DisplayName("returns requestN")
+ @Test
+ void getRequestN() {
+ RequestNFrame frame =
+ createRequestNFrame(
+ Unpooled.buffer(6)
+ .writeShort(0b00100000_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100));
+
+ assertThat(frame.getRequestN()).isEqualTo(100);
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/RequestResponseFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/RequestResponseFrameTest.java
new file mode 100644
index 000000000..a93b7cef9
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/RequestResponseFrameTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.RequestResponseFrame.createRequestResponseFrame;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+final class RequestResponseFrameTest implements FragmentableFrameTest {
+
+ @Override
+ public Function getCreateFrameFromByteBuf() {
+ return RequestResponseFrame::createRequestResponseFrame;
+ }
+
+ @Override
+ public Tuple2 getFrame() {
+ ByteBuf byteBuf =
+ Unpooled.buffer(9)
+ .writeShort(0b00010001_00000000)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(getRandomByteBuf(2))
+ .writeBytes(getRandomByteBuf(2));
+
+ RequestResponseFrame frame = createRequestResponseFrame(byteBuf);
+
+ return Tuples.of(frame, byteBuf);
+ }
+
+ @Override
+ public Tuple2 getFrameWithData() {
+ ByteBuf data = getRandomByteBuf(2);
+
+ RequestResponseFrame frame =
+ createRequestResponseFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00010000_00000000)
+ .writeMedium(0b00000000_00000000_00000000)
+ .writeBytes(data, 0, data.readableBytes()));
+
+ return Tuples.of(frame, data);
+ }
+
+ @Override
+ public RequestResponseFrame getFrameWithEmptyData() {
+ return createRequestResponseFrame(
+ Unpooled.buffer(5)
+ .writeShort(0b00010000_00000000)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public RequestResponseFrame getFrameWithEmptyMetadata() {
+ return createRequestResponseFrame(
+ Unpooled.buffer(5)
+ .writeShort(0b00010001_00000000)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public RequestResponseFrame getFrameWithFollowsFlagSet() {
+ return createRequestResponseFrame(
+ Unpooled.buffer(5)
+ .writeShort(0b00010000_10000000)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public Tuple2 getFrameWithMetadata() {
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ RequestResponseFrame frame =
+ createRequestResponseFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00010001_00000000)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes()));
+
+ return Tuples.of(frame, metadata);
+ }
+
+ @Override
+ public RequestResponseFrame getFrameWithoutFollowsFlagSet() {
+ return createRequestResponseFrame(
+ Unpooled.buffer(5)
+ .writeShort(0b00010000_00000000)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public RequestResponseFrame getFrameWithoutMetadata() {
+ return createRequestResponseFrame(
+ Unpooled.buffer(5)
+ .writeShort(0b00010000_10000000)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @DisplayName("createRequestResponseFrame throws NullPointerException with null byteBufAllocator")
+ @Test
+ void createRequestResponseFrameNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createRequestResponseFrame(null, false, (ByteBuf) null, null))
+ .withMessage("byteBufAllocator must not be null");
+ }
+
+ @DisplayName("creates REQUEST_FNF frame with data")
+ @Test
+ void createRequestResponseFrameWithData() {
+ ByteBuf data = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(7)
+ .writeShort(0b00010000_00000000)
+ .writeMedium(0b00000000_00000000_00000000)
+ .writeBytes(data, 0, data.readableBytes());
+
+ RequestResponseFrame frame = createRequestResponseFrame(DEFAULT, false, null, data);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates REQUEST_FNF frame with Follows flag")
+ @Test
+ void createRequestResponseFrameWithFollows() {
+ ByteBuf expected =
+ Unpooled.buffer(5)
+ .writeShort(0b00010000_10000000)
+ .writeMedium(0b00000000_00000000_00000000);
+
+ RequestResponseFrame frame = createRequestResponseFrame(DEFAULT, true, (ByteBuf) null, null);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates REQUEST_FNF frame with metadata")
+ @Test
+ void createRequestResponseFrameWithMetadata() {
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(7)
+ .writeShort(0b00010001_00000000)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes());
+
+ RequestResponseFrame frame = createRequestResponseFrame(DEFAULT, false, metadata, null);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates REQUEST_FNF frame with metadata and data")
+ @Test
+ void createRequestResponseFrameWithMetadataAnData() {
+ ByteBuf metadata = getRandomByteBuf(2);
+ ByteBuf data = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(9)
+ .writeShort(0b00010001_00000000)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes())
+ .writeBytes(data, 0, data.readableBytes());
+
+ RequestResponseFrame frame = createRequestResponseFrame(DEFAULT, false, metadata, data);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/RequestStreamFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/RequestStreamFrameTest.java
new file mode 100644
index 000000000..664d5358e
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/RequestStreamFrameTest.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.RequestStreamFrame.createRequestStreamFrame;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+final class RequestStreamFrameTest implements FragmentableFrameTest {
+
+ @Override
+ public Function getCreateFrameFromByteBuf() {
+ return RequestStreamFrame::createRequestStreamFrame;
+ }
+
+ @Override
+ public Tuple2 getFrame() {
+ ByteBuf byteBuf =
+ Unpooled.buffer(11)
+ .writeShort(0b00011001_00000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(getRandomByteBuf(2))
+ .writeBytes(getRandomByteBuf(2));
+
+ RequestStreamFrame frame = createRequestStreamFrame(byteBuf);
+
+ return Tuples.of(frame, byteBuf);
+ }
+
+ @Override
+ public Tuple2 getFrameWithData() {
+ ByteBuf data = getRandomByteBuf(2);
+
+ RequestStreamFrame frame =
+ createRequestStreamFrame(
+ Unpooled.buffer(9)
+ .writeShort(0b00011000_00000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000000)
+ .writeBytes(data, 0, data.readableBytes()));
+
+ return Tuples.of(frame, data);
+ }
+
+ @Override
+ public RequestStreamFrame getFrameWithEmptyData() {
+ return createRequestStreamFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00011000_00000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public RequestStreamFrame getFrameWithEmptyMetadata() {
+ return createRequestStreamFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00011001_00000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public RequestStreamFrame getFrameWithFollowsFlagSet() {
+ return createRequestStreamFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00011000_10000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public Tuple2 getFrameWithMetadata() {
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ RequestStreamFrame frame =
+ createRequestStreamFrame(
+ Unpooled.buffer(9)
+ .writeShort(0b00011001_00000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes()));
+
+ return Tuples.of(frame, metadata);
+ }
+
+ @Override
+ public RequestStreamFrame getFrameWithoutFollowsFlagSet() {
+ return createRequestStreamFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00011000_00000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public RequestStreamFrame getFrameWithoutMetadata() {
+ return createRequestStreamFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00001100_10000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @DisplayName(
+ "createRequestStreamFrame throws IllegalArgumentException with invalid initialRequestN")
+ @Test
+ void createRequestStreamFrameInvalidInitialRequestN() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> createRequestStreamFrame(DEFAULT, false, 0, (ByteBuf) null, null))
+ .withMessage("initialRequestN must be positive");
+ }
+
+ @DisplayName("createRequestStreamFrame throws NullPointerException with null byteBufAllocator")
+ @Test
+ void createRequestStreamFrameNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createRequestStreamFrame(null, false, 100, (ByteBuf) null, null))
+ .withMessage("byteBufAllocator must not be null");
+ }
+
+ @DisplayName("creates REQUEST_STREAM frame with data")
+ @Test
+ void createRequestStreamFrameWithData() {
+ ByteBuf data = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(11)
+ .writeShort(0b00011000_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000000)
+ .writeBytes(data, 0, data.readableBytes());
+
+ RequestStreamFrame frame = createRequestStreamFrame(DEFAULT, false, 100, null, data);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates REQUEST_STREAM frame with Follows flag")
+ @Test
+ void createRequestStreamFrameWithFollows() {
+ ByteBuf expected =
+ Unpooled.buffer(9)
+ .writeShort(0b00011000_10000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000000);
+
+ RequestStreamFrame frame = createRequestStreamFrame(DEFAULT, true, 100, (ByteBuf) null, null);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates REQUEST_STREAM frame with metadata")
+ @Test
+ void createRequestStreamFrameWithMetadata() {
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(11)
+ .writeShort(0b00011001_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes());
+
+ RequestStreamFrame frame = createRequestStreamFrame(DEFAULT, false, 100, metadata, null);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates REQUEST_STREAM frame with metadata and data")
+ @Test
+ void createRequestStreamFrameWithMetadataAnData() {
+ ByteBuf metadata = getRandomByteBuf(2);
+ ByteBuf data = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(13)
+ .writeShort(0b00011001_00000000)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes())
+ .writeBytes(data, 0, data.readableBytes());
+
+ RequestStreamFrame frame = createRequestStreamFrame(DEFAULT, false, 100, metadata, data);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("returns the initial requestN")
+ @Test
+ void getInitialRequestN() {
+ RequestStreamFrame frame =
+ createRequestStreamFrame(
+ Unpooled.buffer(7)
+ .writeShort(0b00011000_10000000)
+ .writeInt(0b00000000_00000000_00000000_00000001)
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.getInitialRequestN()).isEqualTo(1);
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/ResumeFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/ResumeFrameTest.java
new file mode 100644
index 000000000..8a467eccf
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/ResumeFrameTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.ResumeFrame.createResumeFrame;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+final class ResumeFrameTest implements FrameTest {
+
+ @Override
+ public Function getCreateFrameFromByteBuf() {
+ return ResumeFrame::createResumeFrame;
+ }
+
+ @Override
+ public Tuple2 getFrame() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+
+ ByteBuf byteBuf =
+ Unpooled.buffer(26)
+ .writeShort(0b00110100_00000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeLong(0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_00101100)
+ .writeLong(0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_10010000);
+
+ ResumeFrame frame = createResumeFrame(DEFAULT, 100, 200, resumeIdentificationToken, 300, 400);
+
+ return Tuples.of(frame, byteBuf);
+ }
+
+ @DisplayName("creates RESUME frame with ByteBufAllocator")
+ @Test
+ void createResumeByteBufAllocator() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(26)
+ .writeShort(0b00110100_00000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeLong(0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_00101100)
+ .writeLong(0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_10010000);
+
+ assertThat(
+ createResumeFrame(DEFAULT, 100, 200, resumeIdentificationToken, 300, 400)
+ .mapFrame(Function.identity()))
+ .isEqualTo(expected);
+ }
+
+ @DisplayName("createResumeFrame throws NullPointerException with null byteBufAllocator")
+ @Test
+ void createResumeFrameNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createResumeFrame(null, 100, 200, EMPTY_BUFFER, 300, 400))
+ .withMessage("byteBufAllocator must not be null");
+ }
+
+ @DisplayName("createResumeFrame throws NullPointerException with null resumeIdentificationToken")
+ @Test
+ void createResumeFrameNullResumeIdentificationToken() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createResumeFrame(DEFAULT, 100, 200, null, 300, 400))
+ .withMessage("resumeIdentificationToken must not be null");
+ }
+
+ @DisplayName("returns first available client position")
+ @Test
+ void getFirstAvailableClientPosition() {
+ ResumeFrame frame =
+ createResumeFrame(
+ Unpooled.buffer(24)
+ .writeShort(0b00110100_00000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeShort(0b00000000_00000000)
+ .writeBytes(EMPTY_BUFFER)
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_00101100)
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_10010000));
+
+ assertThat(frame.getFirstAvailableClientPosition()).isEqualTo(400);
+ }
+
+ @DisplayName("returns last received server position")
+ @Test
+ void getLastReceivedServerPosition() {
+ ResumeFrame frame =
+ createResumeFrame(
+ Unpooled.buffer(24)
+ .writeShort(0b00110100_00000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeShort(0b00000000_00000000)
+ .writeBytes(EMPTY_BUFFER)
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_00101100)
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_10010000));
+
+ assertThat(frame.getLastReceivedServerPosition()).isEqualTo(300);
+ }
+
+ @DisplayName("returns major version")
+ @Test
+ void getMajorVersion() {
+ ResumeFrame frame =
+ createResumeFrame(
+ Unpooled.buffer(24)
+ .writeShort(0b00110100_00000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeShort(0b00000000_00000000)
+ .writeBytes(EMPTY_BUFFER)
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_00101100)
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_10010000));
+
+ assertThat(frame.getMajorVersion()).isEqualTo(100);
+ }
+
+ @DisplayName("returns minor version")
+ @Test
+ void getMinorVersion() {
+ ResumeFrame frame =
+ createResumeFrame(
+ Unpooled.buffer(24)
+ .writeShort(0b00110100_00000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeShort(0b00000000_00000000)
+ .writeBytes(EMPTY_BUFFER)
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_00101100)
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_10010000));
+
+ assertThat(frame.getMinorVersion()).isEqualTo(200);
+ }
+
+ @DisplayName("returns resume identification token as UTF-8")
+ @Test
+ void getResumeIdentificationTokenAsUtf8() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+
+ ResumeFrame frame =
+ createResumeFrame(
+ Unpooled.buffer(26)
+ .writeShort(0b00110100_00000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_00101100)
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_10010000));
+
+ assertThat(frame.getResumeIdentificationTokenAsUtf8())
+ .isEqualTo(resumeIdentificationToken.toString(UTF_8));
+ }
+
+ @DisplayName("returns unsafe resume identification token")
+ @Test
+ void getUnsafeResumeIdentificationToken() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+
+ ResumeFrame frame =
+ createResumeFrame(
+ Unpooled.buffer(26)
+ .writeShort(0b00110100_00000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_00101100)
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_10010000));
+
+ assertThat(frame.getUnsafeResumeIdentificationToken()).isEqualTo(resumeIdentificationToken);
+ }
+
+ @DisplayName("maps resume identification token")
+ @Test
+ void mapResumeIdentificationToken() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+
+ ResumeFrame frame =
+ createResumeFrame(
+ Unpooled.buffer(26)
+ .writeShort(0b00110100_00000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_00101100)
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_10010000));
+
+ assertThat(frame.mapResumeIdentificationToken(Function.identity()))
+ .isEqualTo(resumeIdentificationToken);
+ }
+
+ @DisplayName("mapResumeIdentificationToken throws NullPointerException with null function")
+ @Test
+ void mapResumeIdentificationTokenNullFunction() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+
+ ResumeFrame frame =
+ createResumeFrame(
+ Unpooled.buffer(26)
+ .writeShort(0b00110100_00000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_00101100)
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000001_10010000));
+
+ assertThatNullPointerException()
+ .isThrownBy(() -> frame.mapResumeIdentificationToken(null))
+ .withMessage("function must not be null");
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/ResumeOkFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/ResumeOkFrameTest.java
new file mode 100644
index 000000000..6262d5546
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/ResumeOkFrameTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.ResumeOkFrame.createResumeOkFrame;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+final class ResumeOkFrameTest implements FrameTest {
+
+ @Override
+ public Function getCreateFrameFromByteBuf() {
+ return ResumeOkFrame::createResumeOkFrame;
+ }
+
+ @Override
+ public Tuple2 getFrame() {
+ ByteBuf byteBuf =
+ Unpooled.buffer(10)
+ .writeShort(0b00111000_00000000)
+ .writeLong(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_01100100);
+
+ ResumeOkFrame frame = createResumeOkFrame(byteBuf);
+
+ return Tuples.of(frame, byteBuf);
+ }
+
+ @DisplayName("creates RESUME_OK frame with ByteBufAllocator")
+ @Test
+ void createResumeOkFrameByteBufAllocator() {
+ ByteBuf expected =
+ Unpooled.buffer(10)
+ .writeShort(0b00111000_00000000)
+ .writeLong(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_01100100);
+
+ assertThat(createResumeOkFrame(DEFAULT, 100).mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("createResumeOkFrame throws NullPointerException with null byteBufAllocator")
+ @Test
+ void createResumeOkFrameNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createResumeOkFrame(null, 100))
+ .withMessage("byteBufAllocator must not be null");
+ }
+
+ @DisplayName("returns last received client position")
+ @Test
+ void getLastReceivedClientPosition() {
+ ResumeOkFrame frame =
+ createResumeOkFrame(
+ Unpooled.buffer(10)
+ .writeShort(0b001110_0000000000)
+ .writeLong(
+ 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_01100100));
+
+ assertThat(frame.getLastReceivedClientPosition()).isEqualTo(100);
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/SetupFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/SetupFrameTest.java
new file mode 100644
index 000000000..83a6b534c
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/SetupFrameTest.java
@@ -0,0 +1,859 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.SetupFrame.createSetupFrame;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static io.rsocket.test.util.StringUtils.getRandomString;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.time.Duration;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+final class SetupFrameTest implements MetadataAndDataFrameTest {
+
+ @Override
+ public Function getCreateFrameFromByteBuf() {
+ return SetupFrame::createSetupFrame;
+ }
+
+ @Override
+ public Tuple2 getFrame() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+ String metadataMimeType = getRandomString(2);
+ ByteBuf metadataMimeTypeBuf = Unpooled.copiedBuffer(metadataMimeType, UTF_8);
+ String dataMimeType = getRandomString(3);
+ ByteBuf dataMimeTypeBuf = Unpooled.copiedBuffer(dataMimeType, UTF_8);
+ ByteBuf metadata = getRandomByteBuf(2);
+ ByteBuf data = getRandomByteBuf(2);
+
+ ByteBuf byteBuf =
+ Unpooled.buffer(32)
+ .writeShort(0b00000101_11000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeTypeBuf, 0, metadataMimeTypeBuf.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeTypeBuf, 0, dataMimeTypeBuf.readableBytes())
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes())
+ .writeBytes(data, 0, data.readableBytes());
+
+ SetupFrame frame =
+ createSetupFrame(
+ DEFAULT,
+ true,
+ 100,
+ 200,
+ Duration.ofMillis(300),
+ Duration.ofMillis(400),
+ resumeIdentificationToken,
+ metadataMimeType,
+ dataMimeType,
+ metadata,
+ data);
+
+ return Tuples.of(frame, byteBuf);
+ }
+
+ @Override
+ public Tuple2 getFrameWithData() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+ ByteBuf metadataMimeType = getRandomByteBuf(2);
+ ByteBuf dataMimeType = getRandomByteBuf(3);
+ ByteBuf data = getRandomByteBuf(2);
+
+ SetupFrame frame =
+ createSetupFrame(
+ Unpooled.buffer(30)
+ .writeShort(0b00000101_11000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeType, 0, metadataMimeType.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeType, 0, dataMimeType.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000)
+ .writeBytes(data, 0, data.readableBytes()));
+
+ return Tuples.of(frame, data);
+ }
+
+ @Override
+ public SetupFrame getFrameWithEmptyData() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+ ByteBuf metadataMimeType = getRandomByteBuf(2);
+ ByteBuf dataMimeType = getRandomByteBuf(3);
+
+ return createSetupFrame(
+ Unpooled.buffer(28)
+ .writeShort(0b00000100_11000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeType, 0, metadataMimeType.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeType, 0, dataMimeType.readableBytes())
+ .writeMedium(0b00000000_00000000_0000000));
+ }
+
+ @Override
+ public SetupFrame getFrameWithEmptyMetadata() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+ ByteBuf metadataMimeType = getRandomByteBuf(2);
+ ByteBuf dataMimeType = getRandomByteBuf(3);
+
+ return createSetupFrame(
+ Unpooled.buffer(28)
+ .writeShort(0b00000101_11000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeType, 0, metadataMimeType.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeType, 0, dataMimeType.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @Override
+ public Tuple2 getFrameWithMetadata() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+ ByteBuf metadataMimeType = getRandomByteBuf(2);
+ ByteBuf dataMimeType = getRandomByteBuf(3);
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ SetupFrame frame =
+ createSetupFrame(
+ Unpooled.buffer(30)
+ .writeShort(0b00000101_11000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeType, 0, metadataMimeType.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeType, 0, dataMimeType.readableBytes())
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes()));
+
+ return Tuples.of(frame, metadata);
+ }
+
+ @Override
+ public SetupFrame getFrameWithoutMetadata() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+ ByteBuf metadataMimeType = getRandomByteBuf(2);
+ ByteBuf dataMimeType = getRandomByteBuf(3);
+
+ return createSetupFrame(
+ Unpooled.buffer(27)
+ .writeShort(0b00000100_11000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeType, 0, metadataMimeType.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeType, 0, dataMimeType.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000));
+ }
+
+ @DisplayName("createSetup throws IllegalArgumentException with invalid keepAliveInterval")
+ @Test
+ void createSetupFrameInvalidKeepAliveInterval() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(
+ () ->
+ createSetupFrame(
+ DEFAULT,
+ true,
+ Duration.ZERO,
+ Duration.ofMillis(1),
+ null,
+ "",
+ "",
+ (ByteBuf) null,
+ null))
+ .withMessage("keepAliveInterval must be a positive duration");
+ }
+
+ @DisplayName("createSetup throws IllegalArgumentException with invalid maxLifetime")
+ @Test
+ void createSetupFrameInvalidMaxLifetime() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(
+ () ->
+ createSetupFrame(
+ DEFAULT,
+ true,
+ Duration.ofMillis(1),
+ Duration.ZERO,
+ null,
+ "",
+ "",
+ (ByteBuf) null,
+ null))
+ .withMessage("maxLifetime must be a positive duration");
+ }
+
+ @DisplayName("createSetup throws NullPointerException with null byteBufAllocator")
+ @Test
+ void createSetupFrameNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(
+ () ->
+ createSetupFrame(
+ null,
+ true,
+ Duration.ofMillis(1),
+ Duration.ofMillis(1),
+ null,
+ "",
+ "",
+ (ByteBuf) null,
+ null))
+ .withMessage("byteBufAllocator must not be null");
+ }
+
+ @DisplayName("createSetup throws NullPointerException with null dataMimeType")
+ @Test
+ void createSetupFrameNullDataMimeType() {
+ assertThatNullPointerException()
+ .isThrownBy(
+ () ->
+ createSetupFrame(
+ DEFAULT,
+ true,
+ Duration.ofMillis(1),
+ Duration.ofMillis(1),
+ null,
+ "",
+ null,
+ (ByteBuf) null,
+ null))
+ .withMessage("dataMimeType must not be null");
+ }
+
+ @DisplayName("createSetup throws NullPointerException with null keepAliveInterval")
+ @Test
+ void createSetupFrameNullKeepAliveInterval() {
+ assertThatNullPointerException()
+ .isThrownBy(
+ () ->
+ createSetupFrame(
+ DEFAULT, true, null, Duration.ofMillis(1), null, "", "", (ByteBuf) null, null))
+ .withMessage("keepAliveInterval must not be null");
+ }
+
+ @DisplayName("createSetup throws NullPointerException with null maxLifetime")
+ @Test
+ void createSetupFrameNullMaxLifetime() {
+ assertThatNullPointerException()
+ .isThrownBy(
+ () ->
+ createSetupFrame(
+ DEFAULT, true, Duration.ofMillis(1), null, null, "", "", (ByteBuf) null, null))
+ .withMessage("maxLifetime must not be null");
+ }
+
+ @DisplayName("createSetup throws NullPointerException with null metadataMimeType")
+ @Test
+ void createSetupFrameNullMetadataMimeType() {
+ assertThatNullPointerException()
+ .isThrownBy(
+ () ->
+ createSetupFrame(
+ DEFAULT,
+ true,
+ Duration.ofMillis(1),
+ Duration.ofMillis(1),
+ null,
+ null,
+ "",
+ (ByteBuf) null,
+ null))
+ .withMessage("metadataMimeType must not be null");
+ }
+
+ @DisplayName("creates SETUP frame with data")
+ @Test
+ void createSetupFrameWithData() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+ String metadataMimeType = getRandomString(2);
+ ByteBuf metadataMimeTypeBuf = Unpooled.copiedBuffer(metadataMimeType, UTF_8);
+ String dataMimeType = getRandomString(3);
+ ByteBuf dataMimeTypeBuf = Unpooled.copiedBuffer(dataMimeType, UTF_8);
+ ByteBuf data = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(30)
+ .writeShort(0b00000100_11000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeTypeBuf, 0, metadataMimeTypeBuf.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeTypeBuf, 0, dataMimeTypeBuf.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000)
+ .writeBytes(data, 0, data.readableBytes());
+
+ SetupFrame frame =
+ createSetupFrame(
+ DEFAULT,
+ true,
+ 100,
+ 200,
+ Duration.ofMillis(300),
+ Duration.ofMillis(400),
+ resumeIdentificationToken,
+ metadataMimeType,
+ dataMimeType,
+ null,
+ data);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates SETUP frame with metadata")
+ @Test
+ void createSetupFrameWithMetadata() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+ String metadataMimeType = getRandomString(2);
+ ByteBuf metadataMimeTypeBuf = Unpooled.copiedBuffer(metadataMimeType, UTF_8);
+ String dataMimeType = getRandomString(3);
+ ByteBuf dataMimeTypeBuf = Unpooled.copiedBuffer(dataMimeType, UTF_8);
+ ByteBuf metadata = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(30)
+ .writeShort(0b00000101_11000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeTypeBuf, 0, metadataMimeTypeBuf.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeTypeBuf, 0, dataMimeTypeBuf.readableBytes())
+ .writeMedium(0b00000000_00000000_00000010)
+ .writeBytes(metadata, 0, metadata.readableBytes());
+
+ SetupFrame frame =
+ createSetupFrame(
+ DEFAULT,
+ true,
+ 100,
+ 200,
+ Duration.ofMillis(300),
+ Duration.ofMillis(400),
+ resumeIdentificationToken,
+ metadataMimeType,
+ dataMimeType,
+ metadata,
+ null);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates SETUP frame with resume identification token")
+ @Test
+ void createSetupFrameWithResumeIdentificationToken() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+ String metadataMimeType = getRandomString(2);
+ ByteBuf metadataMimeTypeBuf = Unpooled.copiedBuffer(metadataMimeType, UTF_8);
+ String dataMimeType = getRandomString(3);
+ ByteBuf dataMimeTypeBuf = Unpooled.copiedBuffer(dataMimeType, UTF_8);
+
+ ByteBuf expected =
+ Unpooled.buffer(32)
+ .writeShort(0b00000100_11000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeTypeBuf, 0, metadataMimeTypeBuf.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeTypeBuf, 0, dataMimeTypeBuf.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000);
+
+ SetupFrame frame =
+ createSetupFrame(
+ DEFAULT,
+ true,
+ 100,
+ 200,
+ Duration.ofMillis(300),
+ Duration.ofMillis(400),
+ resumeIdentificationToken,
+ metadataMimeType,
+ dataMimeType,
+ null,
+ null);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates SETUP frame without data")
+ @Test
+ void createSetupFrameWithoutData() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+ String metadataMimeType = getRandomString(2);
+ ByteBuf metadataMimeTypeBuf = Unpooled.copiedBuffer(metadataMimeType, UTF_8);
+ String dataMimeType = getRandomString(3);
+ ByteBuf dataMimeTypeBuf = Unpooled.copiedBuffer(dataMimeType, UTF_8);
+
+ ByteBuf expected =
+ Unpooled.buffer(30)
+ .writeShort(0b00000100_11000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeTypeBuf, 0, metadataMimeTypeBuf.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeTypeBuf, 0, dataMimeTypeBuf.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000);
+
+ SetupFrame frame =
+ createSetupFrame(
+ DEFAULT,
+ true,
+ 100,
+ 200,
+ Duration.ofMillis(300),
+ Duration.ofMillis(400),
+ resumeIdentificationToken,
+ metadataMimeType,
+ dataMimeType,
+ null,
+ null);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates SETUP frame without metadata")
+ @Test
+ void createSetupFrameWithoutMetadata() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+ String metadataMimeType = getRandomString(2);
+ ByteBuf metadataMimeTypeBuf = Unpooled.copiedBuffer(metadataMimeType, UTF_8);
+ String dataMimeType = getRandomString(3);
+ ByteBuf dataMimeTypeBuf = Unpooled.copiedBuffer(dataMimeType, UTF_8);
+
+ ByteBuf expected =
+ Unpooled.buffer(30)
+ .writeShort(0b00000100_11000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeTypeBuf, 0, metadataMimeTypeBuf.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeTypeBuf, 0, dataMimeTypeBuf.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000);
+
+ SetupFrame frame =
+ createSetupFrame(
+ DEFAULT,
+ true,
+ 100,
+ 200,
+ Duration.ofMillis(300),
+ Duration.ofMillis(400),
+ resumeIdentificationToken,
+ metadataMimeType,
+ dataMimeType,
+ null,
+ null);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("creates SETUP frame without resume identification token")
+ @Test
+ void createSetupFrameWithoutResumeIdentificationToken() {
+ String metadataMimeType = getRandomString(2);
+ ByteBuf metadataMimeTypeBuf = Unpooled.copiedBuffer(metadataMimeType, UTF_8);
+ String dataMimeType = getRandomString(3);
+ ByteBuf dataMimeTypeBuf = Unpooled.copiedBuffer(dataMimeType, UTF_8);
+
+ ByteBuf expected =
+ Unpooled.buffer(32)
+ .writeShort(0b00000100_01000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeTypeBuf, 0, metadataMimeTypeBuf.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeTypeBuf, 0, dataMimeTypeBuf.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000);
+
+ SetupFrame frame =
+ createSetupFrame(
+ DEFAULT,
+ true,
+ 100,
+ 200,
+ Duration.ofMillis(300),
+ Duration.ofMillis(400),
+ null,
+ metadataMimeType,
+ dataMimeType,
+ null,
+ null);
+
+ assertThat(frame.mapFrame(Function.identity())).isEqualTo(expected);
+ }
+
+ @DisplayName("returns data mime type")
+ @Test
+ void getDataMimeType() {
+ String metadataMimeType = getRandomString(2);
+ ByteBuf metadataMimeTypeBuf = Unpooled.copiedBuffer(metadataMimeType, UTF_8);
+ String dataMimeType = getRandomString(3);
+ ByteBuf dataMimeTypeBuf = Unpooled.copiedBuffer(dataMimeType, UTF_8);
+
+ SetupFrame frame =
+ createSetupFrame(
+ Unpooled.buffer(23)
+ .writeShort(0b00000100_01000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeTypeBuf, 0, metadataMimeTypeBuf.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeTypeBuf, 0, dataMimeTypeBuf.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.getDataMimeType()).isEqualTo(dataMimeType);
+ }
+
+ @DisplayName("returns the keepalive interval")
+ @Test
+ void getKeepaliveInterval() {
+ ByteBuf metadataMimeType = getRandomByteBuf(2);
+ ByteBuf dataMimeType = getRandomByteBuf(3);
+
+ SetupFrame frame =
+ createSetupFrame(
+ Unpooled.buffer(23)
+ .writeShort(0b00000100_01000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeType, 0, metadataMimeType.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeType, 0, dataMimeType.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.getKeepAliveInterval()).isEqualTo(Duration.ofMillis(300));
+ }
+
+ @DisplayName("returns major version")
+ @Test
+ void getMajorVersion() {
+ ByteBuf metadataMimeType = getRandomByteBuf(2);
+ ByteBuf dataMimeType = getRandomByteBuf(3);
+
+ SetupFrame frame =
+ createSetupFrame(
+ Unpooled.buffer(23)
+ .writeShort(0b00000100_01000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeType, 0, metadataMimeType.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeType, 0, dataMimeType.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.getMajorVersion()).isEqualTo(100);
+ }
+
+ @DisplayName("returns the max lifetime")
+ @Test
+ void getMaxLifetime() {
+ ByteBuf metadataMimeType = getRandomByteBuf(2);
+ ByteBuf dataMimeType = getRandomByteBuf(3);
+
+ SetupFrame frame =
+ createSetupFrame(
+ Unpooled.buffer(23)
+ .writeShort(0b00000100_01000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeType, 0, metadataMimeType.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeType, 0, dataMimeType.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.getMaxLifetime()).isEqualTo(Duration.ofMillis(400));
+ }
+
+ @DisplayName("returns metadata mime type")
+ @Test
+ void getMetadataMimeType() {
+ String metadataMimeType = getRandomString(2);
+ ByteBuf metadataMimeTypeBuf = Unpooled.copiedBuffer(metadataMimeType, UTF_8);
+ String dataMimeType = getRandomString(3);
+ ByteBuf dataMimeTypeBuf = Unpooled.copiedBuffer(dataMimeType, UTF_8);
+
+ SetupFrame frame =
+ createSetupFrame(
+ Unpooled.buffer(23)
+ .writeShort(0b00000100_01000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeTypeBuf, 0, metadataMimeTypeBuf.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeTypeBuf, 0, dataMimeTypeBuf.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.getMetadataMimeType()).isEqualTo(metadataMimeType);
+ }
+
+ @DisplayName("returns minor version")
+ @Test
+ void getMinorVersion() {
+ ByteBuf metadataMimeType = getRandomByteBuf(2);
+ ByteBuf dataMimeType = getRandomByteBuf(3);
+
+ SetupFrame frame =
+ createSetupFrame(
+ Unpooled.buffer(23)
+ .writeShort(0b00000100_01000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeType, 0, metadataMimeType.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeType, 0, dataMimeType.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.getMinorVersion()).isEqualTo(200);
+ }
+
+ @DisplayName("tests lease flag not set")
+ @Test
+ void isLeaseFlagSetFalse() {
+ ByteBuf metadataMimeType = getRandomByteBuf(2);
+ ByteBuf dataMimeType = getRandomByteBuf(3);
+
+ SetupFrame frame =
+ createSetupFrame(
+ Unpooled.buffer(23)
+ .writeShort(0b00000100_00000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeType, 0, metadataMimeType.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeType, 0, dataMimeType.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.isLeaseFlagSet()).isFalse();
+ }
+
+ @DisplayName("test lease flag set")
+ @Test
+ void isLeaseFlagSetTrue() {
+ ByteBuf metadataMimeType = getRandomByteBuf(2);
+ ByteBuf dataMimeType = getRandomByteBuf(3);
+
+ SetupFrame frame =
+ createSetupFrame(
+ Unpooled.buffer(23)
+ .writeShort(0b00000100_01000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeType, 0, metadataMimeType.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeType, 0, dataMimeType.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.isLeaseFlagSet()).isTrue();
+ }
+
+ @DisplayName("maps empty optional for resume identification token")
+ @Test
+ void mapResumeIdentificationNoFlag() {
+ ByteBuf metadataMimeType = getRandomByteBuf(2);
+ ByteBuf dataMimeType = getRandomByteBuf(3);
+
+ SetupFrame frame =
+ createSetupFrame(
+ Unpooled.buffer(28)
+ .writeShort(0b00000101_01000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeType, 0, metadataMimeType.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeType, 0, dataMimeType.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.mapResumeIdentificationToken(Function.identity())).isEmpty();
+ }
+
+ @DisplayName("maps resume identification token")
+ @Test
+ void mapResumeIdentificationToken() {
+ ByteBuf resumeIdentificationToken = getRandomByteBuf(2);
+ ByteBuf metadataMimeType = getRandomByteBuf(2);
+ ByteBuf dataMimeType = getRandomByteBuf(3);
+
+ SetupFrame frame =
+ createSetupFrame(
+ Unpooled.buffer(28)
+ .writeShort(0b00000101_11000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeShort(0b00000000_00000010)
+ .writeBytes(resumeIdentificationToken, 0, resumeIdentificationToken.readableBytes())
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeType, 0, metadataMimeType.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeType, 0, dataMimeType.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.mapResumeIdentificationToken(Function.identity()))
+ .hasValue(resumeIdentificationToken);
+ }
+
+ @DisplayName("maps empty resume identification token")
+ @Test
+ void mapResumeIdentificationTokenEmpty() {
+ ByteBuf metadataMimeType = getRandomByteBuf(2);
+ ByteBuf dataMimeType = getRandomByteBuf(3);
+
+ SetupFrame frame =
+ createSetupFrame(
+ Unpooled.buffer(28)
+ .writeShort(0b00000101_11000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeShort(0b00000000_00000000)
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeType, 0, metadataMimeType.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeType, 0, dataMimeType.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThat(frame.mapResumeIdentificationToken(Function.identity())).hasValue(EMPTY_BUFFER);
+ }
+
+ @DisplayName("mapResumeIdentificationToken throws NullPointerException with null function")
+ @Test
+ void mapResumeIdentificationTokenNullFunction() {
+ ByteBuf metadataMimeType = getRandomByteBuf(2);
+ ByteBuf dataMimeType = getRandomByteBuf(3);
+
+ SetupFrame frame =
+ createSetupFrame(
+ Unpooled.buffer(24)
+ .writeShort(0b00000101_01000000)
+ .writeShort(0b00000000_01100100)
+ .writeShort(0b00000000_11001000)
+ .writeInt(0b00000000_00000000_0000001_00101100)
+ .writeInt(0b00000000_00000000_0000001_10010000)
+ .writeByte(0b00000010)
+ .writeBytes(metadataMimeType, 0, metadataMimeType.readableBytes())
+ .writeByte(0b00000011)
+ .writeBytes(dataMimeType, 0, dataMimeType.readableBytes())
+ .writeMedium(0b00000000_00000000_00000000));
+
+ assertThatNullPointerException()
+ .isThrownBy(() -> frame.mapResumeIdentificationToken(null))
+ .withMessage("function must not be null");
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/StreamIdFrameTest.java b/rsocket-core/src/test/java/io/rsocket/framing/StreamIdFrameTest.java
new file mode 100644
index 000000000..9b3a97db6
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/StreamIdFrameTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.StreamIdFrame.createStreamIdFrame;
+import static io.rsocket.framing.TestFrames.createTestCancelFrame;
+import static io.rsocket.framing.TestFrames.createTestFrame;
+import static io.rsocket.test.util.ByteBufUtils.getRandomByteBuf;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.util.function.Function;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
+
+final class StreamIdFrameTest implements FrameTest {
+
+ @Override
+ public Function getCreateFrameFromByteBuf() {
+ return StreamIdFrame::createStreamIdFrame;
+ }
+
+ @Override
+ public Tuple2 getFrame() {
+ ByteBuf byteBuf =
+ Unpooled.buffer(6)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeBytes(getRandomByteBuf(2));
+
+ StreamIdFrame frame = createStreamIdFrame(byteBuf);
+
+ return Tuples.of(frame, byteBuf);
+ }
+
+ @DisplayName("creates stream id frame with ByteBufAllocator")
+ @Test
+ void createStreamIdFrameByteBufAllocator() {
+ ByteBuf frame = getRandomByteBuf(2);
+
+ ByteBuf expected =
+ Unpooled.buffer(6)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeBytes(frame, 0, frame.readableBytes());
+
+ assertThat(
+ createStreamIdFrame(DEFAULT, 100, createTestFrame(frame)).mapFrame(Function.identity()))
+ .isEqualTo(expected);
+ }
+
+ @DisplayName("createStreamIdFrame throws NullPointerException with null byteBufAllocator")
+ @Test
+ void createStreamIdFrameNullByteBufAllocator() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createStreamIdFrame(null, 0, createTestCancelFrame()))
+ .withMessage("byteBufAllocator must not be null");
+ }
+
+ @DisplayName("createStreamIdFrame throws NullPointerException with null frame")
+ @Test
+ void createStreamIdFrameNullFrame() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> createStreamIdFrame(DEFAULT, 0, null))
+ .withMessage("frame must not be null");
+ }
+
+ @DisplayName("returns stream id")
+ @Test
+ void getStreamId() {
+ StreamIdFrame frame =
+ createStreamIdFrame(Unpooled.buffer(4).writeInt(0b00000000_00000000_00000000_01100100));
+
+ assertThat(frame.getStreamId()).isEqualTo(100);
+ }
+
+ @DisplayName("maps byteBuf without stream id")
+ @Test
+ void mapFrameWithoutStreamId() {
+ ByteBuf frame = getRandomByteBuf(2);
+
+ StreamIdFrame streamIdFrame =
+ createStreamIdFrame(
+ Unpooled.buffer(6)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeBytes(frame, 0, frame.readableBytes()));
+
+ assertThat(streamIdFrame.mapFrameWithoutStreamId(Function.identity())).isEqualTo(frame);
+ }
+
+ @DisplayName("mapFrameWithoutStreamId throws NullPointerException with null function")
+ @Test
+ void mapFrameWithoutStreamIdNullFunction() {
+ ByteBuf frame = getRandomByteBuf(2);
+
+ StreamIdFrame streamIdFrame =
+ createStreamIdFrame(
+ Unpooled.buffer(6)
+ .writeInt(0b00000000_00000000_00000000_01100100)
+ .writeBytes(frame, 0, frame.readableBytes()));
+
+ assertThatNullPointerException()
+ .isThrownBy(() -> streamIdFrame.mapFrameWithoutStreamId(null))
+ .withMessage("function must not be null");
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/framing/TestFrames.java b/rsocket-core/src/test/java/io/rsocket/framing/TestFrames.java
new file mode 100644
index 000000000..5c0d23536
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/framing/TestFrames.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.framing;
+
+import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
+import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT;
+import static io.rsocket.framing.CancelFrame.createCancelFrame;
+import static io.rsocket.framing.ErrorFrame.createErrorFrame;
+import static io.rsocket.framing.ExtensionFrame.createExtensionFrame;
+import static io.rsocket.framing.FrameLengthFrame.createFrameLengthFrame;
+import static io.rsocket.framing.KeepaliveFrame.createKeepaliveFrame;
+import static io.rsocket.framing.LeaseFrame.createLeaseFrame;
+import static io.rsocket.framing.MetadataPushFrame.createMetadataPushFrame;
+import static io.rsocket.framing.PayloadFrame.createPayloadFrame;
+import static io.rsocket.framing.RequestChannelFrame.createRequestChannelFrame;
+import static io.rsocket.framing.RequestFireAndForgetFrame.createRequestFireAndForgetFrame;
+import static io.rsocket.framing.RequestNFrame.createRequestNFrame;
+import static io.rsocket.framing.RequestResponseFrame.createRequestResponseFrame;
+import static io.rsocket.framing.RequestStreamFrame.createRequestStreamFrame;
+import static io.rsocket.framing.ResumeFrame.createResumeFrame;
+import static io.rsocket.framing.ResumeOkFrame.createResumeOkFrame;
+import static io.rsocket.framing.SetupFrame.createSetupFrame;
+import static io.rsocket.framing.StreamIdFrame.createStreamIdFrame;
+
+import io.netty.buffer.ByteBuf;
+import java.time.Duration;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public final class TestFrames {
+
+ private TestFrames() {}
+
+ public static CancelFrame createTestCancelFrame() {
+ return createCancelFrame(DEFAULT);
+ }
+
+ public static ErrorFrame createTestErrorFrame() {
+ return createErrorFrame(DEFAULT, 1, (ByteBuf) null);
+ }
+
+ public static ExtensionFrame createTestExtensionFrame() {
+ return createExtensionFrame(DEFAULT, true, 1, (ByteBuf) null, null);
+ }
+
+ public static Frame createTestFrame(ByteBuf byteBuf) {
+ return new TestFrame(byteBuf);
+ }
+
+ public static FrameLengthFrame createTestFrameLengthFrame() {
+ return createFrameLengthFrame(DEFAULT, createTestStreamIdFrame());
+ }
+
+ public static KeepaliveFrame createTestKeepaliveFrame() {
+ return createKeepaliveFrame(DEFAULT, false, 1, null);
+ }
+
+ public static LeaseFrame createTestLeaseFrame() {
+ return createLeaseFrame(DEFAULT, Duration.ofMillis(1), 1, null);
+ }
+
+ public static MetadataPushFrame createTestMetadataPushFrame() {
+ return createMetadataPushFrame(DEFAULT, EMPTY_BUFFER);
+ }
+
+ public static PayloadFrame createTestPayloadFrame() {
+ return createPayloadFrame(DEFAULT, false, true, (ByteBuf) null, null);
+ }
+
+ public static RequestChannelFrame createTestRequestChannelFrame() {
+ return createRequestChannelFrame(DEFAULT, false, false, 1, (ByteBuf) null, null);
+ }
+
+ public static RequestFireAndForgetFrame createTestRequestFireAndForgetFrame() {
+ return createRequestFireAndForgetFrame(DEFAULT, false, (ByteBuf) null, null);
+ }
+
+ public static RequestNFrame createTestRequestNFrame() {
+ return createRequestNFrame(DEFAULT, 1);
+ }
+
+ public static RequestResponseFrame createTestRequestResponseFrame() {
+ return createRequestResponseFrame(DEFAULT, false, (ByteBuf) null, null);
+ }
+
+ public static RequestStreamFrame createTestRequestStreamFrame() {
+ return createRequestStreamFrame(DEFAULT, false, 1, (ByteBuf) null, null);
+ }
+
+ public static ResumeFrame createTestResumeFrame() {
+ return createResumeFrame(DEFAULT, 1, 0, EMPTY_BUFFER, 1, 1);
+ }
+
+ public static ResumeOkFrame createTestResumeOkFrame() {
+ return createResumeOkFrame(DEFAULT, 1);
+ }
+
+ public static SetupFrame createTestSetupFrame() {
+ return createSetupFrame(
+ DEFAULT, true, 1, 1, Duration.ofMillis(1), Duration.ofMillis(1), null, "", "", null, null);
+ }
+
+ public static StreamIdFrame createTestStreamIdFrame() {
+ return createStreamIdFrame(DEFAULT, 1, createTestCancelFrame());
+ }
+
+ private static final class TestFrame implements Frame {
+
+ private final ByteBuf byteBuf;
+
+ private TestFrame(ByteBuf byteBuf) {
+ this.byteBuf = byteBuf;
+ }
+
+ @Override
+ public void consumeFrame(Consumer consumer) {
+ consumer.accept(byteBuf.asReadOnly());
+ }
+
+ @Override
+ public void dispose() {}
+
+ @Override
+ public T mapFrame(Function function) {
+ return function.apply(byteBuf.asReadOnly());
+ }
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/resume/ResumeCacheTest.java b/rsocket-core/src/test/java/io/rsocket/resume/ResumeCacheTest.java
index ab7fde89d..25d3f1696 100644
--- a/rsocket-core/src/test/java/io/rsocket/resume/ResumeCacheTest.java
+++ b/rsocket-core/src/test/java/io/rsocket/resume/ResumeCacheTest.java
@@ -19,7 +19,7 @@
import static org.junit.Assert.assertEquals;
import io.rsocket.Frame;
-import io.rsocket.FrameType;
+import io.rsocket.framing.FrameType;
import io.rsocket.util.DefaultPayload;
import org.junit.Test;
import reactor.core.publisher.Flux;
diff --git a/rsocket-core/src/test/java/io/rsocket/resume/ResumeUtilTest.java b/rsocket-core/src/test/java/io/rsocket/resume/ResumeUtilTest.java
index a6f78e15c..ea5486e14 100644
--- a/rsocket-core/src/test/java/io/rsocket/resume/ResumeUtilTest.java
+++ b/rsocket-core/src/test/java/io/rsocket/resume/ResumeUtilTest.java
@@ -21,7 +21,7 @@
import static org.junit.Assert.assertTrue;
import io.rsocket.Frame;
-import io.rsocket.FrameType;
+import io.rsocket.framing.FrameType;
import io.rsocket.util.DefaultPayload;
import org.junit.Test;
@@ -38,7 +38,7 @@ public void testSupportedTypes() {
assertTrue(ResumeUtil.isTracked(FrameType.REQUEST_N));
assertTrue(ResumeUtil.isTracked(FrameType.CANCEL));
assertTrue(ResumeUtil.isTracked(FrameType.ERROR));
- assertTrue(ResumeUtil.isTracked(FrameType.FIRE_AND_FORGET));
+ assertTrue(ResumeUtil.isTracked(FrameType.REQUEST_FNF));
assertTrue(ResumeUtil.isTracked(FrameType.PAYLOAD));
}
diff --git a/rsocket-core/src/test/java/io/rsocket/test/util/ByteBufUtils.java b/rsocket-core/src/test/java/io/rsocket/test/util/ByteBufUtils.java
new file mode 100644
index 000000000..9bed415ae
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/test/util/ByteBufUtils.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.test.util;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.util.concurrent.ThreadLocalRandom;
+
+public final class ByteBufUtils {
+
+ private ByteBufUtils() {}
+
+ public static ByteBuf getRandomByteBuf(int size) {
+ byte[] bytes = new byte[size];
+ ThreadLocalRandom.current().nextBytes(bytes);
+ return Unpooled.wrappedBuffer(bytes);
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/test/util/StringUtils.java b/rsocket-core/src/test/java/io/rsocket/test/util/StringUtils.java
new file mode 100644
index 000000000..403eacb6d
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/test/util/StringUtils.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.test.util;
+
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Collectors;
+
+public final class StringUtils {
+
+ private static final String CANDIDATE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
+
+ private StringUtils() {}
+
+ public static String getRandomString(int size) {
+ return ThreadLocalRandom.current()
+ .ints(size, 0, CANDIDATE_CHARS.length())
+ .mapToObj(index -> ((Character) CANDIDATE_CHARS.charAt(index)).toString())
+ .collect(Collectors.joining());
+ }
+}
diff --git a/rsocket-core/src/test/java/io/rsocket/util/NumberUtilsTest.java b/rsocket-core/src/test/java/io/rsocket/util/NumberUtilsTest.java
new file mode 100644
index 000000000..5dd978c9b
--- /dev/null
+++ b/rsocket-core/src/test/java/io/rsocket/util/NumberUtilsTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2015-2018 the original author or authors.
+ *
+ * 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 io.rsocket.util;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import io.rsocket.util.NumberUtils;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+final class NumberUtilsTest {
+
+ @DisplayName("returns long value with positive int")
+ @Test
+ void requirePositiveInt() {
+ assertThat(NumberUtils.requirePositive(Integer.MAX_VALUE, "test-message"))
+ .isEqualTo(Integer.MAX_VALUE);
+ }
+
+ @DisplayName(
+ "requirePositive with int argument throws IllegalArgumentException with negative value")
+ @Test
+ void requirePositiveIntNegative() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> NumberUtils.requirePositive(Integer.MIN_VALUE, "test-message"))
+ .withMessage("test-message");
+ }
+
+ @DisplayName("requirePositive with int argument throws NullPointerException with null message")
+ @Test
+ void requirePositiveIntNullMessage() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> NumberUtils.requirePositive(Integer.MIN_VALUE, null))
+ .withMessage("message must not be null");
+ }
+
+ @DisplayName("requirePositive with int argument throws IllegalArgumentException with zero value")
+ @Test
+ void requirePositiveIntZero() {
+
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> NumberUtils.requirePositive(0, "test-message"))
+ .withMessage("test-message");
+ }
+
+ @DisplayName("returns long value with positive long")
+ @Test
+ void requirePositiveLong() {
+ assertThat(NumberUtils.requirePositive(Long.MAX_VALUE, "test-message"))
+ .isEqualTo(Long.MAX_VALUE);
+ }
+
+ @DisplayName(
+ "requirePositive with long argument throws IllegalArgumentException with negative value")
+ @Test
+ void requirePositiveLongNegative() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> NumberUtils.requirePositive(Long.MIN_VALUE, "test-message"))
+ .withMessage("test-message");
+ }
+
+ @DisplayName("requirePositive with long argument throws NullPointerException with null message")
+ @Test
+ void requirePositiveLongNullMessage() {
+ assertThatNullPointerException()
+ .isThrownBy(() -> NumberUtils.requirePositive(Long.MIN_VALUE, null))
+ .withMessage("message must not be null");
+ }
+
+ @DisplayName("requirePositive with long argument throws IllegalArgumentException with zero value")
+ @Test
+ void requirePositiveLongZero() {
+
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> NumberUtils.requirePositive(0L, "test-message"))
+ .withMessage("test-message");
+ }
+
+ @DisplayName("requireUnsignedByte returns length if 255")
+ @Test
+ void requireUnsignedByte() {
+ assertThat(NumberUtils.requireUnsignedByte((1 << 8) - 1)).isEqualTo(255);
+ }
+
+ @DisplayName("requireUnsignedByte throws IllegalArgumentException if larger than 255")
+ @Test
+ void requireUnsignedByteOverFlow() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> NumberUtils.requireUnsignedByte(1 << 8))
+ .withMessage("%d is larger than 8 bits", 1 << 8);
+ }
+
+ @DisplayName("requireUnsignedMedium returns length if 16_777_215")
+ @Test
+ void requireUnsignedMedium() {
+ assertThat(NumberUtils.requireUnsignedMedium((1 << 24) - 1)).isEqualTo(16_777_215);
+ }
+
+ @DisplayName("requireUnsignedMedium throws IllegalArgumentException if larger than 16_777_215")
+ @Test
+ void requireUnsignedMediumOverFlow() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> NumberUtils.requireUnsignedMedium(1 << 24))
+ .withMessage("%d is larger than 24 bits", 1 << 24);
+ }
+
+ @DisplayName("requireUnsignedShort returns length if 65_535")
+ @Test
+ void requireUnsignedShort() {
+ assertThat(NumberUtils.requireUnsignedShort((1 << 16) - 1)).isEqualTo(65_535);
+ }
+
+ @DisplayName("requireUnsignedShort throws IllegalArgumentException if larger than 65_535")
+ @Test
+ void requireUnsignedShortOverFlow() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> NumberUtils.requireUnsignedShort(1 << 16))
+ .withMessage("%d is larger than 16 bits", 1 << 16);
+ }
+}
diff --git a/rsocket-core/src/test/resources/log4j.properties b/rsocket-core/src/test/resources/META-INF/services/org.assertj.core.presentation.Representation
similarity index 67%
rename from rsocket-core/src/test/resources/log4j.properties
rename to rsocket-core/src/test/resources/META-INF/services/org.assertj.core.presentation.Representation
index 5bfca1a11..723d87c20 100644
--- a/rsocket-core/src/test/resources/log4j.properties
+++ b/rsocket-core/src/test/resources/META-INF/services/org.assertj.core.presentation.Representation
@@ -13,9 +13,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-log4j.rootLogger=INFO, stdout
-
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{dd MMM yyyy HH:mm:ss,SSS} %5p [%t] (%F:%L) - %m%n
-#log4j.logger.io.rsocket.FrameLogger=Info
\ No newline at end of file
+io.rsocket.framing.ByteBufRepresentation
\ No newline at end of file
diff --git a/rsocket-core/src/test/resources/logback-test.xml b/rsocket-core/src/test/resources/logback-test.xml
new file mode 100644
index 000000000..9081698fb
--- /dev/null
+++ b/rsocket-core/src/test/resources/logback-test.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+ %date{HH:mm:ss.SSS} %-10thread %-42logger %msg%n
+
+
+
+
+
+
+
+
+
+
diff --git a/rsocket-spectator/src/main/java/io/rsocket/spectator/SpectatorFrameInterceptor.java b/rsocket-spectator/src/main/java/io/rsocket/spectator/SpectatorFrameInterceptor.java
index e39c67ae9..14902d447 100644
--- a/rsocket-spectator/src/main/java/io/rsocket/spectator/SpectatorFrameInterceptor.java
+++ b/rsocket-spectator/src/main/java/io/rsocket/spectator/SpectatorFrameInterceptor.java
@@ -20,7 +20,7 @@
import com.netflix.spectator.api.Registry;
import io.rsocket.DuplexConnection;
import io.rsocket.Frame;
-import io.rsocket.FrameType;
+import io.rsocket.framing.FrameType;
import io.rsocket.plugins.DuplexConnectionInterceptor;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
@@ -44,7 +44,7 @@ public DuplexConnection apply(Type type, DuplexConnection connection) {
Counter errorCounter = registry.counter(FrameType.ERROR.name(), type.name());
Counter extCounter = registry.counter(FrameType.EXT.name(), type.name());
Counter fireAndForgetCounter =
- registry.counter(FrameType.FIRE_AND_FORGET.name(), type.name());
+ registry.counter(FrameType.REQUEST_FNF.name(), type.name());
Counter keepAliveCounter = registry.counter(FrameType.KEEPALIVE.name(), type.name());
Counter leaseCounter = registry.counter(FrameType.LEASE.name(), type.name());
Counter metadataPushCounter = registry.counter(FrameType.METADATA_PUSH.name(), type.name());
@@ -58,7 +58,7 @@ public DuplexConnection apply(Type type, DuplexConnection connection) {
Counter resumeCounter = registry.counter(FrameType.RESUME.name(), type.name());
Counter resumeOkCounter = registry.counter(FrameType.RESUME_OK.name(), type.name());
Counter setupCounter = registry.counter(FrameType.SETUP.name(), type.name());
- Counter undefinedCounter = registry.counter(FrameType.UNDEFINED.name(), type.name());
+ Counter undefinedCounter = registry.counter(FrameType.RESERVED.name(), type.name());
@Override
public Mono send(Publisher frame) {
@@ -116,7 +116,7 @@ private void count(Frame frame) {
case EXT:
extCounter.increment();
break;
- case FIRE_AND_FORGET:
+ case REQUEST_FNF:
fireAndForgetCounter.increment();
break;
case KEEPALIVE:
@@ -155,7 +155,7 @@ private void count(Frame frame) {
case SETUP:
setupCounter.increment();
break;
- case UNDEFINED:
+ case RESERVED:
default:
undefinedCounter.increment();
break;