diff --git a/.travis.yml b/.travis.yml
index 0039232f7..2b847142c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -19,7 +19,7 @@ cache:
directories:
- $HOME/.m2
- $HOME/.gradle
-
+
env:
global:
- secure: cXHzd2WHqmdmJEyEKlELt8Rp9qCvhTRXTEHpQz0sKt55KorI8vO33sSOBs8uBqknWgGgOzHsB7cw0dJRxCmW+BRy90ELtdg/dVLzU8D8BrI6/DHzd/Bhyt9wx2eVdLmDV7lQ113AqJ7lphbH+U8ceTBlbNYDPKcIjFhsPO0WcPxQYed45na8XRK0UcAOpVmmNlTE6fHy5acQblNO84SN6uevCFqWAZJY7rc6xGrzFzca+ul5kR8xIzdE5jKs2Iw0MDeWi8cshkhj9c0FDtfsNIB1F+NafDtEdqjt6kMqYAUUiTAM2QdNoffzgmWEbVOj3uvthlm+S11XaU3Cn2uC7CiZTn2ebuoqCuV5Ge6KQI0ysEQVUfLhIF7iJG6dJvoyYy8ta8LEcjcsYAdF34BVddoUJkp+eJuhlto2aTZsDdXpmnwRM1PPDRoyrLjRcKiWYPR2tO2RG9sb0nRAGEpHTDd5ju2Ta4zpvgpWGUiKprs5R+YY7TEg16VSTYMmCJj5C9ap2lYIH4EoxsQpuxYig9AV1sOUJujLSa4TXqlcOmSM0IkHJ/i0VE8TZg4nV4XowyH6nKZ63InF4pUDcG13BpJQyTFKbK2D0lFn8MzpWvIV2oOUxNkOaOBg9cGhAnv9Sfw/Iv1UVaUgCNQd2M0R0rwfJoPCg2mmWVxsvh3cW4M=
diff --git a/README.md b/README.md
index d05fce64d..70f135c39 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ It enables the following interaction models via async message passing over a sin
- fire-and-forget (no response)
- event subscription (infinite stream of many)
-This is the core project for Java that implements the protocol and exposes Reactive Stream APIs. Typically most use will come via another library that uses this one.
+This is the core project for Java that implements the protocol and exposes Reactive Stream APIs. Typically most use will come via another library that uses this one.
For example:
@@ -27,7 +27,7 @@ Others can be found in the [ReactiveSocket Github](https://github.com/ReactiveSo
-Snapshots are available via JFrog.
+Snapshots are available via JFrog.
Example:
@@ -48,7 +48,6 @@ No releases to Maven Central or JCenter have occurred yet.
For bugs, questions and discussions please use the [Github Issues](https://github.com/ReactiveSocket/reactivesocket-java/issues).
-
## LICENSE
Copyright 2015 Netflix, Inc.
diff --git a/build.gradle b/build.gradle
index 8fbcaa6c4..18eaca87d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,27 +1,43 @@
buildscript {
- repositories {
- jcenter()
- }
-
- dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.5' }
+ repositories { jcenter() }
+ dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.6' }
}
description = 'ReactiveSocket: stream oriented messaging passing with Reactive Stream semantics.'
apply plugin: 'reactivesocket-project'
-apply plugin: 'java'
-
-repositories {
- maven { url 'https://oss.jfrog.org/libs-snapshot' }
-}
-
-dependencies {
- compile 'org.reactivestreams:reactive-streams:1.0.0.final'
- compile 'org.agrona:Agrona:0.4.13'
- testCompile 'io.reactivex:rxjava:2.0.0-DP0-20151003.214425-143'
- testCompile 'junit:junit:4.12'
- testCompile 'org.mockito:mockito-core:1.10.19'
+subprojects {
+ apply plugin: 'reactivesocket-project'
+ apply plugin: 'java'
+
+ compileJava {
+ sourceCompatibility = 1.8
+ targetCompatibility = 1.8
+ }
+
+ repositories {
+ jcenter()
+ maven { url 'https://oss.jfrog.org/libs-snapshot' }
+ maven { url 'https://dl.bintray.com/reactivesocket/ReactiveSocket' }
+ }
+
+ dependencies {
+ compile 'org.reactivestreams:reactive-streams:1.0.0.final'
+ compile 'org.agrona:Agrona:0.4.13'
+ compile 'io.reactivex:rxjava:latest.release'
+ compile 'io.reactivex:rxjava-reactive-streams:latest.release'
+ compile 'org.hdrhistogram:HdrHistogram:latest.release'
+ compile 'org.slf4j:slf4j-api:latest.release'
+
+ testCompile 'junit:junit:4.12'
+ testCompile 'org.mockito:mockito-core:1.10.19'
+ testRuntime 'org.slf4j:slf4j-simple:1.7.12'
+ }
+
+ test {
+ testLogging.showStandardStreams = true
+ }
}
// support for snapshot/final releases via versioned branch names like 1.x
@@ -33,12 +49,3 @@ nebulaRelease {
if (project.hasProperty('release.useLastTag')) {
tasks.prepare.enabled = false
}
-
-test {
- testLogging.showStandardStreams = true
-}
-
-compileJava {
- sourceCompatibility = 1.8
- targetCompatibility = 1.8
-}
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 5ccda13e9..2c6137b87 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index f1df5b75c..546631a96 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Mon Mar 07 16:10:12 PST 2016
+#Tue Mar 15 03:05:19 MSK 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.11-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-bin.zip
diff --git a/reactivesocket-core/build.gradle b/reactivesocket-core/build.gradle
new file mode 100644
index 000000000..60fb959de
--- /dev/null
+++ b/reactivesocket-core/build.gradle
@@ -0,0 +1,3 @@
+dependencies {
+ testCompile 'io.reactivex:rxjava:2.0.0-DP0-20151003.214425-143'
+}
\ No newline at end of file
diff --git a/src/main/java/io/reactivesocket/ConnectionSetupHandler.java b/reactivesocket-core/src/main/java/io/reactivesocket/ConnectionSetupHandler.java
similarity index 100%
rename from src/main/java/io/reactivesocket/ConnectionSetupHandler.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/ConnectionSetupHandler.java
diff --git a/src/main/java/io/reactivesocket/ConnectionSetupPayload.java b/reactivesocket-core/src/main/java/io/reactivesocket/ConnectionSetupPayload.java
similarity index 100%
rename from src/main/java/io/reactivesocket/ConnectionSetupPayload.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/ConnectionSetupPayload.java
diff --git a/src/main/java/io/reactivesocket/DefaultReactiveSocket.java b/reactivesocket-core/src/main/java/io/reactivesocket/DefaultReactiveSocket.java
similarity index 100%
rename from src/main/java/io/reactivesocket/DefaultReactiveSocket.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/DefaultReactiveSocket.java
diff --git a/src/main/java/io/reactivesocket/DuplexConnection.java b/reactivesocket-core/src/main/java/io/reactivesocket/DuplexConnection.java
similarity index 100%
rename from src/main/java/io/reactivesocket/DuplexConnection.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/DuplexConnection.java
diff --git a/src/main/java/io/reactivesocket/Frame.java b/reactivesocket-core/src/main/java/io/reactivesocket/Frame.java
similarity index 100%
rename from src/main/java/io/reactivesocket/Frame.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/Frame.java
diff --git a/src/main/java/io/reactivesocket/FrameType.java b/reactivesocket-core/src/main/java/io/reactivesocket/FrameType.java
similarity index 100%
rename from src/main/java/io/reactivesocket/FrameType.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/FrameType.java
diff --git a/src/main/java/io/reactivesocket/LeaseGovernor.java b/reactivesocket-core/src/main/java/io/reactivesocket/LeaseGovernor.java
similarity index 100%
rename from src/main/java/io/reactivesocket/LeaseGovernor.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/LeaseGovernor.java
diff --git a/src/main/java/io/reactivesocket/Payload.java b/reactivesocket-core/src/main/java/io/reactivesocket/Payload.java
similarity index 100%
rename from src/main/java/io/reactivesocket/Payload.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/Payload.java
diff --git a/src/main/java/io/reactivesocket/ReactiveSocket.java b/reactivesocket-core/src/main/java/io/reactivesocket/ReactiveSocket.java
similarity index 100%
rename from src/main/java/io/reactivesocket/ReactiveSocket.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/ReactiveSocket.java
diff --git a/src/main/java/io/reactivesocket/ReactiveSocketConnector.java b/reactivesocket-core/src/main/java/io/reactivesocket/ReactiveSocketConnector.java
similarity index 100%
rename from src/main/java/io/reactivesocket/ReactiveSocketConnector.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/ReactiveSocketConnector.java
diff --git a/src/main/java/io/reactivesocket/ReactiveSocketFactory.java b/reactivesocket-core/src/main/java/io/reactivesocket/ReactiveSocketFactory.java
similarity index 100%
rename from src/main/java/io/reactivesocket/ReactiveSocketFactory.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/ReactiveSocketFactory.java
diff --git a/src/main/java/io/reactivesocket/RequestHandler.java b/reactivesocket-core/src/main/java/io/reactivesocket/RequestHandler.java
similarity index 100%
rename from src/main/java/io/reactivesocket/RequestHandler.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/RequestHandler.java
diff --git a/src/main/java/io/reactivesocket/exceptions/ApplicationException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/ApplicationException.java
similarity index 100%
rename from src/main/java/io/reactivesocket/exceptions/ApplicationException.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/ApplicationException.java
diff --git a/src/main/java/io/reactivesocket/exceptions/CancelException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/CancelException.java
similarity index 100%
rename from src/main/java/io/reactivesocket/exceptions/CancelException.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/CancelException.java
diff --git a/src/main/java/io/reactivesocket/exceptions/ConnectionException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/ConnectionException.java
similarity index 100%
rename from src/main/java/io/reactivesocket/exceptions/ConnectionException.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/ConnectionException.java
diff --git a/src/main/java/io/reactivesocket/exceptions/Exceptions.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/Exceptions.java
similarity index 100%
rename from src/main/java/io/reactivesocket/exceptions/Exceptions.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/Exceptions.java
diff --git a/src/main/java/io/reactivesocket/exceptions/InvalidRequestException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/InvalidRequestException.java
similarity index 100%
rename from src/main/java/io/reactivesocket/exceptions/InvalidRequestException.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/InvalidRequestException.java
diff --git a/src/main/java/io/reactivesocket/exceptions/InvalidSetupException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/InvalidSetupException.java
similarity index 100%
rename from src/main/java/io/reactivesocket/exceptions/InvalidSetupException.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/InvalidSetupException.java
diff --git a/src/main/java/io/reactivesocket/exceptions/RejectedException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/RejectedException.java
similarity index 100%
rename from src/main/java/io/reactivesocket/exceptions/RejectedException.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/RejectedException.java
diff --git a/src/main/java/io/reactivesocket/exceptions/RejectedSetupException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/RejectedSetupException.java
similarity index 100%
rename from src/main/java/io/reactivesocket/exceptions/RejectedSetupException.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/RejectedSetupException.java
diff --git a/src/main/java/io/reactivesocket/exceptions/Retryable.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/Retryable.java
similarity index 100%
rename from src/main/java/io/reactivesocket/exceptions/Retryable.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/Retryable.java
diff --git a/src/main/java/io/reactivesocket/exceptions/SetupException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/SetupException.java
similarity index 100%
rename from src/main/java/io/reactivesocket/exceptions/SetupException.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/SetupException.java
diff --git a/src/main/java/io/reactivesocket/exceptions/TransportException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/TransportException.java
similarity index 100%
rename from src/main/java/io/reactivesocket/exceptions/TransportException.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/TransportException.java
diff --git a/src/main/java/io/reactivesocket/exceptions/UnsupportedSetupException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/UnsupportedSetupException.java
similarity index 100%
rename from src/main/java/io/reactivesocket/exceptions/UnsupportedSetupException.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/UnsupportedSetupException.java
diff --git a/src/main/java/io/reactivesocket/internal/FragmentedPublisher.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/FragmentedPublisher.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/FragmentedPublisher.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/FragmentedPublisher.java
diff --git a/src/main/java/io/reactivesocket/internal/PublisherUtils.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/PublisherUtils.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/PublisherUtils.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/PublisherUtils.java
diff --git a/src/main/java/io/reactivesocket/internal/Requester.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/Requester.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/Requester.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/Requester.java
diff --git a/src/main/java/io/reactivesocket/internal/Responder.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/Responder.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/Responder.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/Responder.java
diff --git a/src/main/java/io/reactivesocket/internal/UnicastSubject.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/UnicastSubject.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/UnicastSubject.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/UnicastSubject.java
diff --git a/src/main/java/io/reactivesocket/internal/frame/ByteBufferUtil.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/ByteBufferUtil.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/frame/ByteBufferUtil.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/ByteBufferUtil.java
diff --git a/src/main/java/io/reactivesocket/internal/frame/ErrorFrameFlyweight.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/ErrorFrameFlyweight.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/frame/ErrorFrameFlyweight.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/ErrorFrameFlyweight.java
diff --git a/src/main/java/io/reactivesocket/internal/frame/FrameHeaderFlyweight.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/FrameHeaderFlyweight.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/frame/FrameHeaderFlyweight.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/FrameHeaderFlyweight.java
diff --git a/src/main/java/io/reactivesocket/internal/frame/FramePool.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/FramePool.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/frame/FramePool.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/FramePool.java
diff --git a/src/main/java/io/reactivesocket/internal/frame/KeepaliveFrameFlyweight.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/KeepaliveFrameFlyweight.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/frame/KeepaliveFrameFlyweight.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/KeepaliveFrameFlyweight.java
diff --git a/src/main/java/io/reactivesocket/internal/frame/LeaseFrameFlyweight.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/LeaseFrameFlyweight.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/frame/LeaseFrameFlyweight.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/LeaseFrameFlyweight.java
diff --git a/src/main/java/io/reactivesocket/internal/frame/PayloadBuilder.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/PayloadBuilder.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/frame/PayloadBuilder.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/PayloadBuilder.java
diff --git a/src/main/java/io/reactivesocket/internal/frame/PayloadFragmenter.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/PayloadFragmenter.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/frame/PayloadFragmenter.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/PayloadFragmenter.java
diff --git a/src/main/java/io/reactivesocket/internal/frame/PayloadReassembler.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/PayloadReassembler.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/frame/PayloadReassembler.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/PayloadReassembler.java
diff --git a/src/main/java/io/reactivesocket/internal/frame/RequestFrameFlyweight.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/RequestFrameFlyweight.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/frame/RequestFrameFlyweight.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/RequestFrameFlyweight.java
diff --git a/src/main/java/io/reactivesocket/internal/frame/RequestNFrameFlyweight.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/RequestNFrameFlyweight.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/frame/RequestNFrameFlyweight.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/RequestNFrameFlyweight.java
diff --git a/src/main/java/io/reactivesocket/internal/frame/SetupFrameFlyweight.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/SetupFrameFlyweight.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/frame/SetupFrameFlyweight.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/SetupFrameFlyweight.java
diff --git a/src/main/java/io/reactivesocket/internal/frame/ThreadLocalFramePool.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/ThreadLocalFramePool.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/frame/ThreadLocalFramePool.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/ThreadLocalFramePool.java
diff --git a/src/main/java/io/reactivesocket/internal/frame/ThreadSafeFramePool.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/ThreadSafeFramePool.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/frame/ThreadSafeFramePool.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/ThreadSafeFramePool.java
diff --git a/src/main/java/io/reactivesocket/internal/frame/UnpooledFrame.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/UnpooledFrame.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/frame/UnpooledFrame.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/UnpooledFrame.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/AppendOnlyLinkedArrayList.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/AppendOnlyLinkedArrayList.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/AppendOnlyLinkedArrayList.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/AppendOnlyLinkedArrayList.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/BackpressureHelper.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BackpressureHelper.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/BackpressureHelper.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BackpressureHelper.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/BackpressureUtils.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BackpressureUtils.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/BackpressureUtils.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BackpressureUtils.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/BaseArrayQueue.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BaseArrayQueue.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/BaseArrayQueue.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BaseArrayQueue.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/BaseLinkedQueue.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BaseLinkedQueue.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/BaseLinkedQueue.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BaseLinkedQueue.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/BooleanDisposable.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BooleanDisposable.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/BooleanDisposable.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BooleanDisposable.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/CompositeCompletable.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/CompositeCompletable.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/CompositeCompletable.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/CompositeCompletable.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/CompositeDisposable.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/CompositeDisposable.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/CompositeDisposable.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/CompositeDisposable.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/EmptyDisposable.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/EmptyDisposable.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/EmptyDisposable.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/EmptyDisposable.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/EmptySubscription.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/EmptySubscription.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/EmptySubscription.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/EmptySubscription.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/LinkedQueueNode.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/LinkedQueueNode.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/LinkedQueueNode.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/LinkedQueueNode.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/MpscLinkedQueue.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/MpscLinkedQueue.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/MpscLinkedQueue.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/MpscLinkedQueue.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/NotificationLite.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/NotificationLite.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/NotificationLite.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/NotificationLite.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/OperatorConcatMap.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/OperatorConcatMap.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/OperatorConcatMap.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/OperatorConcatMap.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/Pow2.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/Pow2.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/Pow2.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/Pow2.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/QueueDrainHelper.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/QueueDrainHelper.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/QueueDrainHelper.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/QueueDrainHelper.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/README.md b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/README.md
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/README.md
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/README.md
diff --git a/src/main/java/io/reactivesocket/internal/rx/SerializedSubscriber.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SerializedSubscriber.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/SerializedSubscriber.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SerializedSubscriber.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/SpscArrayQueue.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SpscArrayQueue.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/SpscArrayQueue.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SpscArrayQueue.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/SpscExactArrayQueue.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SpscExactArrayQueue.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/SpscExactArrayQueue.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SpscExactArrayQueue.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/SubscriptionArbiter.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SubscriptionArbiter.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/SubscriptionArbiter.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SubscriptionArbiter.java
diff --git a/src/main/java/io/reactivesocket/internal/rx/SubscriptionHelper.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SubscriptionHelper.java
similarity index 100%
rename from src/main/java/io/reactivesocket/internal/rx/SubscriptionHelper.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SubscriptionHelper.java
diff --git a/src/main/java/io/reactivesocket/lease/FairLeaseGovernor.java b/reactivesocket-core/src/main/java/io/reactivesocket/lease/FairLeaseGovernor.java
similarity index 100%
rename from src/main/java/io/reactivesocket/lease/FairLeaseGovernor.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/lease/FairLeaseGovernor.java
diff --git a/src/main/java/io/reactivesocket/lease/NullLeaseGovernor.java b/reactivesocket-core/src/main/java/io/reactivesocket/lease/NullLeaseGovernor.java
similarity index 100%
rename from src/main/java/io/reactivesocket/lease/NullLeaseGovernor.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/lease/NullLeaseGovernor.java
diff --git a/src/main/java/io/reactivesocket/lease/UnlimitedLeaseGovernor.java b/reactivesocket-core/src/main/java/io/reactivesocket/lease/UnlimitedLeaseGovernor.java
similarity index 100%
rename from src/main/java/io/reactivesocket/lease/UnlimitedLeaseGovernor.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/lease/UnlimitedLeaseGovernor.java
diff --git a/src/main/java/io/reactivesocket/rx/Completable.java b/reactivesocket-core/src/main/java/io/reactivesocket/rx/Completable.java
similarity index 100%
rename from src/main/java/io/reactivesocket/rx/Completable.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/rx/Completable.java
diff --git a/src/main/java/io/reactivesocket/rx/Disposable.java b/reactivesocket-core/src/main/java/io/reactivesocket/rx/Disposable.java
similarity index 100%
rename from src/main/java/io/reactivesocket/rx/Disposable.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/rx/Disposable.java
diff --git a/src/main/java/io/reactivesocket/rx/Observable.java b/reactivesocket-core/src/main/java/io/reactivesocket/rx/Observable.java
similarity index 100%
rename from src/main/java/io/reactivesocket/rx/Observable.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/rx/Observable.java
diff --git a/src/main/java/io/reactivesocket/rx/Observer.java b/reactivesocket-core/src/main/java/io/reactivesocket/rx/Observer.java
similarity index 100%
rename from src/main/java/io/reactivesocket/rx/Observer.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/rx/Observer.java
diff --git a/src/main/java/io/reactivesocket/rx/README.md b/reactivesocket-core/src/main/java/io/reactivesocket/rx/README.md
similarity index 100%
rename from src/main/java/io/reactivesocket/rx/README.md
rename to reactivesocket-core/src/main/java/io/reactivesocket/rx/README.md
diff --git a/src/main/java/io/reactivesocket/util/ReactiveSocketProxy.java b/reactivesocket-core/src/main/java/io/reactivesocket/util/ReactiveSocketProxy.java
similarity index 100%
rename from src/main/java/io/reactivesocket/util/ReactiveSocketProxy.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/util/ReactiveSocketProxy.java
diff --git a/src/main/java/io/reactivesocket/util/Unsafe.java b/reactivesocket-core/src/main/java/io/reactivesocket/util/Unsafe.java
similarity index 100%
rename from src/main/java/io/reactivesocket/util/Unsafe.java
rename to reactivesocket-core/src/main/java/io/reactivesocket/util/Unsafe.java
diff --git a/src/perf/java/io/reactivesocket/FramePerf.java b/reactivesocket-core/src/perf/java/io/reactivesocket/FramePerf.java
similarity index 100%
rename from src/perf/java/io/reactivesocket/FramePerf.java
rename to reactivesocket-core/src/perf/java/io/reactivesocket/FramePerf.java
diff --git a/src/perf/java/io/reactivesocket/README.md b/reactivesocket-core/src/perf/java/io/reactivesocket/README.md
similarity index 100%
rename from src/perf/java/io/reactivesocket/README.md
rename to reactivesocket-core/src/perf/java/io/reactivesocket/README.md
diff --git a/src/perf/java/io/reactivesocket/ReactiveSocketPerf.java b/reactivesocket-core/src/perf/java/io/reactivesocket/ReactiveSocketPerf.java
similarity index 100%
rename from src/perf/java/io/reactivesocket/ReactiveSocketPerf.java
rename to reactivesocket-core/src/perf/java/io/reactivesocket/ReactiveSocketPerf.java
diff --git a/src/perf/java/io/reactivesocket/perfutil/PerfTestConnection.java b/reactivesocket-core/src/perf/java/io/reactivesocket/perfutil/PerfTestConnection.java
similarity index 100%
rename from src/perf/java/io/reactivesocket/perfutil/PerfTestConnection.java
rename to reactivesocket-core/src/perf/java/io/reactivesocket/perfutil/PerfTestConnection.java
diff --git a/src/perf/java/io/reactivesocket/perfutil/PerfUnicastSubjectNoBackpressure.java b/reactivesocket-core/src/perf/java/io/reactivesocket/perfutil/PerfUnicastSubjectNoBackpressure.java
similarity index 100%
rename from src/perf/java/io/reactivesocket/perfutil/PerfUnicastSubjectNoBackpressure.java
rename to reactivesocket-core/src/perf/java/io/reactivesocket/perfutil/PerfUnicastSubjectNoBackpressure.java
diff --git a/src/test/java/io/reactivesocket/FrameTest.java b/reactivesocket-core/src/test/java/io/reactivesocket/FrameTest.java
similarity index 100%
rename from src/test/java/io/reactivesocket/FrameTest.java
rename to reactivesocket-core/src/test/java/io/reactivesocket/FrameTest.java
diff --git a/src/test/java/io/reactivesocket/LatchedCompletable.java b/reactivesocket-core/src/test/java/io/reactivesocket/LatchedCompletable.java
similarity index 100%
rename from src/test/java/io/reactivesocket/LatchedCompletable.java
rename to reactivesocket-core/src/test/java/io/reactivesocket/LatchedCompletable.java
diff --git a/src/test/java/io/reactivesocket/LeaseTest.java b/reactivesocket-core/src/test/java/io/reactivesocket/LeaseTest.java
similarity index 100%
rename from src/test/java/io/reactivesocket/LeaseTest.java
rename to reactivesocket-core/src/test/java/io/reactivesocket/LeaseTest.java
diff --git a/src/test/java/io/reactivesocket/ReactiveSocketTest.java b/reactivesocket-core/src/test/java/io/reactivesocket/ReactiveSocketTest.java
similarity index 100%
rename from src/test/java/io/reactivesocket/ReactiveSocketTest.java
rename to reactivesocket-core/src/test/java/io/reactivesocket/ReactiveSocketTest.java
diff --git a/src/test/java/io/reactivesocket/SerializedEventBus.java b/reactivesocket-core/src/test/java/io/reactivesocket/SerializedEventBus.java
similarity index 100%
rename from src/test/java/io/reactivesocket/SerializedEventBus.java
rename to reactivesocket-core/src/test/java/io/reactivesocket/SerializedEventBus.java
diff --git a/src/test/java/io/reactivesocket/TestConnection.java b/reactivesocket-core/src/test/java/io/reactivesocket/TestConnection.java
similarity index 100%
rename from src/test/java/io/reactivesocket/TestConnection.java
rename to reactivesocket-core/src/test/java/io/reactivesocket/TestConnection.java
diff --git a/src/test/java/io/reactivesocket/TestConnectionWithControlledRequestN.java b/reactivesocket-core/src/test/java/io/reactivesocket/TestConnectionWithControlledRequestN.java
similarity index 100%
rename from src/test/java/io/reactivesocket/TestConnectionWithControlledRequestN.java
rename to reactivesocket-core/src/test/java/io/reactivesocket/TestConnectionWithControlledRequestN.java
diff --git a/src/test/java/io/reactivesocket/TestFlowControlRequestN.java b/reactivesocket-core/src/test/java/io/reactivesocket/TestFlowControlRequestN.java
similarity index 100%
rename from src/test/java/io/reactivesocket/TestFlowControlRequestN.java
rename to reactivesocket-core/src/test/java/io/reactivesocket/TestFlowControlRequestN.java
diff --git a/src/test/java/io/reactivesocket/TestTransportRequestN.java b/reactivesocket-core/src/test/java/io/reactivesocket/TestTransportRequestN.java
similarity index 100%
rename from src/test/java/io/reactivesocket/TestTransportRequestN.java
rename to reactivesocket-core/src/test/java/io/reactivesocket/TestTransportRequestN.java
diff --git a/src/test/java/io/reactivesocket/TestUtil.java b/reactivesocket-core/src/test/java/io/reactivesocket/TestUtil.java
similarity index 100%
rename from src/test/java/io/reactivesocket/TestUtil.java
rename to reactivesocket-core/src/test/java/io/reactivesocket/TestUtil.java
diff --git a/src/test/java/io/reactivesocket/internal/FragmenterTest.java b/reactivesocket-core/src/test/java/io/reactivesocket/internal/FragmenterTest.java
similarity index 100%
rename from src/test/java/io/reactivesocket/internal/FragmenterTest.java
rename to reactivesocket-core/src/test/java/io/reactivesocket/internal/FragmenterTest.java
diff --git a/src/test/java/io/reactivesocket/internal/ReassemblerTest.java b/reactivesocket-core/src/test/java/io/reactivesocket/internal/ReassemblerTest.java
similarity index 100%
rename from src/test/java/io/reactivesocket/internal/ReassemblerTest.java
rename to reactivesocket-core/src/test/java/io/reactivesocket/internal/ReassemblerTest.java
diff --git a/src/test/java/io/reactivesocket/internal/RequesterTest.java b/reactivesocket-core/src/test/java/io/reactivesocket/internal/RequesterTest.java
similarity index 100%
rename from src/test/java/io/reactivesocket/internal/RequesterTest.java
rename to reactivesocket-core/src/test/java/io/reactivesocket/internal/RequesterTest.java
diff --git a/src/test/java/io/reactivesocket/internal/ResponderTest.java b/reactivesocket-core/src/test/java/io/reactivesocket/internal/ResponderTest.java
similarity index 100%
rename from src/test/java/io/reactivesocket/internal/ResponderTest.java
rename to reactivesocket-core/src/test/java/io/reactivesocket/internal/ResponderTest.java
diff --git a/src/test/java/io/reactivesocket/internal/UnicastSubjectTest.java b/reactivesocket-core/src/test/java/io/reactivesocket/internal/UnicastSubjectTest.java
similarity index 100%
rename from src/test/java/io/reactivesocket/internal/UnicastSubjectTest.java
rename to reactivesocket-core/src/test/java/io/reactivesocket/internal/UnicastSubjectTest.java
diff --git a/reactivesocket-mime-types/README.md b/reactivesocket-mime-types/README.md
new file mode 100644
index 000000000..02c28524b
--- /dev/null
+++ b/reactivesocket-mime-types/README.md
@@ -0,0 +1,66 @@
+## Overview
+
+This module provides support for encoding/decoding ReactiveSocket data and metadata into using different mime types as defined by [ReactiveSocket protocol](https://github.com/ReactiveSocket/reactivesocket/blob/master/Protocol.md#setup-frame).
+The support for mime types is not comprehensive but it will at least support the [default metadata mime type](https://github.com/ReactiveSocket/reactivesocket/blob/mimetypes/MimeTypes.md)
+
+## Usage
+
+#### Supported Codecs
+
+Supported mime types are listed as [SupportedMimeTypes](src/main/java/io/reactivesocket/mimetypes/SupportedMimeTypes.java).
+
+#### Obtaining the appropriate codec
+
+[MimeType](src/main/java/io/reactivesocket/mimetypes/MimeType.java) is the interface that provides different methods for encoding/decoding ReactiveSocket data and metadata.
+An instance of `MimeType` can be obtained via [MimeTypeFactory](src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java).
+
+A simple usage of `MimeType` is as follows:
+
+```java
+public class ConnectionSetupHandlerImpl implements ConnectionSetupHandler {
+
+ @Override
+ public RequestHandler apply(ConnectionSetupPayload setupPayload, ReactiveSocket reactiveSocket) throws SetupException {
+
+ final MimeType mimeType = MimeTypeFactory.from(setupPayload); // If the mime types aren't supported, throws an error.
+
+ return new RequestHandler() {
+
+ // Not a complete implementation, just a method to demonstrate usage.
+ @Override
+ public Publisher handleRequestResponse(Payload payload) {
+ // use (en/de)codeMetadata() methods to encode/decode metadata
+ mimeType.decodeMetadata(payload.getMetadata(), KVMetadata.class);
+ // use (en/de)codeData() methods to encode/decode data
+ mimeType.decodeData(payload.getData(), Person.class);
+ return PublisherUtils.empty(); // Do something useful in reality!
+ }
+ };
+ }
+}
+```
+
+## Build and Binaries
+
+
+
+Artifacts are available via JCenter.
+
+Example:
+
+```groovy
+repositories {
+ maven { url 'https://jcenter.bintray.com' }
+}
+
+dependencies {
+ compile 'io.reactivesocket:reactivesocket-mime-types:x.y.z'
+}
+```
+
+No releases to Maven Central have occurred yet.
+
+
+## Bugs and Feedback
+
+For bugs, questions and discussions please use the [Github Issues](https://github.com/ReactiveSocket/reactivesocket-java-impl/issues).
diff --git a/reactivesocket-mime-types/build.gradle b/reactivesocket-mime-types/build.gradle
new file mode 100644
index 000000000..869c17c79
--- /dev/null
+++ b/reactivesocket-mime-types/build.gradle
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+dependencies {
+ compile project(':reactivesocket-core')
+ compile 'com.fasterxml.jackson.core:jackson-core:latest.release'
+ compile 'com.fasterxml.jackson.core:jackson-databind:latest.release'
+ compile 'com.fasterxml.jackson.module:jackson-module-afterburner:latest.release'
+ compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:latest.release'
+
+ testCompile "org.hamcrest:hamcrest-library:1.3"
+}
\ No newline at end of file
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/KVMetadata.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/KVMetadata.java
new file mode 100644
index 000000000..47f333ffd
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/KVMetadata.java
@@ -0,0 +1,37 @@
+package io.reactivesocket.mimetypes;
+
+import org.agrona.MutableDirectBuffer;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * A representation of ReactiveSocket metadata as a key-value pair.
+ *
+ * Implementations are not required to be thread-safe.
+ */
+public interface KVMetadata extends Map {
+
+ /**
+ * Lookup the value for the passed key and return the value as a string.
+ *
+ * @param key To Lookup.
+ * @param valueEncoding Encoding for the value.
+ *
+ * @return Value as a string with the passed {@code valueEncoding}
+ * @throws NullPointerException If the key does not exist.
+ */
+ String getAsString(String key, Charset valueEncoding);
+
+ /**
+ * Creates a new copy of this metadata.
+ *
+ * @param newBufferFactory A factory to create new buffer instances to copy, if required. The argument to the
+ * function is the capacity of the new buffer.
+ *
+ * @return New copy of this metadata.
+ */
+ KVMetadata duplicate(Function newBufferFactory);
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeType.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeType.java
new file mode 100644
index 000000000..68caac399
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeType.java
@@ -0,0 +1,177 @@
+package io.reactivesocket.mimetypes;
+
+import io.reactivesocket.Frame;
+import org.agrona.DirectBuffer;
+import org.agrona.MutableDirectBuffer;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Encoding and decoding operations for a ReactiveSocket. Since, mime-types for data and metadata do not change once
+ * setup, a MimeType instance can be stored per ReactiveSocket instance and can be used for repeated encode/decode of
+ * data and metadata.
+ */
+public interface MimeType {
+
+ /**
+ * Decodes metadata of the passed frame to the specified {@code clazz}.
+ *
+ * @param toDecode Frame for which metadata is to be decoded.
+ * @param clazz Class to which metadata will be decoded.
+ *
+ * @param Type of the class to which metadata will be decoded.
+ *
+ * @return Instance of the class post decode.
+ */
+ default T decodeMetadata(Frame toDecode, Class clazz) {
+ return decodeMetadata(toDecode.getMetadata(), clazz);
+ }
+
+ /**
+ * Decodes the passed buffer to the specified {@code clazz}.
+ *
+ * @param toDecode buffer to be decoded.
+ * @param clazz Class to which the buffer will be decoded.
+ *
+ * @param Type of the class to which the buffer will be decoded.
+ *
+ * @return Instance of the class post decode.
+ */
+ T decodeMetadata(ByteBuffer toDecode, Class clazz);
+
+ /**
+ * Decodes the passed buffer to the specified {@code clazz}.
+ *
+ * @param Type of the class to which the buffer will be decoded.
+ *
+ * @param toDecode buffer to be decoded.
+ * @param clazz Class to which the buffer will be decoded.
+ * @param offset Offset in the buffer.
+ *
+ * @return Instance of the class post decode.
+ */
+ T decodeMetadata(DirectBuffer toDecode, Class clazz, int offset);
+
+ /**
+ * Encodes passed metadata to a buffer.
+ *
+ * @param toEncode Object to encode as metadata.
+ *
+ * @param Type of the object to encode.
+ *
+ * @return Buffer with encoded data.
+ */
+ ByteBuffer encodeMetadata(T toEncode);
+
+ /**
+ * Encodes passed metadata to a buffer.
+ *
+ * @param toEncode Object to encode as metadata.
+ *
+ * @param Type of the object to encode.
+ *
+ * @return Buffer with encoded data.
+ */
+ DirectBuffer encodeMetadataDirect(T toEncode);
+
+ /**
+ * Encodes passed metadata to the passed buffer.
+ *
+ * @param Type of the object to encode.
+ * @param buffer Encodes the metadata to this buffer.
+ * @param toEncode Metadata to encode.
+ * @param offset Offset in the buffer to start writing.
+ */
+ void encodeMetadataTo(MutableDirectBuffer buffer, T toEncode, int offset);
+
+ /**
+ * Encodes passed metadata to the passed buffer.
+ *
+ * @param buffer Encodes the metadata to this buffer.
+ * @param toEncode Metadata to encode.
+ *
+ * @param Type of the object to encode.
+ */
+ void encodeMetadataTo(ByteBuffer buffer, T toEncode);
+
+ /**
+ * Decodes data of the passed frame to the specified {@code clazz}.
+ *
+ * @param toDecode Frame for which metadata is to be decoded.
+ * @param clazz Class to which metadata will be decoded.
+ *
+ * @param Type of the class to which metadata will be decoded.
+ *
+ * @return Instance of the class post decode.
+ */
+ default T decodeData(Frame toDecode, Class clazz) {
+ return decodeData(toDecode.getData(), clazz);
+ }
+
+ /**
+ * Decodes the passed buffer to the specified {@code clazz}.
+ *
+ * @param toDecode buffer to be decoded.
+ * @param clazz Class to which the buffer will be decoded.
+ *
+ * @param Type of the class to which the buffer will be decoded.
+ *
+ * @return Instance of the class post decode.
+ */
+ T decodeData(ByteBuffer toDecode, Class clazz);
+
+ /**
+ * Decodes the passed buffer to the specified {@code clazz}.
+ *
+ * @param Type of the class to which the buffer will be decoded.
+ *
+ * @param toDecode buffer to be decoded.
+ * @param clazz Class to which the buffer will be decoded.
+ * @param offset Offset in the buffer.
+ *
+ * @return Instance of the class post decode.
+ */
+ T decodeData(DirectBuffer toDecode, Class clazz, int offset);
+
+ /**
+ * Encodes passed data to a buffer.
+ *
+ * @param toEncode Object to encode as data.
+ *
+ * @param Type of the object to encode.
+ *
+ * @return Buffer with encoded data.
+ */
+ ByteBuffer encodeData(T toEncode);
+
+ /**
+ * Encodes passed data to a buffer.
+ *
+ * @param toEncode Object to encode as data.
+ *
+ * @param Type of the object to encode.
+ *
+ * @return Buffer with encoded data.
+ */
+ DirectBuffer encodeDataDirect(T toEncode);
+
+ /**
+ * Encodes passed data to the passed buffer.
+ *
+ * @param Type of the object to encode.
+ * @param buffer Encodes the data to this buffer.
+ * @param toEncode Data to encode.
+ * @param offset Offset in the buffer to start writing.
+ */
+ void encodeDataTo(MutableDirectBuffer buffer, T toEncode, int offset);
+
+ /**
+ * Encodes passed data to the passed buffer.
+ *
+ * @param buffer Encodes the data to this buffer.
+ * @param toEncode Data to encode.
+ *
+ * @param Type of the object to encode.
+ */
+ void encodeDataTo(ByteBuffer buffer, T toEncode);
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java
new file mode 100644
index 000000000..6842d7abc
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java
@@ -0,0 +1,179 @@
+package io.reactivesocket.mimetypes;
+
+import io.reactivesocket.ConnectionSetupPayload;
+import io.reactivesocket.mimetypes.internal.Codec;
+import io.reactivesocket.mimetypes.internal.cbor.CborCodec;
+import io.reactivesocket.mimetypes.internal.cbor.ReactiveSocketDefaultMetadataCodec;
+import io.reactivesocket.mimetypes.internal.json.JsonCodec;
+import org.agrona.DirectBuffer;
+import org.agrona.MutableDirectBuffer;
+
+import java.nio.ByteBuffer;
+import java.util.EnumMap;
+
+import static io.reactivesocket.mimetypes.SupportedMimeTypes.*;
+
+/**
+ * A factory to retrieve {@link MimeType} instances for {@link SupportedMimeTypes}. The retrieved mime type instances
+ * are thread-safe.
+ */
+public final class MimeTypeFactory {
+
+ private static final EnumMap codecs;
+ private static final EnumMap> mimeTypes;
+
+ static {
+ codecs = new EnumMap(SupportedMimeTypes.class);
+ codecs.put(CBOR, CborCodec.create());
+ codecs.put(JSON, JsonCodec.create());
+ codecs.put(ReactiveSocketDefaultMetadata, ReactiveSocketDefaultMetadataCodec.create());
+
+ mimeTypes = new EnumMap<>(SupportedMimeTypes.class);
+ mimeTypes.put(CBOR, getEnumMapForMetadataCodec(CBOR));
+ mimeTypes.put(JSON, getEnumMapForMetadataCodec(JSON));
+ mimeTypes.put(ReactiveSocketDefaultMetadata, getEnumMapForMetadataCodec(ReactiveSocketDefaultMetadata));
+ }
+
+ private MimeTypeFactory() {
+ }
+
+ /**
+ * Provides an appropriate {@link MimeType} for the passed {@link ConnectionSetupPayload}.
+ * Only the mime types represented by {@link SupportedMimeTypes} are supported by this factory. For any other mime
+ * type, this method will throw an exception.
+ * It is safer to first retrieve the mime types and then use {@link #from(SupportedMimeTypes, SupportedMimeTypes)}
+ * method.
+ *
+ * @param setup Setup for which the mime type is to be fetched.
+ *
+ * @return Appropriate {@link MimeType} for the passed {@code setup}.
+ *
+ * @throws IllegalArgumentException If the mime type for either data or metadata is not supported by this factory.
+ */
+ public static MimeType from(ConnectionSetupPayload setup) {
+ SupportedMimeTypes metaMimeType = parseOrDie(setup.metadataMimeType());
+ SupportedMimeTypes dataMimeType = parseOrDie(setup.dataMimeType());
+
+ return from(metaMimeType, dataMimeType);
+ }
+
+ /**
+ * Same as calling {@code from(mimeType, mimeType}.
+ *
+ * @param mimeType Mime type to be used for both data and metadata.
+ *
+ * @return MimeType for the passed {@code mimeType}
+ */
+ public static MimeType from(SupportedMimeTypes mimeType) {
+ return from(mimeType, mimeType);
+ }
+
+ /**
+ * Provides an appropriate {@link MimeType} for the passed date and metadata mime types.
+ *
+ * @param metadataMimeType Mime type for metadata.
+ * @param dataMimeType Mime type for data.
+ *
+ * @return Appropriate {@link MimeType} to use.
+ */
+ public static MimeType from(SupportedMimeTypes metadataMimeType, SupportedMimeTypes dataMimeType) {
+ if (null == metadataMimeType) {
+ throw new IllegalArgumentException("Metadata mime type can not be null.");
+ }
+ if (null == dataMimeType) {
+ throw new IllegalArgumentException("Data mime type can not be null.");
+ }
+
+ return mimeTypes.get(metadataMimeType).get(dataMimeType);
+ }
+
+ private static EnumMap getEnumMapForMetadataCodec(SupportedMimeTypes metaMime) {
+
+ final Codec metaMimeCodec = codecs.get(metaMime);
+
+ EnumMap toReturn =
+ new EnumMap(SupportedMimeTypes.class);
+
+ toReturn.put(CBOR, new MimeTypeImpl(metaMimeCodec, codecs.get(CBOR)));
+ toReturn.put(JSON, new MimeTypeImpl(metaMimeCodec, codecs.get(JSON)));
+ toReturn.put(ReactiveSocketDefaultMetadata,
+ new MimeTypeImpl(metaMimeCodec, codecs.get(ReactiveSocketDefaultMetadata)));
+
+ return toReturn;
+ }
+
+ /*Visible for testing*/ Codec getCodec(SupportedMimeTypes mimeType) {
+ return codecs.get(mimeType);
+ }
+
+ private static class MimeTypeImpl implements MimeType {
+
+ private final Codec metaCodec;
+ private final Codec dataCodec;
+
+ public MimeTypeImpl(Codec metaCodec, Codec dataCodec) {
+ this.metaCodec = metaCodec;
+ this.dataCodec = dataCodec;
+ }
+
+ @Override
+ public T decodeMetadata(ByteBuffer toDecode, Class clazz) {
+ return metaCodec.decode(toDecode, clazz);
+ }
+
+ @Override
+ public T decodeMetadata(DirectBuffer toDecode, Class clazz, int offset) {
+ return metaCodec.decode(toDecode, offset, clazz);
+ }
+
+ @Override
+ public ByteBuffer encodeMetadata(T toEncode) {
+ return metaCodec.encode(toEncode);
+ }
+
+ @Override
+ public DirectBuffer encodeMetadataDirect(T toEncode) {
+ return metaCodec.encodeDirect(toEncode);
+ }
+
+ @Override
+ public void encodeMetadataTo(MutableDirectBuffer buffer, T toEncode, int offset) {
+ metaCodec.encodeTo(buffer, toEncode, offset);
+ }
+
+ @Override
+ public void encodeMetadataTo(ByteBuffer buffer, T toEncode) {
+ metaCodec.encodeTo(buffer, toEncode);
+ }
+
+ @Override
+ public T decodeData(ByteBuffer toDecode, Class clazz) {
+ return dataCodec.decode(toDecode, clazz);
+ }
+
+ @Override
+ public T decodeData(DirectBuffer toDecode, Class clazz, int offset) {
+ return dataCodec.decode(toDecode, offset, clazz);
+ }
+
+ @Override
+ public ByteBuffer encodeData(T toEncode) {
+ return dataCodec.encode(toEncode);
+ }
+
+ @Override
+ public DirectBuffer encodeDataDirect(T toEncode) {
+ return dataCodec.encodeDirect(toEncode);
+ }
+
+ @Override
+ public void encodeDataTo(MutableDirectBuffer buffer, T toEncode, int offset) {
+ dataCodec.encodeTo(buffer, toEncode, offset);
+ }
+
+ @Override
+ public void encodeDataTo(ByteBuffer buffer, T toEncode) {
+ dataCodec.encodeTo(buffer, toEncode);
+ }
+ }
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/SupportedMimeTypes.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/SupportedMimeTypes.java
new file mode 100644
index 000000000..c1d83cb1b
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/SupportedMimeTypes.java
@@ -0,0 +1,59 @@
+package io.reactivesocket.mimetypes;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public enum SupportedMimeTypes {
+
+ /*CBOR encoding*/
+ CBOR ("application/cbor"),
+ /*JSON encoding*/
+ JSON ("application/json"),
+ /*Default ReactiveSocket metadata encoding as specified in
+ https://github.com/ReactiveSocket/reactivesocket/blob/mimetypes/MimeTypes.md*/
+ ReactiveSocketDefaultMetadata ("application/x.reactivesocket.meta+cbor");
+
+ private final List mimeTypes;
+
+ SupportedMimeTypes(String... mimeTypes) {
+ this.mimeTypes = Collections.unmodifiableList(Arrays.asList(mimeTypes));
+ }
+
+ /**
+ * Parses the passed string to this enum.
+ *
+ * @param mimeType Mimetype to parse.
+ *
+ * @return This enum if the mime type is supported, else {@code null}
+ */
+ public static SupportedMimeTypes parse(String mimeType) {
+ for (SupportedMimeTypes aMimeType : SupportedMimeTypes.values()) {
+ if (aMimeType.mimeTypes.contains(mimeType)) {
+ return aMimeType;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Same as {@link #parse(String)} but throws an exception if the passed mime type is not supported.
+ *
+ * @param mimeType Mime-type to parse.
+ *
+ * @return This enum instance.
+ *
+ * @throws IllegalArgumentException If the mime-type is not supported.
+ */
+ public static SupportedMimeTypes parseOrDie(String mimeType) {
+ SupportedMimeTypes parsed = parse(mimeType);
+ if (null == parsed) {
+ throw new IllegalArgumentException("Unsupported mime-type: " + mimeType);
+ }
+ return parsed;
+ }
+
+ public List getMimeTypes() {
+ return mimeTypes;
+ }
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodec.java
new file mode 100644
index 000000000..ef3119d5c
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodec.java
@@ -0,0 +1,129 @@
+package io.reactivesocket.mimetypes.internal;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonGenerator.Feature;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
+import org.agrona.DirectBuffer;
+import org.agrona.LangUtil;
+import org.agrona.MutableDirectBuffer;
+import org.agrona.concurrent.UnsafeBuffer;
+import org.agrona.io.DirectBufferInputStream;
+import org.agrona.io.MutableDirectBufferOutputStream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+public abstract class AbstractJacksonCodec implements Codec {
+
+ private static final ThreadLocal directInWrappers =
+ ThreadLocal.withInitial(DirectBufferInputStream::new);
+
+ private static final ThreadLocal directOutWrappers =
+ ThreadLocal.withInitial(MutableDirectBufferOutputStream::new);
+
+ private static final ThreadLocal bbInWrappers =
+ ThreadLocal.withInitial(ByteBufferInputStream::new);
+
+ private static final ThreadLocal bbOutWrappers =
+ ThreadLocal.withInitial(ByteBufferOutputStream::new);
+
+ private static final byte[] emptyByteArray = new byte[0];
+ private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
+ private static final DirectBuffer EMPTY_DIRECT_BUFFER = new UnsafeBuffer(emptyByteArray);
+
+ private final ObjectMapper mapper;
+
+ protected AbstractJacksonCodec(ObjectMapper mapper) {
+ this.mapper = mapper;
+ }
+
+ protected static void configureDefaults(ObjectMapper mapper) {
+ mapper.registerModule(new AfterburnerModule());
+ mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
+ mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
+ mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
+ mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ mapper.enable(Feature.AUTO_CLOSE_TARGET); // encodeTo methods do not close the OutputStream.
+ SimpleModule module = new SimpleModule(Version.unknownVersion());
+ mapper.registerModule(module);
+ }
+
+ @Override
+ public T decode(ByteBuffer buffer, Class tClass) {
+ return _decode(bbInWrappers.get().wrap(buffer), tClass);
+ }
+
+ @Override
+ public T decode(DirectBuffer buffer, int offset, Class tClass) {
+ DirectBufferInputStream stream = directInWrappers.get();
+ stream.wrap(buffer, offset, buffer.capacity());
+ return _decode(stream, tClass);
+ }
+
+ @Override
+ public ByteBuffer encode(T toEncode) {
+ byte[] bytes = _encode(toEncode);
+ return bytes == emptyByteArray ? EMPTY_BUFFER : ByteBuffer.wrap(bytes);
+ }
+
+ @Override
+ public DirectBuffer encodeDirect(T toEncode) {
+ byte[] bytes = _encode(toEncode);
+ return bytes == emptyByteArray ? EMPTY_DIRECT_BUFFER : new UnsafeBuffer(bytes);
+ }
+
+ @Override
+ public void encodeTo(ByteBuffer buffer, T toEncode) {
+ _encodeTo(bbOutWrappers.get().wrap(buffer), toEncode);
+ }
+
+ @Override
+ public void encodeTo(MutableDirectBuffer buffer, T toEncode, int offset) {
+ MutableDirectBufferOutputStream stream = directOutWrappers.get();
+ stream.wrap(buffer, offset, buffer.capacity());
+ _encodeTo(stream, toEncode);
+ }
+
+ private T _decode(InputStream stream, Class clazz) {
+ T v = null;
+ try {
+ v = mapper.readValue(stream, clazz);
+ } catch (IOException e) {
+ LangUtil.rethrowUnchecked(e);
+ }
+
+ return v;
+ }
+
+ private byte[] _encode(Object toEncode) {
+ byte[] encode = emptyByteArray;
+
+ try {
+ encode = mapper.writeValueAsBytes(toEncode);
+ } catch (JsonProcessingException e) {
+ LangUtil.rethrowUnchecked(e);
+ }
+
+ return encode;
+ }
+
+ private void _encodeTo(OutputStream stream, Object toEncode) {
+ try {
+ mapper.writeValue(stream, toEncode);
+ } catch (JsonProcessingException e) {
+ LangUtil.rethrowUnchecked(e);
+ } catch (IOException e) {
+ LangUtil.rethrowUnchecked(e);
+ }
+ }
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferInputStream.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferInputStream.java
new file mode 100644
index 000000000..9997317f9
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferInputStream.java
@@ -0,0 +1,52 @@
+package io.reactivesocket.mimetypes.internal;
+
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * This code is copied from {@link com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream} with the
+ * modifications to make the stream mutable per thread.
+ */
+final class ByteBufferInputStream extends InputStream {
+
+ private ByteBuffer _b;
+
+ public ByteBufferInputStream() {
+ }
+
+ public ByteBufferInputStream(ByteBuffer _b) {
+ this._b = _b;
+ }
+
+ /**
+ * Returns a {@link ThreadLocal} instance of the {@link InputStream} which wraps the passed buffer.
+ *
+ * This instance must not leak from the calling thread.
+ *
+ * @param buffer Buffer to wrap.
+ */
+ public ByteBufferInputStream wrap(ByteBuffer buffer) {
+ _b = buffer;
+ return this;
+ }
+
+ @Override
+ public int available() {
+ return _b.remaining();
+ }
+
+ @Override
+ public int read() {
+ return _b.hasRemaining() ? _b.get() & 0xFF : -1;
+ }
+
+ @Override
+ public int read(byte[] bytes, int off, int len) {
+ if (!_b.hasRemaining()) {
+ return -1;
+ }
+ len = Math.min(len, _b.remaining());
+ _b.get(bytes, off, len);
+ return len;
+ }
+}
\ No newline at end of file
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferOutputStream.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferOutputStream.java
new file mode 100644
index 000000000..975803c53
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferOutputStream.java
@@ -0,0 +1,38 @@
+package io.reactivesocket.mimetypes.internal;
+
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * An {@link OutputStream} backed by a {@link ByteBuffer} which must only be used within the thread that retrieved it
+ * via {@link #wrap(ByteBuffer)} method.
+ *
+ * This code is copied from {@link com.fasterxml.jackson.databind.util.ByteBufferBackedOutputStream} with the
+ * modifications to make the stream mutable per thread.
+ */
+final class ByteBufferOutputStream extends OutputStream {
+
+ private ByteBuffer _b;
+
+ public ByteBufferOutputStream() {
+ }
+
+ public ByteBufferOutputStream(ByteBuffer _b) {
+ this._b = _b;
+ }
+
+ public ByteBufferOutputStream wrap(ByteBuffer buffer) {
+ _b = buffer;
+ return this;
+ }
+
+ @Override
+ public void write(int b) {
+ _b.put((byte) b);
+ }
+
+ @Override
+ public void write(byte[] bytes, int off, int len) {
+ _b.put(bytes, off, len);
+ }
+}
\ No newline at end of file
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/Codec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/Codec.java
new file mode 100644
index 000000000..d04acfe6e
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/Codec.java
@@ -0,0 +1,21 @@
+package io.reactivesocket.mimetypes.internal;
+
+import org.agrona.DirectBuffer;
+import org.agrona.MutableDirectBuffer;
+
+import java.nio.ByteBuffer;
+
+public interface Codec {
+
+ T decode(ByteBuffer buffer, Class tClass);
+
+ T decode(DirectBuffer buffer, int offset, Class tClass);
+
+ ByteBuffer encode(T toEncode);
+
+ DirectBuffer encodeDirect(T toEncode);
+
+ void encodeTo(ByteBuffer buffer, T toEncode);
+
+ void encodeTo(MutableDirectBuffer buffer, T toEncode, int offset);
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/KVMetadataImpl.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/KVMetadataImpl.java
new file mode 100644
index 000000000..e8bf2f937
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/KVMetadataImpl.java
@@ -0,0 +1,143 @@
+package io.reactivesocket.mimetypes.internal;
+
+import io.reactivesocket.mimetypes.KVMetadata;
+import org.agrona.MutableDirectBuffer;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+public class KVMetadataImpl implements KVMetadata {
+
+ private Map store;
+
+ public KVMetadataImpl(Map store) {
+ this.store = store;
+ }
+
+ public KVMetadataImpl() {
+ store = new HashMap<>();
+ }
+
+ public void setStore(Map store) {
+ if (null == store) {
+ throw new IllegalArgumentException("Store can not be null");
+ }
+ this.store = store;
+ }
+
+ public Map getStore() {
+ return store;
+ }
+
+ @Override
+ public String getAsString(String key, Charset valueEncoding) {
+ ByteBuffer toReturn = get(key);
+
+ if (null != toReturn) {
+ byte[] dst = new byte[toReturn.remaining()];
+ toReturn.get(dst);
+ return new String(dst, valueEncoding);
+ }
+
+ return null;
+ }
+
+ @Override
+ public KVMetadata duplicate(Function newBufferFactory) {
+ Map copy = new HashMap<>(store);
+ return new KVMetadataImpl(copy);
+ }
+
+ @Override
+ public int size() {
+ return store.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return store.isEmpty();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return store.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return store.containsValue(value);
+ }
+
+ @Override
+ public ByteBuffer get(Object key) {
+ return store.get(key);
+ }
+
+ @Override
+ public ByteBuffer put(String key, ByteBuffer value) {
+ return store.put(key, value);
+ }
+
+ @Override
+ public ByteBuffer remove(Object key) {
+ return store.remove(key);
+ }
+
+ @Override
+ public void putAll(Map extends String, ? extends ByteBuffer> m) {
+ store.putAll(m);
+ }
+
+ @Override
+ public void clear() {
+ store.clear();
+ }
+
+ @Override
+ public Set keySet() {
+ return store.keySet();
+ }
+
+ @Override
+ public Collection values() {
+ return store.values();
+ }
+
+ @Override
+ public Set> entrySet() {
+ return store.entrySet();
+ }
+
+ @Override
+ public String toString() {
+ return "KVMetadataImpl{" + "store=" + store + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof KVMetadataImpl)) {
+ return false;
+ }
+
+ KVMetadataImpl that = (KVMetadataImpl) o;
+
+ if (!store.equals(that.store)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return store.hashCode();
+ }
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/MalformedInputException.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/MalformedInputException.java
new file mode 100644
index 000000000..95d1d435a
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/MalformedInputException.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal;
+
+public class MalformedInputException extends RuntimeException {
+
+ private static final long serialVersionUID = 3130502874275862715L;
+
+ public MalformedInputException(String message) {
+ super(message);
+ }
+
+ public MalformedInputException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public MalformedInputException(Throwable cause) {
+ super(cause);
+ }
+
+ public MalformedInputException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORMap.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORMap.java
new file mode 100644
index 000000000..7a7f43ac5
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORMap.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal.cbor;
+
+import io.reactivesocket.internal.frame.ByteBufferUtil;
+import org.agrona.DirectBuffer;
+import org.agrona.concurrent.UnsafeBuffer;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static io.reactivesocket.mimetypes.internal.cbor.CborMajorType.*;
+
+/**
+ * A representation of CBOR map as defined in the spec.
+ *
+ * The benefit of this class is that it does not create additional buffers for the values of the metadata, when
+ * possible. Instead it holds views into the original buffer and when queried creates a slice of the underlying buffer
+ * that represents the value. Thus, it is lean in terms of memory usage as compared to other standard libraries that
+ * allocate memory for all values.
+ *
+ *
Allocations
+ *
+ *
Modifications
+ *
+ * Any additions to the map (adding one or more key-value pairs) will create a map with all keys and values, where
+ * values are the buffer slices of the original buffer. From then onwards, the newly created map will be used for all
+ * further queries.
+ * So, additions to this map will result in more allocations than usual but it still does not allocate fresh memory
+ * for existing entries.
+ *
+ *
Access
+ *
+ * Bulk queries like {@link #entrySet()}, {@link #values()} and value queries like {@link #containsValue(Object)} will
+ * switch to a new map as described in case of modifications above.
+ *
+ *
Structure
+ *
+ * In absence of the above cases for allocations, this map uses an index of {@code String} keys to a {@code Long}. The
+ * first 32 bits of this {@code Long} holds the length of the value and the next 32 bits contain the offset in the
+ * original buffer. {@link #encodeValueMask(int, long)} encodes this mask and {@link #decodeLengthFromMask(long)},
+ * {@link #decodeOffsetFromMask(long)} decodes the mask.
+ */
+public class CBORMap implements Map {
+
+ protected final DirectBuffer backingBuffer;
+ protected final int offset;
+ protected final Map keysVsOffsets;
+ protected Map storeWhenModified;
+
+ public CBORMap(DirectBuffer backingBuffer, int offset) {
+ this(backingBuffer, offset, 16, 0.75f);
+ }
+
+ public CBORMap(DirectBuffer backingBuffer, int offset, int initialCapacity) {
+ this(backingBuffer, offset, initialCapacity, 0.75f);
+ }
+
+ public CBORMap(DirectBuffer backingBuffer, int offset, int initialCapacity, float loadFactor) {
+ this.backingBuffer = backingBuffer;
+ this.offset = offset;
+ keysVsOffsets = new HashMap<>(initialCapacity, loadFactor);
+ }
+
+ protected CBORMap(Map storeWhenModified) {
+ backingBuffer = new UnsafeBuffer(IndexedUnsafeBuffer.EMPTY_ARRAY);
+ offset = 0;
+ this.storeWhenModified = storeWhenModified;
+ keysVsOffsets = Collections.emptyMap();
+ }
+
+ protected CBORMap(DirectBuffer backingBuffer, int offset, Map keysVsOffsets) {
+ this.backingBuffer = backingBuffer;
+ this.offset = offset;
+ this.keysVsOffsets = keysVsOffsets;
+ storeWhenModified = null;
+ }
+
+ public Long putValueOffset(String key, int offset, int length) {
+ return keysVsOffsets.put(key, encodeValueMask(offset, length));
+ }
+
+ @Override
+ public int size() {
+ return null != storeWhenModified ? storeWhenModified.size() : keysVsOffsets.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return null != storeWhenModified ? storeWhenModified.isEmpty() : keysVsOffsets.isEmpty();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return null != storeWhenModified ? storeWhenModified.containsKey(key) : keysVsOffsets.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ switchToAlternateMap();
+ return storeWhenModified.containsValue(value);
+ }
+
+ @Override
+ public ByteBuffer get(Object key) {
+ return getByteBuffer((String) key);
+ }
+
+ @Override
+ public ByteBuffer put(String key, ByteBuffer value) {
+ if (null != storeWhenModified) {
+ return storeWhenModified.put(key, value);
+ }
+
+ switchToAlternateMap();
+ return storeWhenModified.put(key, value);
+ }
+
+ @Override
+ public ByteBuffer remove(Object key) {
+ if (null != storeWhenModified) {
+ return storeWhenModified.remove(key);
+ }
+
+ Long removed = keysVsOffsets.remove(key);
+ return getFromBackingBuffer(removed);
+ }
+
+ @Override
+ public void putAll(Map extends String, ? extends ByteBuffer> m) {
+ if (null != storeWhenModified) {
+ storeWhenModified.putAll(m);
+ } else {
+ switchToAlternateMap();
+ storeWhenModified.putAll(m);
+ }
+ }
+
+ @Override
+ public void clear() {
+ if (null != storeWhenModified) {
+ storeWhenModified.clear();
+ } else {
+ keysVsOffsets.clear();
+ }
+ }
+
+ @Override
+ public Set keySet() {
+ return null != storeWhenModified ? storeWhenModified.keySet() : keysVsOffsets.keySet();
+ }
+
+ @Override
+ public Collection values() {
+ switchToAlternateMap();
+ return storeWhenModified.values();
+ }
+
+ @Override
+ public Set> entrySet() {
+ switchToAlternateMap();
+ return storeWhenModified.entrySet();
+ }
+
+ public void encodeTo(IndexedUnsafeBuffer dst) {
+ if (null == storeWhenModified) {
+ final int size = keysVsOffsets.size();
+ CborHeader.forLengthToEncode(size).encode(dst, MAP, size);
+ for (Entry entry : keysVsOffsets.entrySet()) {
+ CborUtf8StringCodec.encode(dst, entry.getKey());
+
+ Long valueMask = entry.getValue();
+ int valueLength = decodeLengthFromMask(valueMask);
+ int valueOffset = decodeOffsetFromMask(valueMask);
+ CborBinaryStringCodec.encode(dst, backingBuffer, valueOffset, valueLength);
+ }
+ } else {
+ CborMapCodec.encode(dst, storeWhenModified);
+ }
+ }
+
+ DirectBuffer getBackingBuffer() {
+ return backingBuffer;
+ }
+
+ private ByteBuffer getByteBuffer(String key) {
+ if (null == storeWhenModified) {
+ Long valueMask = keysVsOffsets.get(key);
+ return null == valueMask ? null : getFromBackingBuffer(valueMask);
+ } else {
+ return storeWhenModified.get(key);
+ }
+ }
+
+ private void switchToAlternateMap() {
+ if (null != storeWhenModified) {
+ return;
+ }
+ storeWhenModified = new HashMap<>(keysVsOffsets.size());
+ for (Entry entry : keysVsOffsets.entrySet()) {
+ storeWhenModified.put(entry.getKey(), getFromBackingBuffer(entry.getValue()));
+ }
+ }
+
+ private ByteBuffer getFromBackingBuffer(Long valueMask) {
+ int offset = this.offset + decodeOffsetFromMask(valueMask);
+ int length = decodeLengthFromMask(valueMask);
+
+ ByteBuffer bb = backingBuffer.byteBuffer();
+ if (null == bb) {
+ bb = ByteBuffer.wrap(backingBuffer.byteArray());
+ }
+ return ByteBufferUtil.preservingSlice(bb, offset, offset + length);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof CBORMap)) {
+ return false;
+ }
+
+ CBORMap that = (CBORMap) o;
+
+ if (offset != that.offset) {
+ return false;
+ }
+ if (backingBuffer != null? !backingBuffer.equals(that.backingBuffer) : that.backingBuffer != null) {
+ return false;
+ }
+ if (keysVsOffsets != null? !keysVsOffsets.equals(that.keysVsOffsets) : that.keysVsOffsets != null) {
+ return false;
+ }
+ if (storeWhenModified != null? !storeWhenModified.equals(that.storeWhenModified) :
+ that.storeWhenModified != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = backingBuffer != null? backingBuffer.hashCode() : 0;
+ result = 31 * result + offset;
+ result = 31 * result + (keysVsOffsets != null? keysVsOffsets.hashCode() : 0);
+ result = 31 * result + (storeWhenModified != null? storeWhenModified.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ String sb = "CBORMap{" + "backingBuffer=" + backingBuffer +
+ ", offset=" + offset +
+ ", keysVsOffsets=" + keysVsOffsets +
+ ", storeWhenModified=" + (null == storeWhenModified ? "null" : storeWhenModified) +
+ '}';
+ return sb;
+ }
+
+ static long encodeValueMask(int offset, long length) {
+ return length << 32 | offset & 0xFFFFFFFFL;
+ }
+
+ static int decodeLengthFromMask(long valueMask) {
+ return (int) (valueMask >> 32);
+ }
+
+ static int decodeOffsetFromMask(long valueMask) {
+ return (int) valueMask;
+ }
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtils.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtils.java
new file mode 100644
index 000000000..a61b2f320
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtils.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal.cbor;
+
+import io.reactivesocket.mimetypes.internal.MalformedInputException;
+
+import java.util.function.Function;
+
+import static io.reactivesocket.mimetypes.internal.cbor.CborMajorType.*;
+
+public final class CBORUtils {
+
+ public static final StackTraceElement[] EMPTY_STACK = new StackTraceElement[0];
+ public static final Function BREAK_SCANNER = aByte -> aByte != CBOR_BREAK;
+
+ @SuppressWarnings("ThrowableInstanceNeverThrown")
+ private static final MalformedInputException TOO_LONG_LENGTH =
+ new MalformedInputException("Length of a field is longer than: " + Integer.MAX_VALUE + " bytes.");
+
+ static {
+ TOO_LONG_LENGTH.setStackTrace(EMPTY_STACK);
+ }
+
+ private CBORUtils() {
+ }
+
+ /**
+ * Parses the passed {@code buffer} and returns the length of the data following the index at the
+ * {@link IndexedUnsafeBuffer#getReaderIndex()}.
+ *
+ *
Special cases
+ *
+
If the next data is a "Break" then -1 is
+ returned.
+
+ *
+ * @param buffer Buffer which will be parsed to determine the length of the next data item.
+ * @param expectedType {@link CborMajorType} to expect.
+ * @param errorIfMismatchType Error to throw if the type is not as expected.
+ *
+ * @return Length of the following data item. {@code -1} if the type is a Break.
+ */
+ public static long parseDataLengthOrDie(IndexedUnsafeBuffer buffer, CborMajorType expectedType,
+ RuntimeException errorIfMismatchType) {
+ final byte header = (byte) buffer.readUnsignedByte();
+ final CborMajorType type = fromUnsignedByte(header);
+
+ if (type == Break) {
+ return -1;
+ }
+
+ if (type != expectedType) {
+ throw errorIfMismatchType;
+ }
+
+ return CborHeader.readDataLength(buffer, header);
+ }
+
+ /**
+ * Returns the length in bytes that the passed {@code bytesToEncode} will be when encoded as CBOR.
+ *
+ * @param bytesToEncode Length in bytes to encode.
+ *
+ * @return Length in bytes post encode.
+ */
+ public static long getEncodeLength(long bytesToEncode) {
+ CborHeader header = CborHeader.forLengthToEncode(bytesToEncode);
+ return bytesToEncode + header.getSizeInBytes();
+ }
+
+ /**
+ * Encodes the passed {@code type} with {@code length} as a CBOR data header. The encoding is written on to the
+ * passed {@code buffer}
+ *
+ * @param buffer Buffer to encode to.
+ * @param type Type to encode.
+ * @param length Length of data that will be encoded.
+ *
+ * @return Number of bytes written on to the buffer for this encoding.
+ */
+ public static int encodeTypeHeader(IndexedUnsafeBuffer buffer, CborMajorType type, long length) {
+ CborHeader header = CborHeader.forLengthToEncode(length);
+ header.encode(buffer, type, length);
+ return header.getSizeInBytes();
+ }
+
+ /**
+ * Encodes the passed {@code type} with indefinite length. The encoding is written on to the passed {@code buffer}
+ *
+ * @param buffer Buffer to encode to.
+ * @param type Type to encode.
+ *
+ * @return Number of bytes written on to the buffer for this encoding.
+ */
+ public static int encodeIndefiniteTypeHeader(IndexedUnsafeBuffer buffer, CborMajorType type) {
+ return encodeTypeHeader(buffer, type, -1);
+ }
+
+ /**
+ * Returns the length(in bytes) till the next CBOR break i.e. {@link CborMajorType#CBOR_BREAK}.
+ * This method does not move the {@code readerIndex} for the passed buffer.
+ * @param src Buffer to scan.
+ *
+ * @return Index of the next CBOR break in the source buffer. {@code -1} if break is not found.
+ */
+ public static int scanToBreak(IndexedUnsafeBuffer src) {
+ int i = src.forEachByte(BREAK_SCANNER);
+ return i == src.getBackingBuffer().capacity() ? -1 : i;
+ }
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodec.java
new file mode 100644
index 000000000..7a2bd8be2
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodec.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal.cbor;
+
+import io.reactivesocket.mimetypes.internal.MalformedInputException;
+import org.agrona.DirectBuffer;
+import org.agrona.MutableDirectBuffer;
+
+import java.nio.ByteBuffer;
+
+import static io.reactivesocket.mimetypes.internal.cbor.CBORUtils.*;
+import static io.reactivesocket.mimetypes.internal.cbor.CborMajorType.*;
+
+final class CborBinaryStringCodec {
+
+ @SuppressWarnings("ThrowableInstanceNeverThrown")
+ static final MalformedInputException NOT_BINARY_STRING =
+ new MalformedInputException("Data is not a definite length binary string.");
+
+ @SuppressWarnings("ThrowableInstanceNeverThrown")
+ static final MalformedInputException INDEFINITE_LENGTH_NOT_SUPPORTED =
+ new MalformedInputException("Indefinite length binary string parsing not supported.");
+
+ static {
+ NOT_BINARY_STRING.setStackTrace(EMPTY_STACK);
+ INDEFINITE_LENGTH_NOT_SUPPORTED.setStackTrace(EMPTY_STACK);
+ }
+
+ private CborBinaryStringCodec() {
+ }
+
+ public static void encode(IndexedUnsafeBuffer dst, DirectBuffer src, int offset, int length) {
+ encodeTypeHeader(dst, ByteString, length);
+ dst.writeBytes(src, offset, length);
+ }
+
+ public static void encode(IndexedUnsafeBuffer dst, ByteBuffer src) {
+ encodeTypeHeader(dst, ByteString, src.remaining());
+ dst.writeBytes(src, src.remaining());
+ }
+
+ public static void decode(IndexedUnsafeBuffer src, IndexedUnsafeBuffer dst) {
+ int length = decode(src, dst.getBackingBuffer(), 0);
+ dst.incrementWriterIndex(length);
+ }
+
+ public static int decode(IndexedUnsafeBuffer src, MutableDirectBuffer dst, int offset) {
+ int length = (int) parseDataLengthOrDie(src, ByteString, NOT_BINARY_STRING);
+ if (length < 0) {
+ throw NOT_BINARY_STRING;
+ }
+
+ if (length == CborHeader.INDEFINITE.getCode()) {
+ while (true) {
+ byte aByte = src.getBackingBuffer().getByte(src.getReaderIndex());
+ if (aByte == CBOR_BREAK) {
+ break;
+ }
+
+ int chunkLength = (int) parseDataLengthOrDie(src, ByteString, NOT_BINARY_STRING);
+ src.readBytes(dst, offset, chunkLength);
+ offset += chunkLength;
+ }
+ } else {
+ src.readBytes(dst, offset, length);
+ }
+
+ return length;
+ }
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborCodec.java
new file mode 100644
index 000000000..9dd7dac38
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborCodec.java
@@ -0,0 +1,33 @@
+package io.reactivesocket.mimetypes.internal.cbor;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
+import io.reactivesocket.mimetypes.internal.AbstractJacksonCodec;
+
+public class CborCodec extends AbstractJacksonCodec {
+
+ private CborCodec(ObjectMapper mapper) {
+ super(mapper);
+ }
+
+ /**
+ * Creates a {@link CborCodec} with default configurations. Use {@link #create(ObjectMapper)} for custom mapper
+ * configurations.
+ *
+ * @return A new instance of {@link CborCodec} with default mapper configurations.
+ */
+ public static CborCodec create() {
+ ObjectMapper mapper = new ObjectMapper(new CBORFactory());
+ configureDefaults(mapper);
+ return create(mapper);
+ }
+
+ /**
+ * Creates a {@link CborCodec} with custom mapper. Use {@link #create()} for default mapper configurations.
+ *
+ * @return A new instance of {@link CborCodec} with the passed mapper.
+ */
+ public static CborCodec create(ObjectMapper mapper) {
+ return new CborCodec(mapper);
+ }
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborHeader.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborHeader.java
new file mode 100644
index 000000000..da9813634
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborHeader.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal.cbor;
+
+import org.agrona.BitUtil;
+import rx.functions.Action2;
+import rx.functions.Actions;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * CBOR uses a compact format to encode the length and type of data that is written. More details can be found in the
+ * spec but it follows the following format:
+ *
+ *
First Byte
+ * The first byte of the header has the following data encoded:
+ *
+
Data type as specified by {@link CborMajorType#getTypeCode()}.
+ * <Code> above is the actual length for Header {@link #SMALL} and the code {@link #getCode()} for all other
+ * headers.
+ *
+ *
Remaining Bytes
+ *
+ * Headers {@link #SMALL} and {@link #INDEFINITE} does not contain any other bytes after the first bytes. The other
+ * headers contain {@link #getSizeInBytes()} {@code - 1} more bytes containing the actual length of the following data.
+ *
+ * This class abstracts all the above rules to correctly encode and decode this type headers.
+ */
+public enum CborHeader {
+
+ INDEFINITE(1, 31, Actions.empty(),
+ aLong -> aLong < 0,
+ buffer -> 31L,
+ aLong -> (byte) 31),
+ SMALL(1, -1,
+ Actions.empty(),
+ aLong -> aLong < 24, buffer -> -1L,
+ aLong -> aLong.byteValue()),
+ BYTE(1 + BitUtil.SIZE_OF_BYTE, 24,
+ (buffer, aLong) -> buffer.writeByte((byte) aLong.shortValue()),
+ aLong -> aLong <= Byte.MAX_VALUE,
+ buffer -> (long)buffer.readByte(), aLong -> (byte) 24),
+ SHORT(1 + BitUtil.SIZE_OF_SHORT, 25,
+ (buffer, aLong) -> buffer.writeShort(aLong.shortValue()),
+ aLong -> aLong <= Short.MAX_VALUE,
+ buffer -> (long)buffer.readShort(),
+ aLong -> (byte) 25),
+ INT(1 + BitUtil.SIZE_OF_INT, 26,
+ (buffer, aLong) -> buffer.writeInt(aLong.intValue()),
+ aLong -> aLong <= Integer.MAX_VALUE,
+ buffer -> (long)buffer.readInt(),
+ aLong -> (byte) 26),
+ LONG(1 + BitUtil.SIZE_OF_LONG, 27,
+ (buffer, aLong) -> buffer.writeLong(aLong),
+ aLong -> aLong <= Long.MAX_VALUE,
+ buffer -> (long)buffer.readLong(),
+ aLong -> (byte) 27);
+
+ private static final int LENGTH_MASK = 0b000_11111;
+ private final static Map reverseIndex;
+
+ static {
+ reverseIndex = new HashMap<>(CborHeader.values().length);
+ for (CborHeader h : CborHeader.values()) {
+ reverseIndex.put(h.code, h);
+ }
+ }
+
+ private final short sizeInBytes;
+ private final int code;
+ private final Action2 encodeFunction;
+ private final Function matchFunction;
+ private final Function decodeFunction;
+ private final Function codeFunction;
+
+ CborHeader(int sizeInBytes, int code, Action2 encodeFunction,
+ Function matchFunction, Function decodeFunction,
+ Function codeFunction) {
+ this.sizeInBytes = (short) sizeInBytes;
+ this.code = code;
+ this.encodeFunction = encodeFunction;
+ this.matchFunction = matchFunction;
+ this.decodeFunction = decodeFunction;
+ this.codeFunction = codeFunction;
+ }
+
+
+
+ /**
+ * Returns {@link CborHeader} instance appropriate for encoding the passed {@code bytesToEncode}.
+ *
+ * @param bytesToEncode Number of bytes to encode.
+ *
+ * @return {@link CborHeader} appropriate for encoding the passed number of bytes.
+ */
+ public static CborHeader forLengthToEncode(long bytesToEncode) {
+ if (INDEFINITE.matchFunction.apply(bytesToEncode)) {
+ return INDEFINITE;
+ }
+
+ if (SMALL.matchFunction.apply(bytesToEncode)) {
+ return SMALL;
+ }
+
+ if (BYTE.matchFunction.apply(bytesToEncode)) {
+ return BYTE;
+ }
+
+ if (SHORT.matchFunction.apply(bytesToEncode)) {
+ return SHORT;
+ }
+
+ if (INT.matchFunction.apply(bytesToEncode)) {
+ return INT;
+ }
+
+ return LONG;
+ }
+
+ /**
+ * Returns the number of bytes that this header will encode to.
+ *
+ * @return The number of bytes that this header will encode to.
+ */
+ public short getSizeInBytes() {
+ return sizeInBytes;
+ }
+
+ /**
+ * The CBOR code that will be encoded in the first byte of the header.
+ * {@link CborHeader#SMALL} will return -1 as there is no code for it. Instead it encodes the actual length.
+ *
+ * @return The number of bytes that this header will encode to.
+ */
+ public int getCode() {
+ return code;
+ }
+
+ /**
+ * Encodes the passed {@code type} and {@code length} into the passed {@code buffer}.
+ *
+ * @param buffer Destination for the encoding.
+ * @param type Type to encode.
+ * @param length Length to encode. Can be {@code -1} for {@link #INDEFINITE}, otherwise has to be a positive
+ * number.
+ *
+ * @throws IllegalArgumentException If the length is negative (for all headers except {@link #INDEFINITE}.
+ */
+ public void encode(IndexedUnsafeBuffer buffer, CborMajorType type, long length) {
+ if (length == -1 && this != INDEFINITE && length < 0) {
+ throw new IllegalArgumentException("Length must be positive.");
+ }
+
+ byte code = codeFunction.apply(length);
+ int firstByte = type.getTypeCode() << 5 | code;
+
+ buffer.writeByte((byte) firstByte);
+ encodeFunction.call(buffer, length);
+ }
+
+ /**
+ * Encodes the passed {@code type} for indefinite length into the passed {@code buffer}. Same as calling
+ * {@link #encode(IndexedUnsafeBuffer, CborMajorType, long)} with {@code -1} as length.
+ */
+ public void encodeIndefiniteLength(IndexedUnsafeBuffer buffer, CborMajorType type) {
+ encode(buffer, type, -1);
+ }
+
+ /**
+ * Given the first byte of a CBOR data type and length header, returns the length of the data item that follows the
+ * header. This will read {@link #getSizeInBytes()} number of bytes from the passed buffer corresponding to the
+ * {@link CborHeader} encoded in the first byte.
+ */
+ public static long readDataLength(IndexedUnsafeBuffer buffer, short firstHeaderByte) {
+ int firstLength = readLengthFromFirstHeaderByte(firstHeaderByte);
+ CborHeader cborHeader = reverseIndex.get(firstLength);
+ if (null != cborHeader) {
+ return cborHeader.decodeFunction.apply(buffer);
+ }
+
+ return firstLength;
+ }
+
+ /**
+ * Reads the length code from the first byte of CBOR header. This can be the actual length if the header is of type
+ * {@link #SMALL} or {@link #getCode()} for all other types.
+ */
+ public static int readLengthFromFirstHeaderByte(short firstHeaderByte) {
+ return firstHeaderByte & LENGTH_MASK;
+ }
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMajorType.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMajorType.java
new file mode 100644
index 000000000..66342b425
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMajorType.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal.cbor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A representation of all supported CBOR major types as defined in the spec.
+ */
+public enum CborMajorType {
+
+ UnsignedInteger(0),
+ NegativeInteger(1),
+ ByteString(2),
+ Utf8String(3),
+ ARRAY(4),
+ MAP(5),
+ Break(7),
+ Unknown(-1);
+
+ private final int typeCode;
+ private final static Map reverseIndex;
+
+ public static final byte CBOR_BREAK = (byte) 0b111_11111;
+
+ static {
+ reverseIndex = new HashMap<>(CborMajorType.values().length);
+ for (CborMajorType type : CborMajorType.values()) {
+ reverseIndex.put(type.typeCode, type);
+ }
+ }
+
+ CborMajorType(int typeCode) {
+ this.typeCode = typeCode;
+ }
+
+ public int getTypeCode() {
+ return typeCode;
+ }
+
+ /**
+ * Reads the first byte of the CBOR type header ({@link CborHeader}) to determine which type is encoded in the
+ * header.
+ *
+ * @param unsignedByte First byte of the type header.
+ *
+ * @return The major type as encoded in the header.
+ */
+ public static CborMajorType fromUnsignedByte(short unsignedByte) {
+ int type = unsignedByte >> 5 & 0x7;
+ CborMajorType t = reverseIndex.get(type);
+ if (null == t) {
+ return Unknown;
+ }
+
+ if (t == Break) {
+ final int length = CborHeader.readLengthFromFirstHeaderByte(unsignedByte);
+ if (31 == length) {
+ return Break;
+ }
+ return Unknown;
+ }
+
+ return t;
+ }
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodec.java
new file mode 100644
index 000000000..f7aa4fb96
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodec.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal.cbor;
+
+import io.reactivesocket.mimetypes.internal.MalformedInputException;
+import org.agrona.DirectBuffer;
+
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import static io.reactivesocket.mimetypes.internal.cbor.CBORUtils.*;
+import static io.reactivesocket.mimetypes.internal.cbor.CborMajorType.*;
+
+final class CborMapCodec {
+
+ @SuppressWarnings("ThrowableInstanceNeverThrown")
+ private static final MalformedInputException NOT_MAP = new MalformedInputException("Data is not a Map.");
+
+ @SuppressWarnings("ThrowableInstanceNeverThrown")
+ private static final MalformedInputException VALUE_NOT_BINARY =
+ new MalformedInputException("Value for a map entry is not binary.");
+
+ static {
+ NOT_MAP.setStackTrace(EMPTY_STACK);
+ VALUE_NOT_BINARY.setStackTrace(EMPTY_STACK);
+ }
+
+ private CborMapCodec() {
+ }
+
+ public static void encode(IndexedUnsafeBuffer dst, CBORMap cborMap) {
+ cborMap.encodeTo(dst);
+ }
+
+ public static void encode(IndexedUnsafeBuffer dst, Map toEncode) {
+ final int size = toEncode.size();
+ CborHeader.forLengthToEncode(size).encode(dst, MAP, size);
+ for (Entry entry : toEncode.entrySet()) {
+ CborUtf8StringCodec.encode(dst, entry.getKey());
+
+ CborBinaryStringCodec.encode(dst, entry.getValue());
+ }
+ }
+
+ public static CBORMap decode(IndexedUnsafeBuffer src, CborMapFactory mapFactory) {
+ long mapSize = parseDataLengthOrDie(src, MAP, NOT_MAP);
+ final CBORMap dst = mapFactory.newMap(src.getBackingBuffer(), src.getBackingBufferOffset(),
+ mapSize == CborHeader.INDEFINITE.getCode() ? 16 : (int) mapSize);
+ _decode(src, mapSize, dst);
+ return dst;
+ }
+
+ public static void decode(IndexedUnsafeBuffer src, CBORMap dst) {
+ long mapSize = parseDataLengthOrDie(src, MAP, NOT_MAP);
+ _decode(src, mapSize, dst);
+ }
+
+ private static void _decode(IndexedUnsafeBuffer src, long mapSize, CBORMap dst) {
+ boolean isIndefiniteMap = mapSize == CborHeader.INDEFINITE.getCode();
+ int i = 0;
+ while (true) {
+ String key = CborUtf8StringCodec.decode(src, isIndefiniteMap);
+ if (null == key) {
+ break;
+ }
+
+ int valLength = (int) parseDataLengthOrDie(src, ByteString, VALUE_NOT_BINARY);
+ int valueOffset = src.getReaderIndex();
+ if (valLength < 0) {
+ throw VALUE_NOT_BINARY;
+ }
+
+ if (valLength == CborHeader.INDEFINITE.getCode()) {
+ throw CborBinaryStringCodec.INDEFINITE_LENGTH_NOT_SUPPORTED;
+ }
+ dst.putValueOffset(key, valueOffset, valLength);
+ src.incrementReaderIndex(valLength);
+
+ if (!isIndefiniteMap && ++i >= mapSize) {
+ break;
+ }
+ }
+ }
+
+ public interface CborMapFactory {
+
+ CBORMap newMap(DirectBuffer backingBuffer, int offset, int initialCapacity);
+
+ }
+
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodec.java
new file mode 100644
index 000000000..403c8994f
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodec.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal.cbor;
+
+import io.reactivesocket.mimetypes.internal.MalformedInputException;
+
+import java.nio.charset.StandardCharsets;
+
+import static io.reactivesocket.mimetypes.internal.cbor.CBORUtils.*;
+import static io.reactivesocket.mimetypes.internal.cbor.CborMajorType.*;
+
+final class CborUtf8StringCodec {
+
+ @SuppressWarnings("ThrowableInstanceNeverThrown")
+ static final MalformedInputException NOT_UTF8_STRING =
+ new MalformedInputException("Data is not a definite length UTF-8 encoded string.");
+
+ @SuppressWarnings("ThrowableInstanceNeverThrown")
+ static final MalformedInputException BREAK_NOT_FOUND_FOR_INDEFINITE_LENGTH =
+ new MalformedInputException("End of string not found for indefinite length string.");
+
+ static {
+ NOT_UTF8_STRING.setStackTrace(EMPTY_STACK);
+ BREAK_NOT_FOUND_FOR_INDEFINITE_LENGTH.setStackTrace(EMPTY_STACK);
+ }
+
+ private CborUtf8StringCodec() {
+ }
+
+ public static void encode(IndexedUnsafeBuffer dst, String utf8String) {
+ byte[] bytes = utf8String.getBytes(StandardCharsets.UTF_8);
+ encodeTypeHeader(dst, Utf8String, bytes.length);
+ dst.writeBytes(bytes, 0, bytes.length);
+ }
+
+ public static String decode(IndexedUnsafeBuffer src) {
+ return decode(src, false);
+ }
+
+ public static String decode(IndexedUnsafeBuffer src, boolean returnNullIfBreak) {
+ int length = (int) parseDataLengthOrDie(src, Utf8String, NOT_UTF8_STRING);
+ if (length < 0) {
+ if (returnNullIfBreak) {
+ return null;
+ } else {
+ throw NOT_UTF8_STRING;
+ }
+ }
+
+ if (length == CborHeader.INDEFINITE.getCode()) {
+ String chunk = null;
+ while (true) {
+ byte aByte = src.getBackingBuffer().getByte(src.getReaderIndex());
+ if (aByte == CBOR_BREAK) {
+ break;
+ }
+
+ int chunkLength = (int) parseDataLengthOrDie(src, Utf8String, NOT_UTF8_STRING);
+ String thisChunk = readIntoString(src, chunkLength);
+ chunk = null == chunk ? thisChunk : chunk + thisChunk;
+ }
+
+ return chunk;
+ } else {
+ return readIntoString(src, length);
+ }
+ }
+
+ private static String readIntoString(IndexedUnsafeBuffer src, int chunkLength) {
+ byte[] keyBytes = new byte[chunkLength];
+ src.readBytes(keyBytes, 0, keyBytes.length);
+ return new String(keyBytes, StandardCharsets.UTF_8);
+ }
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBuffer.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBuffer.java
new file mode 100644
index 000000000..0f1b0a873
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBuffer.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal.cbor;
+
+import org.agrona.BitUtil;
+import org.agrona.DirectBuffer;
+import org.agrona.MutableDirectBuffer;
+import org.agrona.concurrent.UnsafeBuffer;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.function.Function;
+
+public class IndexedUnsafeBuffer {
+
+ public static final byte[] EMPTY_ARRAY = new byte[0];
+
+ private int readerIndex;
+ private int writerIndex;
+ private int backingBufferOffset;
+ private final UnsafeBuffer delegate;
+ private final ByteOrder byteOrder;
+
+ public IndexedUnsafeBuffer(ByteOrder byteOrder) {
+ this.byteOrder = byteOrder;
+ delegate = new UnsafeBuffer(EMPTY_ARRAY);
+ }
+
+ public void wrap(ByteBuffer buffer) {
+ wrap(buffer, 0, buffer.capacity());
+ }
+
+ public void wrap(ByteBuffer buffer, int offset, int length) {
+ delegate.wrap(buffer, offset, length);
+ readerIndex = 0;
+ writerIndex = 0;
+ backingBufferOffset = offset;
+ }
+
+ public void wrap(DirectBuffer buffer) {
+ wrap(buffer, 0, buffer.capacity());
+ }
+
+ public void wrap(DirectBuffer buffer, int offset, int length) {
+ delegate.wrap(buffer, offset, length);
+ readerIndex = 0;
+ writerIndex = 0;
+ backingBufferOffset = offset;
+ }
+
+ public short readUnsignedByte() {
+ return (short) (delegate.getByte(readerIndex++) & 0xff);
+ }
+
+ public byte readByte() {
+ return delegate.getByte(readerIndex++);
+ }
+
+ public int readShort() {
+ short s = delegate.getShort(readerIndex, byteOrder);
+ readerIndex += BitUtil.SIZE_OF_SHORT;
+ return s;
+ }
+
+ public int readInt() {
+ int i = delegate.getInt(readerIndex, byteOrder);
+ readerIndex += BitUtil.SIZE_OF_INT;
+ return i;
+ }
+
+ public long readLong() {
+ long l = delegate.getLong(readerIndex, byteOrder);
+ readerIndex += BitUtil.SIZE_OF_LONG;
+ return l;
+ }
+
+ public void readBytes(byte[] dst, int dstOffset, int length) {
+ delegate.getBytes(readerIndex, dst, dstOffset, length);
+ readerIndex += length - dstOffset;
+ }
+
+ public void readBytes(MutableDirectBuffer dst, int offset, int length) {
+ delegate.getBytes(readerIndex, dst, offset, length);
+ readerIndex += length;
+ }
+
+ public void readBytes(IndexedUnsafeBuffer dst, int length) {
+ delegate.getBytes(readerIndex, dst.getBackingBuffer(), dst.getWriterIndex(), length);
+ readerIndex += length;
+ }
+
+ public void writeByte(byte toWrite) {
+ delegate.putByte(writerIndex++, toWrite);
+ }
+
+ public void writeShort(short toWrite) {
+ delegate.putShort(writerIndex, toWrite, byteOrder);
+ writerIndex += BitUtil.SIZE_OF_SHORT;
+ }
+
+ public void writeInt(int toWrite) {
+ delegate.putInt(writerIndex, toWrite, byteOrder);
+ writerIndex += BitUtil.SIZE_OF_INT;
+ }
+
+ public void writeLong(long toWrite) {
+ delegate.putLong(writerIndex, toWrite, byteOrder);
+ writerIndex += BitUtil.SIZE_OF_LONG;
+ }
+
+ public void writeBytes(byte[] src, int offset, int length) {
+ delegate.putBytes(writerIndex, src, offset, length);
+ writerIndex += length;
+ }
+
+ public void writeBytes(ByteBuffer src, int length) {
+ delegate.putBytes(writerIndex, src, src.position(), length);
+ writerIndex += length;
+ }
+
+ public void writeBytes(DirectBuffer src, int offset, int length) {
+ delegate.putBytes(writerIndex, src, offset, length);
+ writerIndex += length;
+ }
+
+ public int getReaderIndex() {
+ return readerIndex;
+ }
+
+ public int getWriterIndex() {
+ return writerIndex;
+ }
+
+ public UnsafeBuffer getBackingBuffer() {
+ return delegate;
+ }
+
+ public int getBackingBufferOffset() {
+ return backingBufferOffset;
+ }
+
+ public void incrementReaderIndex(int increment) {
+ readerIndex += increment;
+ }
+
+ public void setReaderIndex(int readerIndex) {
+ if (readerIndex >= delegate.capacity()) {
+ throw new IllegalArgumentException(
+ String.format("Reader Index should be less than capacity. Reader Index: %d, Capacity: %d",
+ readerIndex, delegate.capacity()));
+ }
+ this.readerIndex = readerIndex;
+ }
+
+ public void setWriterIndex(int writerIndex) {
+ if (writerIndex >= delegate.capacity()) {
+ throw new IllegalArgumentException(
+ String.format("Writer Index should be less than capacity. Writer Index: %d, Capacity: %d",
+ writerIndex, delegate.capacity()));
+ }
+ this.writerIndex = writerIndex;
+ }
+
+ public void incrementWriterIndex(int increment) {
+ writerIndex += increment;
+ }
+
+ /**
+ * Scans this buffer and invokes the passed {@code scanner} for every byte. The scan stops if it has reached the end
+ * of buffer or the {@code scanner} returns {@code false}. This method does not move the {@code readerIndex} for
+ * this buffer.
+ *
+ * @param scanner Scanner that determines to scan further for every byte.
+ *
+ * @return Index in the buffer at which this scan stopped.
+ */
+ public int forEachByte(Function scanner) {
+ int i;
+ for (i = readerIndex; i < delegate.capacity(); i++) {
+ if (!scanner.apply(delegate.getByte(i))) {
+ break;
+ }
+ }
+ return i;
+ }
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodec.java
new file mode 100644
index 000000000..1e4ae7405
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodec.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal.cbor;
+
+import io.reactivesocket.mimetypes.KVMetadata;
+import io.reactivesocket.mimetypes.internal.Codec;
+import org.agrona.DirectBuffer;
+import org.agrona.MutableDirectBuffer;
+import org.agrona.concurrent.UnsafeBuffer;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Map.Entry;
+
+import static io.reactivesocket.mimetypes.internal.cbor.CBORUtils.*;
+
+/**
+ * This is a custom codec for default {@link KVMetadata} as defined by
+ * the spec. Since, the format
+ * is simple, it does not use a third-party library to do the encoding, but the logic is contained in this class.
+ */
+public class MetadataCodec implements Codec {
+
+ public static final MetadataCodec INSTANCE = new MetadataCodec();
+
+ private static final ThreadLocal indexedBuffers =
+ ThreadLocal.withInitial(() -> new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN));
+
+ protected MetadataCodec() {
+ }
+
+ @Override
+ public T decode(ByteBuffer buffer, Class tClass) {
+ isValidType(tClass);
+
+ IndexedUnsafeBuffer tmp = indexedBuffers.get();
+ tmp.wrap(buffer);
+
+ return _decode(tmp);
+ }
+
+ @Override
+ public T decode(DirectBuffer buffer, int offset, Class tClass) {
+ isValidType(tClass);
+
+ IndexedUnsafeBuffer tmp = indexedBuffers.get();
+ tmp.wrap(buffer);
+
+ return _decode(tmp);
+ }
+
+ @Override
+ public ByteBuffer encode(T toEncode) {
+ isValidType(toEncode.getClass());
+
+ if (toEncode instanceof SlicedBufferKVMetadata) {
+ return ((SlicedBufferKVMetadata) toEncode).getBackingBuffer().byteBuffer();
+ }
+
+ ByteBuffer dst = ByteBuffer.allocate(getSizeAsBytes((KVMetadata) toEncode));
+ encodeTo(dst, toEncode);
+ return dst;
+ }
+
+ @Override
+ public DirectBuffer encodeDirect(T toEncode) {
+ isValidType(toEncode.getClass());
+
+ final KVMetadata input = (KVMetadata) toEncode;
+
+ if (toEncode instanceof SlicedBufferKVMetadata) {
+ return ((SlicedBufferKVMetadata) toEncode).getBackingBuffer();
+ }
+
+ MutableDirectBuffer toReturn = new UnsafeBuffer(ByteBuffer.allocate(getSizeAsBytes(input)));
+ encodeTo(toReturn, toEncode, 0);
+ return toReturn;
+ }
+
+ @Override
+ public void encodeTo(ByteBuffer buffer, T toEncode) {
+ isValidType(toEncode.getClass());
+
+ final KVMetadata input = (KVMetadata) toEncode;
+
+ IndexedUnsafeBuffer tmp = indexedBuffers.get();
+ tmp.wrap(buffer);
+
+ _encodeTo(tmp, input);
+ }
+
+ @Override
+ public void encodeTo(MutableDirectBuffer buffer, T toEncode, int offset) {
+ isValidType(toEncode.getClass());
+
+ final KVMetadata input = (KVMetadata) toEncode;
+
+ IndexedUnsafeBuffer tmp = indexedBuffers.get();
+ tmp.wrap(buffer, offset, buffer.capacity());
+
+ _encodeTo(tmp, input);
+ }
+
+ private static void _encodeTo(IndexedUnsafeBuffer buffer, KVMetadata toEncode) {
+ if (toEncode instanceof SlicedBufferKVMetadata) {
+ SlicedBufferKVMetadata s = (SlicedBufferKVMetadata) toEncode;
+ DirectBuffer backingBuffer = s.getBackingBuffer();
+ backingBuffer.getBytes(0, buffer.getBackingBuffer(), buffer.getWriterIndex(), backingBuffer.capacity());
+ return;
+ }
+
+ CborMapCodec.encode(buffer, toEncode);
+ }
+
+ private static T _decode(IndexedUnsafeBuffer src) {
+ CBORMap m = CborMapCodec.decode(src,
+ (backingBuffer, offset, initialCapacity) -> new SlicedBufferKVMetadata(
+ backingBuffer, offset, initialCapacity));
+ @SuppressWarnings("unchecked")
+ T t = (T) m;
+ return t;
+ }
+
+ private static int getSizeAsBytes(KVMetadata toEncode) {
+ int toReturn = 1 + (int) getEncodeLength(toEncode.size()); // Map Starting + break
+ for (Entry entry : toEncode.entrySet()) {
+ toReturn += getEncodeLength(entry.getKey().length());
+ toReturn += entry.getKey().length();
+
+ int valueLength = entry.getValue().remaining();
+ toReturn += getEncodeLength(valueLength);
+ toReturn += valueLength;
+ }
+ return toReturn;
+ }
+
+ private static void isValidType(Class tClass) {
+ if (!KVMetadata.class.isAssignableFrom(tClass)) {
+ throw new IllegalArgumentException("Metadata codec only supports encoding/decoding of: "
+ + KVMetadata.class.getName());
+ }
+ }
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/ReactiveSocketDefaultMetadataCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/ReactiveSocketDefaultMetadataCodec.java
new file mode 100644
index 000000000..8d15c1cf0
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/ReactiveSocketDefaultMetadataCodec.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal.cbor;
+
+import io.reactivesocket.mimetypes.KVMetadata;
+import io.reactivesocket.mimetypes.internal.Codec;
+import org.agrona.DirectBuffer;
+import org.agrona.MutableDirectBuffer;
+
+import java.nio.ByteBuffer;
+
+public class ReactiveSocketDefaultMetadataCodec implements Codec {
+
+ private final CborCodec cborCodec;
+
+ private ReactiveSocketDefaultMetadataCodec(CborCodec cborCodec) {
+ this.cborCodec = cborCodec;
+ }
+
+ public KVMetadata decodeDefault(ByteBuffer buffer) {
+ return MetadataCodec.INSTANCE.decode(buffer, SlicedBufferKVMetadata.class);
+ }
+
+ public KVMetadata decodeDefault(DirectBuffer buffer, int offset) {
+ return MetadataCodec.INSTANCE.decode(buffer, offset, SlicedBufferKVMetadata.class);
+ }
+
+ @Override
+ public T decode(ByteBuffer buffer, Class tClass) {
+ if (KVMetadata.class.isAssignableFrom(tClass)) {
+ @SuppressWarnings("unchecked")
+ T t = (T) decodeDefault(buffer);
+ return t;
+ }
+ return cborCodec.decode(buffer, tClass);
+ }
+
+ @Override
+ public T decode(DirectBuffer buffer, int offset, Class tClass) {
+ if (KVMetadata.class.isAssignableFrom(tClass)) {
+ @SuppressWarnings("unchecked")
+ T t = (T) decodeDefault(buffer, offset);
+ return t;
+ }
+ return cborCodec.decode(buffer, offset, tClass);
+ }
+
+ @Override
+ public ByteBuffer encode(T toEncode) {
+ if (KVMetadata.class.isAssignableFrom(toEncode.getClass())) {
+ return MetadataCodec.INSTANCE.encode(toEncode);
+ }
+ return cborCodec.encode(toEncode);
+ }
+
+ @Override
+ public DirectBuffer encodeDirect(T toEncode) {
+ if (KVMetadata.class.isAssignableFrom(toEncode.getClass())) {
+ return MetadataCodec.INSTANCE.encodeDirect(toEncode);
+ }
+ return cborCodec.encodeDirect(toEncode);
+ }
+
+ @Override
+ public void encodeTo(ByteBuffer buffer, T toEncode) {
+ if (KVMetadata.class.isAssignableFrom(toEncode.getClass())) {
+ MetadataCodec.INSTANCE.encodeTo(buffer, toEncode);
+ } else {
+ cborCodec.encodeTo(buffer, toEncode);
+ }
+ }
+
+ @Override
+ public void encodeTo(MutableDirectBuffer buffer, T toEncode, int offset) {
+ if (KVMetadata.class.isAssignableFrom(toEncode.getClass())) {
+ MetadataCodec.INSTANCE.encodeTo(buffer, toEncode, offset);
+ } else {
+ cborCodec.encodeTo(buffer, toEncode, offset);
+ }
+ }
+
+ public static ReactiveSocketDefaultMetadataCodec create() {
+ return new ReactiveSocketDefaultMetadataCodec(CborCodec.create());
+ }
+
+ public static ReactiveSocketDefaultMetadataCodec create(CborCodec cborCodec) {
+ return new ReactiveSocketDefaultMetadataCodec(cborCodec);
+ }
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadata.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadata.java
new file mode 100644
index 000000000..c6ab714d3
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadata.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal.cbor;
+
+import io.reactivesocket.mimetypes.KVMetadata;
+import org.agrona.DirectBuffer;
+import org.agrona.MutableDirectBuffer;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * An implementation of {@link KVMetadata} that does not allocate buffers for values of metadata, instead it keeps a
+ * view of the original buffer with offsets to the values. See {@link CBORMap} to learn more about the structure and
+ * cases when this creates allocations.
+ *
+ *
Lifecycle
+ *
+ * This assumes exclusive access to the underlying buffer. If that is not the case, then {@link #duplicate(Function)}
+ * must be used to create a copy of the underlying buffer.
+ *
+ * @see CBORMap
+ */
+public class SlicedBufferKVMetadata extends CBORMap implements KVMetadata {
+
+ public SlicedBufferKVMetadata(DirectBuffer backingBuffer, int offset, int initialCapacity) {
+ super(backingBuffer, offset, initialCapacity);
+ }
+
+ public SlicedBufferKVMetadata(DirectBuffer backingBuffer, int offset) {
+ super(backingBuffer, offset);
+ }
+
+ protected SlicedBufferKVMetadata(Map storeWhenModified) {
+ super(storeWhenModified);
+ }
+
+ protected SlicedBufferKVMetadata(DirectBuffer backingBuffer, int offset, Map keysVsOffsets) {
+ super(backingBuffer, offset, keysVsOffsets);
+ }
+
+ @Override
+ public String getAsString(String key, Charset valueEncoding) {
+ ByteBuffer v = get(key);
+ byte[] vBytes;
+ if (v.hasArray()) {
+ return new String(v.array(), v.arrayOffset(), v.limit(), valueEncoding);
+ } else {
+ vBytes = new byte[v.remaining()];
+ v.get(vBytes);
+ return new String(vBytes, valueEncoding);
+ }
+ }
+
+ @Override
+ public KVMetadata duplicate(Function newBufferFactory) {
+ if (null == storeWhenModified) {
+ int newCap = backingBuffer.capacity();
+ MutableDirectBuffer newBuffer = newBufferFactory.apply(newCap);
+ backingBuffer.getBytes(0, newBuffer, 0, newCap);
+ return new SlicedBufferKVMetadata(newBuffer, 0, new HashMap<>(keysVsOffsets));
+ } else {
+ return new SlicedBufferKVMetadata(storeWhenModified);
+ }
+ }
+}
diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/json/JsonCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/json/JsonCodec.java
new file mode 100644
index 000000000..2dd535d67
--- /dev/null
+++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/json/JsonCodec.java
@@ -0,0 +1,32 @@
+package io.reactivesocket.mimetypes.internal.json;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.reactivesocket.mimetypes.internal.AbstractJacksonCodec;
+
+public class JsonCodec extends AbstractJacksonCodec {
+
+ private JsonCodec(ObjectMapper mapper) {
+ super(mapper);
+ }
+
+ /**
+ * Creates a {@link JsonCodec} with default configurations. Use {@link #create(ObjectMapper)} for custom mapper
+ * configurations.
+ *
+ * @return A new instance of {@link JsonCodec} with default mapper configurations.
+ */
+ public static JsonCodec create() {
+ ObjectMapper mapper = new ObjectMapper();
+ configureDefaults(mapper);
+ return create(mapper);
+ }
+
+ /**
+ * Creates a {@link JsonCodec} with custom mapper. Use {@link #create()} for default mapper configurations.
+ *
+ * @return A new instance of {@link JsonCodec} with the passed mapper.
+ */
+ public static JsonCodec create(ObjectMapper mapper) {
+ return new JsonCodec(mapper);
+ }
+}
diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/MimeTypeFactoryTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/MimeTypeFactoryTest.java
new file mode 100644
index 000000000..19771642d
--- /dev/null
+++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/MimeTypeFactoryTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes;
+
+import io.reactivesocket.ConnectionSetupPayload;
+import io.reactivesocket.mimetypes.internal.Codec;
+import io.reactivesocket.mimetypes.internal.CustomObject;
+import io.reactivesocket.mimetypes.internal.CustomObjectRule;
+import io.reactivesocket.mimetypes.internal.KVMetadataImpl;
+import io.reactivesocket.mimetypes.internal.MetadataRule;
+import io.reactivesocket.mimetypes.internal.cbor.CborCodec;
+import io.reactivesocket.mimetypes.internal.cbor.ReactiveSocketDefaultMetadataCodec;
+import org.agrona.DirectBuffer;
+import org.agrona.MutableDirectBuffer;
+import org.agrona.concurrent.UnsafeBuffer;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+import static io.reactivesocket.mimetypes.SupportedMimeTypes.*;
+import static io.reactivesocket.mimetypes.internal.cbor.ByteBufferMapMatcher.*;
+
+public class MimeTypeFactoryTest {
+
+ @Rule
+ public final CustomObjectRule objectRule = new CustomObjectRule();
+ @Rule
+ public final MetadataRule metadataRule = new MetadataRule();
+
+ @Test(timeout = 60000)
+ public void testFromSetup() throws Exception {
+ objectRule.populateDefaultData();
+ metadataRule.populateDefaultMetadataData();
+
+ MimeType mimeType = getMimeTypeFromSetup(ReactiveSocketDefaultMetadata, CBOR);
+
+ testMetadataCodec(mimeType, ReactiveSocketDefaultMetadataCodec.create());
+ testDataCodec(mimeType, CborCodec.create());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testUnsupportedMimetype() throws Exception {
+ ConnectionSetupPayload setup = new ConnectionSetupPayloadImpl("blah", "blah");
+ MimeTypeFactory.from(setup);
+ }
+
+ @Test(timeout = 60000)
+ public void testOneMimetype() throws Exception {
+ objectRule.populateDefaultData();
+ metadataRule.populateDefaultMetadataData();
+
+ MimeType mimeType = MimeTypeFactory.from(CBOR);
+
+ testMetadataCodec(mimeType, CborCodec.create());
+ testDataCodec(mimeType, CborCodec.create());
+ }
+
+ @Test(timeout = 60000)
+ public void testDifferentMimetypes() throws Exception {
+ objectRule.populateDefaultData();
+ metadataRule.populateDefaultMetadataData();
+
+ MimeType mimeType = MimeTypeFactory.from(ReactiveSocketDefaultMetadata, CBOR);
+
+ testMetadataCodec(mimeType, ReactiveSocketDefaultMetadataCodec.create());
+ testDataCodec(mimeType, ReactiveSocketDefaultMetadataCodec.create());
+ }
+
+ private void testMetadataCodec(MimeType mimeType, Codec expectedCodec) {
+ ByteBuffer encode = mimeType.encodeMetadata(metadataRule.getKvMetadata());
+ ByteBuffer encode1 = expectedCodec.encode(metadataRule.getKvMetadata());
+
+ MatcherAssert.assertThat("Unexpected encode from mime type.", encode, Matchers.equalTo(encode1));
+ MatcherAssert.assertThat("Unexpected decode from encode.", metadataRule.getKvMetadata(),
+ mapEqualTo(mimeType.decodeMetadata(encode, KVMetadataImpl.class)));
+
+
+ DirectBuffer dencode = mimeType.encodeMetadataDirect(metadataRule.getKvMetadata());
+ DirectBuffer dencode1 = expectedCodec.encodeDirect(metadataRule.getKvMetadata());
+
+ MatcherAssert.assertThat("Unexpected direct buffer encode from mime type.", dencode, Matchers.equalTo(dencode1));
+ MatcherAssert.assertThat("Unexpected decode from direct encode.", metadataRule.getKvMetadata(),
+ mapEqualTo(mimeType.decodeMetadata(dencode, KVMetadataImpl.class, 0)));
+
+ ByteBuffer dst = ByteBuffer.allocate(100);
+ ByteBuffer dst1 = ByteBuffer.allocate(100);
+
+ mimeType.encodeMetadataTo(dst, metadataRule.getKvMetadata());
+ dst.flip();
+ expectedCodec.encodeTo(dst1, metadataRule.getKvMetadata());
+ dst1.flip();
+
+ MatcherAssert.assertThat("Unexpected encodeTo buffer encode from mime type.", dst, Matchers.equalTo(dst1));
+ MatcherAssert.assertThat("Unexpected decode from encodeTo encode.", metadataRule.getKvMetadata(),
+ mapEqualTo(mimeType.decodeMetadata(dst, KVMetadataImpl.class)));
+
+ MutableDirectBuffer mdst = new UnsafeBuffer(new byte[100]);
+ MutableDirectBuffer mdst1 = new UnsafeBuffer(new byte[100]);
+
+ mimeType.encodeMetadataTo(mdst, metadataRule.getKvMetadata(), 0);
+ expectedCodec.encodeTo(mdst1, metadataRule.getKvMetadata(), 0);
+
+ MatcherAssert.assertThat("Unexpected encodeTo buffer encode from mime type.", mdst, Matchers.equalTo(mdst1));
+ MatcherAssert.assertThat("Unexpected decode from encodeTo encode.", metadataRule.getKvMetadata(),
+ mapEqualTo(mimeType.decodeMetadata(mdst, KVMetadataImpl.class, 0)));
+ }
+
+ private void testDataCodec(MimeType mimeType, Codec expectedCodec) {
+ ByteBuffer encode = mimeType.encodeData(objectRule.getData());
+ ByteBuffer encode1 = expectedCodec.encode(objectRule.getData());
+
+ MatcherAssert.assertThat("Unexpected encode from mime type.", encode, Matchers.equalTo(encode1));
+ MatcherAssert.assertThat("Unexpected decode from encode.", objectRule.getData(),
+ Matchers.equalTo(mimeType.decodeData(encode, CustomObject.class)));
+
+
+ DirectBuffer dencode = mimeType.encodeDataDirect(objectRule.getData());
+ DirectBuffer dencode1 = expectedCodec.encodeDirect(objectRule.getData());
+
+ MatcherAssert.assertThat("Unexpected direct buffer encode from mime type.", dencode, Matchers.equalTo(dencode1));
+ MatcherAssert.assertThat("Unexpected decode from direct encode.", objectRule.getData(),
+ Matchers.equalTo(mimeType.decodeData(dencode, CustomObject.class, 0)));
+
+ ByteBuffer dst = ByteBuffer.allocate(100);
+ ByteBuffer dst1 = ByteBuffer.allocate(100);
+
+ mimeType.encodeDataTo(dst, objectRule.getData());
+ dst.flip();
+ expectedCodec.encodeTo(dst1, objectRule.getData());
+ dst1.flip();
+
+ MatcherAssert.assertThat("Unexpected encodeTo buffer encode from mime type.", dst, Matchers.equalTo(dst1));
+ MatcherAssert.assertThat("Unexpected decode from encodeTo encode.", objectRule.getData(),
+ Matchers.equalTo(mimeType.decodeData(dst, CustomObject.class)));
+
+ MutableDirectBuffer mdst = new UnsafeBuffer(new byte[100]);
+ MutableDirectBuffer mdst1 = new UnsafeBuffer(new byte[100]);
+
+ mimeType.encodeDataTo(mdst, objectRule.getData(), 0);
+ expectedCodec.encodeTo(mdst1, objectRule.getData(), 0);
+
+ MatcherAssert.assertThat("Unexpected encodeTo buffer encode from mime type.", mdst, Matchers.equalTo(mdst1));
+ MatcherAssert.assertThat("Unexpected decode from encodeTo encode.", objectRule.getData(),
+ Matchers.equalTo(mimeType.decodeData(mdst, CustomObject.class, 0)));
+ }
+
+ private static MimeType getMimeTypeFromSetup(SupportedMimeTypes metaMime, SupportedMimeTypes dataMime) {
+ ConnectionSetupPayload setup = new ConnectionSetupPayloadImpl(dataMime.getMimeTypes().get(0),
+ metaMime.getMimeTypes().get(0));
+
+ return MimeTypeFactory.from(setup);
+ }
+
+ private static class ConnectionSetupPayloadImpl extends ConnectionSetupPayload {
+
+ private final String dataMime;
+ private final String metadataMime;
+
+ private ConnectionSetupPayloadImpl(String dataMime, String metadataMime) {
+ this.dataMime = dataMime;
+ this.metadataMime = metadataMime;
+ }
+
+ @Override
+ public String metadataMimeType() {
+ return metadataMime;
+ }
+
+ @Override
+ public String dataMimeType() {
+ return dataMime;
+ }
+
+ @Override
+ public ByteBuffer getData() {
+ return ByteBuffer.allocate(0);
+ }
+
+ @Override
+ public ByteBuffer getMetadata() {
+ return ByteBuffer.allocate(0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodecTest.java
new file mode 100644
index 000000000..c1dd271e7
--- /dev/null
+++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodecTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal;
+
+import org.agrona.DirectBuffer;
+import org.agrona.MutableDirectBuffer;
+import org.agrona.concurrent.UnsafeBuffer;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+public abstract class AbstractJacksonCodecTest {
+
+ @Rule
+ public final CustomObjectRule customObjectRule = new CustomObjectRule();
+ @Rule
+ public final MetadataRule metadataRule = new MetadataRule();
+
+ @Test(timeout = 60000)
+ public void encodeDecode() throws Exception {
+ customObjectRule.populateDefaultData();
+ ByteBuffer encode = getCodecRule().getCodec().encode(customObjectRule.getData());
+
+ CustomObject decode = getCodecRule().getCodec().decode(encode, CustomObject.class);
+ MatcherAssert.assertThat("Unexpected decode.", decode, Matchers.equalTo(customObjectRule.getData()));
+ }
+
+ @Test(timeout = 60000)
+ public void encodeDecodeDirect() throws Exception {
+ customObjectRule.populateDefaultData();
+ DirectBuffer encode = getCodecRule().getCodec().encodeDirect(customObjectRule.getData());
+
+ CustomObject decode = getCodecRule().getCodec().decode(encode, 0, CustomObject.class);
+ MatcherAssert.assertThat("Unexpected decode.", decode, Matchers.equalTo(customObjectRule.getData()));
+ }
+
+ @Test(timeout = 60000)
+ public void encodeTo() throws Exception {
+ customObjectRule.populateDefaultData();
+ ByteBuffer encodeDest = ByteBuffer.allocate(10000);
+ getCodecRule().getCodec().encodeTo(encodeDest, customObjectRule.getData());
+
+ encodeDest.flip(); /*Since we want to decode it now*/
+
+ CustomObject decode = getCodecRule().getCodec().decode(encodeDest, CustomObject.class);
+
+ MatcherAssert.assertThat("Unexpected decode.", decode, Matchers.equalTo(customObjectRule.getData()));
+ }
+
+ @Test(timeout = 60000)
+ public void encodeToDirect() throws Exception {
+ customObjectRule.populateDefaultData();
+ byte[] destArr = new byte[1000];
+ MutableDirectBuffer encodeDest = new UnsafeBuffer(destArr);
+ getCodecRule().getCodec().encodeTo(encodeDest, customObjectRule.getData(), 0);
+
+ CustomObject decode = getCodecRule().getCodec().decode(encodeDest, 0, CustomObject.class);
+
+ MatcherAssert.assertThat("Unexpected decode.", decode, Matchers.equalTo(customObjectRule.getData()));
+ }
+
+ protected abstract CodecRule getCodecRule();
+}
diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CodecRule.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CodecRule.java
new file mode 100644
index 000000000..4a15b8c4d
--- /dev/null
+++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CodecRule.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal;
+
+import org.junit.rules.ExternalResource;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import rx.functions.Func0;
+
+public class CodecRule extends ExternalResource {
+
+ private T codec;
+ private final Func0 codecFactory;
+
+ public CodecRule(Func0 codecFactory) {
+ this.codecFactory = codecFactory;
+ }
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ codec = codecFactory.call();
+ base.evaluate();
+ }
+ };
+ }
+
+ public T getCodec() {
+ return codec;
+ }
+}
diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObject.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObject.java
new file mode 100644
index 000000000..82a7f69ba
--- /dev/null
+++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObject.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal;
+
+import java.util.Map;
+
+public class CustomObject {
+
+ private String name;
+ private int age;
+ private Map attributes;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public Map getAttributes() {
+ return attributes;
+ }
+
+ public void setAttributes(Map attributes) {
+ this.attributes = attributes;
+ }
+
+ @Override
+ public String toString() {
+ String sb = "CustomObject{" + "name='" + name + '\'' +
+ ", age=" + age +
+ ", attributes=" + attributes +
+ '}';
+ return sb;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof CustomObject)) {
+ return false;
+ }
+
+ CustomObject that = (CustomObject) o;
+
+ if (age != that.age) {
+ return false;
+ }
+ if (name != null? !name.equals(that.name) : that.name != null) {
+ return false;
+ }
+ if (attributes != null? !attributes.equals(that.attributes) : that.attributes != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name != null? name.hashCode() : 0;
+ result = 31 * result + age;
+ result = 31 * result + (attributes != null? attributes.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObjectRule.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObjectRule.java
new file mode 100644
index 000000000..9e002d8ac
--- /dev/null
+++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObjectRule.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal;
+
+import org.junit.rules.ExternalResource;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CustomObjectRule extends ExternalResource {
+
+ private CustomObject data;
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ data = new CustomObject();
+ base.evaluate();
+ }
+ };
+ }
+ public CustomObject getData() {
+ return data;
+ }
+
+ public void populateDefaultData() {
+ data.setAge(100);
+ data.setName("Dummy");
+ Map attribs = new HashMap<>();
+ attribs.put("1K", 1);
+ attribs.put("2K", 2);
+ data.setAttributes(attribs);
+ }
+}
diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/KVMetadataImplTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/KVMetadataImplTest.java
new file mode 100644
index 000000000..b3e4836b9
--- /dev/null
+++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/KVMetadataImplTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal;
+
+import org.hamcrest.MatcherAssert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExternalResource;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import static org.hamcrest.Matchers.*;
+
+public class KVMetadataImplTest {
+
+ @Rule
+ public final MetadataRule metadataRule = new MetadataRule();
+
+ @Test(timeout = 60000)
+ public void testGetAsString() throws Exception {
+ String key = "Key1";
+ String value = "Value1";
+ metadataRule.addMetadata(key, value);
+
+ String lookup = metadataRule.getKvMetadata().getAsString(key, StandardCharsets.UTF_8);
+
+ MatcherAssert.assertThat("Unexpected lookup value.", lookup, equalTo(value));
+ }
+
+ @Test(timeout = 60000)
+ public void testGetAsEmptyString() throws Exception {
+ String key = "Key1";
+ String value = "";
+ metadataRule.addMetadata(key, value);
+
+ String lookup = metadataRule.getKvMetadata().getAsString(key, StandardCharsets.UTF_8);
+
+ MatcherAssert.assertThat("Unexpected lookup value.", lookup, equalTo(value));
+ }
+
+ @Test(timeout = 60000)
+ public void testGetAsStringInvalid() throws Exception {
+
+ String lookup = metadataRule.getKvMetadata().getAsString("Key", StandardCharsets.UTF_8);
+
+ MatcherAssert.assertThat("Unexpected lookup value.", lookup, nullValue());
+ }
+}
\ No newline at end of file
diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/MetadataRule.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/MetadataRule.java
new file mode 100644
index 000000000..ffa4bb484
--- /dev/null
+++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/MetadataRule.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal;
+
+import org.junit.rules.ExternalResource;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+
+public class MetadataRule extends ExternalResource {
+
+ private KVMetadataImpl kvMetadata;
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ kvMetadata = new KVMetadataImpl(new HashMap<>());
+ base.evaluate();
+ }
+ };
+ }
+
+ public void populateDefaultMetadataData() {
+ addMetadata("Hello1", "HelloVal1");
+ addMetadata("Hello2", "HelloVal2");
+ }
+
+ public void addMetadata(String key, String value) {
+ ByteBuffer allocate = ByteBuffer.allocate(value.length());
+ allocate.put(value.getBytes(StandardCharsets.UTF_8)).flip();
+ kvMetadata.put(key, allocate);
+ }
+
+ public void addMetadata(String key, ByteBuffer value) {
+ kvMetadata.put(key, value);
+ }
+
+ public KVMetadataImpl getKvMetadata() {
+ return kvMetadata;
+ }
+
+}
diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodecTest.java
new file mode 100644
index 000000000..25bf65383
--- /dev/null
+++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodecTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal;
+
+import io.reactivesocket.mimetypes.KVMetadata;
+import io.reactivesocket.mimetypes.internal.cbor.ReactiveSocketDefaultMetadataCodec;
+import org.agrona.DirectBuffer;
+import org.hamcrest.MatcherAssert;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+import static io.reactivesocket.mimetypes.internal.cbor.ByteBufferMapMatcher.*;
+
+public class ReactiveSocketDefaultMetadataCodecTest {
+
+ @Rule
+ public final CodecRule codecRule =
+ new CodecRule<>(ReactiveSocketDefaultMetadataCodec::create);
+ @Rule
+ public final MetadataRule metadataRule = new MetadataRule();
+
+ @Test(timeout = 60000)
+ public void testDecodeDefault() throws Exception {
+
+ metadataRule.populateDefaultMetadataData();
+
+ ByteBuffer encode = codecRule.getCodec().encode(metadataRule.getKvMetadata());
+ KVMetadata kvMetadata = codecRule.getCodec().decodeDefault(encode);
+
+ MatcherAssert.assertThat("Unexpected decoded metadata.", kvMetadata, mapEqualTo(metadataRule.getKvMetadata()));
+ }
+
+ @Test(timeout = 60000)
+ public void testDecodeDefaultDirect() throws Exception {
+
+ metadataRule.populateDefaultMetadataData();
+
+ DirectBuffer encode = codecRule.getCodec().encodeDirect(metadataRule.getKvMetadata());
+ KVMetadata kvMetadata = codecRule.getCodec().decodeDefault(encode, 0);
+
+ MatcherAssert.assertThat("Unexpected decoded metadata.", kvMetadata, mapEqualTo(metadataRule.getKvMetadata()));
+ }
+
+}
\ No newline at end of file
diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/AbstractCborMapRule.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/AbstractCborMapRule.java
new file mode 100644
index 000000000..69bc95b5a
--- /dev/null
+++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/AbstractCborMapRule.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal.cbor;
+
+import org.junit.rules.ExternalResource;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import static org.hamcrest.MatcherAssert.*;
+import static org.hamcrest.Matchers.*;
+
+public abstract class AbstractCborMapRule extends ExternalResource {
+
+ protected T map;
+ protected ByteBuffer valueBuffer;
+ protected IndexedUnsafeBuffer indexed;
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ init();
+ base.evaluate();
+ }
+ };
+ }
+
+ protected abstract void init();
+
+ public void addMockEntries(int count) {
+ for (int i =0; i < count; i++) {
+ addEntry(getMockKey(i), getMockValue(i));
+ }
+ }
+
+ public void addEntry(String utf8Key, String value) {
+ ByteBuffer vBuf = toValueBuffer(value);
+ int valueLength = vBuf.remaining();
+ int offset = indexed.getWriterIndex();
+ indexed.writeBytes(vBuf, valueLength);
+ map.putValueOffset(utf8Key, offset, valueLength);
+ }
+
+ public String getMockKey(int index) {
+ return "Key" + index;
+ }
+
+ public String getMockValue(int index) {
+ return "Value" + (index + 10);
+ }
+
+ public ByteBuffer getMockValueAsBuffer(int index) {
+ String mockValue = getMockValue(index);
+ return toValueBuffer(mockValue);
+ }
+
+ public ByteBuffer toValueBuffer(String value) {
+ byte[] vBytes = value.getBytes(StandardCharsets.UTF_8);
+ return ByteBuffer.wrap(vBytes);
+ }
+
+ public void assertValueForKey(int index) {
+ String k = getMockKey(index);
+ ByteBuffer vBuf = map.get(k);
+
+ assertThat("Unexpected lookup value for key: " + k, vBuf, equalTo(getMockValueAsBuffer(index)));
+ }
+}
diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/ByteBufferMapMatcher.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/ByteBufferMapMatcher.java
new file mode 100644
index 000000000..967992b02
--- /dev/null
+++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/ByteBufferMapMatcher.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.reactivesocket.mimetypes.internal.cbor;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.mockito.ArgumentMatcher;
+
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public final class ByteBufferMapMatcher {
+
+ private ByteBufferMapMatcher() {
+ }
+
+ public static Matcher