├── .gitignore
├── .travis.yml
├── LICENSE
├── NOTICE.txt
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── com
│ └── spotify
│ └── netty4
│ ├── handler
│ └── codec
│ │ └── zmtp
│ │ ├── ZMTP10Protocol.java
│ │ ├── ZMTP10WireFormat.java
│ │ ├── ZMTP20Protocol.java
│ │ ├── ZMTP20WireFormat.java
│ │ ├── ZMTPCodec.java
│ │ ├── ZMTPConfig.java
│ │ ├── ZMTPDecoder.java
│ │ ├── ZMTPEncoder.java
│ │ ├── ZMTPEstimator.java
│ │ ├── ZMTPException.java
│ │ ├── ZMTPFramingDecoder.java
│ │ ├── ZMTPFramingEncoder.java
│ │ ├── ZMTPHandshake.java
│ │ ├── ZMTPHandshakeFailure.java
│ │ ├── ZMTPHandshakeSuccess.java
│ │ ├── ZMTPHandshaker.java
│ │ ├── ZMTPIdentityGenerator.java
│ │ ├── ZMTPLongIdentityGenerator.java
│ │ ├── ZMTPMessage.java
│ │ ├── ZMTPMessageDecoder.java
│ │ ├── ZMTPMessageEncoder.java
│ │ ├── ZMTPParsingException.java
│ │ ├── ZMTPProtocol.java
│ │ ├── ZMTPProtocols.java
│ │ ├── ZMTPSession.java
│ │ ├── ZMTPSocketType.java
│ │ ├── ZMTPUtils.java
│ │ ├── ZMTPVersion.java
│ │ ├── ZMTPWireFormat.java
│ │ ├── ZMTPWireFormats.java
│ │ └── ZMTPWriter.java
│ └── util
│ └── BatchFlusher.java
└── test
├── java
└── com
│ └── spotify
│ └── netty4
│ └── handler
│ └── codec
│ └── zmtp
│ ├── Buffers.java
│ ├── CodecBenchmark.java
│ ├── EndToEndTest.java
│ ├── Fragmenter.java
│ ├── FragmenterTest.java
│ ├── HandshakeTest.java
│ ├── ListenableFutureAdapter.java
│ ├── PipelineTester.java
│ ├── PipelineTests.java
│ ├── ProtocolViolationTests.java
│ ├── VerifyingDecoder.java
│ ├── ZMQIntegrationTest.java
│ ├── ZMTP10WireFormatTest.java
│ ├── ZMTPFramingEncoderTest.java
│ ├── ZMTPMessageDecoderTest.java
│ ├── ZMTPMessageTest.java
│ ├── ZMTPParserTest.java
│ ├── ZMTPSocket.java
│ ├── ZMTPWriterTest.java
│ └── benchmarks
│ ├── AsciiString.java
│ ├── CustomReqRepBenchmark.java
│ ├── ProgressMeter.java
│ ├── ReqRepBenchmark.java
│ └── ThroughputBenchmark.java
└── resources
└── logback.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | .idea
3 | netty-zmtp.iml
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 |
3 | jdk:
4 | - oraclejdk8
5 | - oraclejdk7
6 | - openjdk6
7 | - openjdk7
8 |
9 | sudo: false
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2012 Spotify AB
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/NOTICE.txt:
--------------------------------------------------------------------------------
1 | Netty ZMTP
2 | Copyright (c) 2012-2014 Spotify AB
3 |
4 | This product includes software developed at Spotify AB.
5 | http://www.spotify.com/
6 |
7 | This product includes modified software from and depends on 'Netty', a network
8 | communication framework in Java, which can be obtained at: http://netty.io/
9 |
10 | This product depends on 'JeroMQ', a communications framework, which can be
11 | obtained at: https://github.com/zeromq/jeromq
12 |
13 | This product depends on 'Junit', a testing framework for Java, which can be
14 | obtained at: http://junit.org/
15 |
16 | This product depends on 'mockito', a mocking framework for Java, which can be
17 | obtained at: https://code.google.com/p/mockito/
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Netty-zmtp
2 |
3 | **Note** This project has been discontinued.
4 |
5 | [](https://travis-ci.org/spotify/netty-zmtp)
6 |
7 | This is a ZeroMQ codec for Netty that aims to implement ZMTP, the ZeroMQ
8 | Message Transport Protocol versions 1.0 and 2.0 as specified in
9 | http://rfc.zeromq.org/spec:13 and http://rfc.zeromq.org/spec:15.
10 |
11 | This project is hosted on https://github.com/spotify/netty-zmtp/
12 |
13 | At Spotify we use ZeroMQ for a lot of the internal communication between
14 | services in the backend. As we implement more services on top of the JVM we
15 | felt the need for more control over the state of TCP connections, routing,
16 | message queue management, etc as well as getting better performance than seems
17 | to be currently possible with the JNI based JZMQ library.
18 |
19 | This project implements the ZMTP wire protocol but not the ZeroMQ API, meaning
20 | that it can be used to communicate with other peers using e.g. ZeroMQ (libzmq)
21 | but it's not a drop-in replacement for JZMQ like e.g. JeroMQ attempts to be.
22 | For an example of how a ZeroMQ socket equivalent might be implemented using
23 | the netty-zmtp codecs, see the `ZMTPSocket` class in the tests.
24 |
25 | We have successfully used these handlers to implement services capable of
26 | processing millions of messages per second.
27 |
28 | Currently this project targets Java 6+ and Netty 4.x. It does not have any
29 | native dependency on e.g. libzmq.
30 |
31 | ## Usage
32 |
33 | To use netty-zmtp, insert a `ZMTPCodec` instance into your channel pipeline.
34 |
35 | ```java
36 | ch.pipeline().addLast(ZMTPCodec.of(ROUTER));
37 | ```
38 |
39 | Upstream handlers will receive `ZMTPMessage` instances.
40 |
41 | ```java
42 | @Override
43 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
44 | final ZMTPMessage message = (ZMTPMessage) msg;
45 | // ...
46 | }
47 | ```
48 |
49 | Wait for the ZMTP handshake to complete before sending messages.
50 |
51 | ```java
52 | @Override
53 | public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt)
54 | throws Exception {
55 | if (evt instanceof ZMTPHandshakeSuccess) {
56 | // ...
57 | }
58 | }
59 | ```
60 |
61 | ### `pom.xml`
62 |
63 | ```xml
64 |
65 | com.spotify
66 | netty4-zmtp
67 | 0.4.1
68 |
69 | ```
70 |
71 | ## Performance
72 |
73 | Performance seems decent, with the throughput benchmark producing 7M+ messages/s throughput numbers
74 | on a recent laptop.
75 |
76 | For maximum throughput, look into using the `BatchFlusher` to opportunistically gather writes into
77 | fewer syscalls.
78 |
79 | Truly overhead conscientious users might want to look into implementing the `ZMTPEncoder` and
80 | `ZMTPDecoder` interfaces for eliminating the `ZMTPMessage` intermediary when reading/writing
81 | application messages.
82 |
83 | ## Benchmarks
84 |
85 | ### Preparation
86 |
87 | First compile and run tests:
88 |
89 | ```
90 | mvn clean test
91 | ```
92 |
93 | Fetch dependencies:
94 |
95 | ```
96 | mvn dependency:copy-dependencies
97 | ```
98 |
99 | Now benchmarks are ready to run.
100 |
101 | ### One-Way Throughput
102 |
103 | ```
104 | java -cp 'target/classes:target/test-classes:target/dependency/*' \
105 | com.spotify.netty4.handler.codec.zmtp.benchmarks.ThroughputBenchmark
106 | ```
107 |
108 | ```
109 | 1s: 3,393,219 messages/s. (total: 3,399,036)
110 | 2s: 6,595,711 messages/s. (total: 10,007,250)
111 | 3s: 7,158,176 messages/s. (total: 17,154,830)
112 | 4s: 7,313,554 messages/s. (total: 24,461,521)
113 | 5s: 7,294,709 messages/s. (total: 31,790,260)
114 | 6s: 7,282,308 messages/s. (total: 39,064,152)
115 | 7s: 7,294,591 messages/s. (total: 46,336,682)
116 | ```
117 |
118 |
119 | ### Req/Rep Throughput
120 |
121 |
122 | ```
123 | java -cp 'target/classes:target/test-classes:target/dependency/*' \
124 | com.spotify.netty4.handler.codec.zmtp.benchmarks.ReqRepBenchmark
125 | ```
126 |
127 | ```
128 | 1s: 444,848 requests/s. (total: 446,249)
129 | 2s: 1,251,304 requests/s. (total: 1,699,916)
130 | 3s: 1,241,569 requests/s. (total: 2,941,709)
131 | 4s: 1,365,408 requests/s. (total: 4,307,949)
132 | 5s: 1,379,640 requests/s. (total: 5,681,522)
133 | 6s: 1,379,048 requests/s. (total: 7,064,183)
134 | 7s: 1,377,180 requests/s. (total: 8,438,673)
135 | ```
136 |
137 | ### Req/Rep With Custom Encoder/Decoder Throughput
138 |
139 | ```
140 | java -cp 'target/classes:target/test-classes:target/dependency/*' \
141 | com.spotify.netty4.handler.codec.zmtp.benchmarks.CustomReqRepBenchmark
142 | ```
143 |
144 | ```
145 | 1s: 443,337 requests/s. 1.470 ms avg latency. (total: 445,512)
146 | 2s: 1,306,539 requests/s. 0.765 ms avg latency. (total: 1,747,153)
147 | 3s: 1,549,594 requests/s. 0.645 ms avg latency. (total: 3,303,268)
148 | 4s: 1,557,397 requests/s. 0.642 ms avg latency. (total: 4,859,727)
149 | 5s: 1,618,137 requests/s. 0.618 ms avg latency. (total: 6,472,114)
150 | 6s: 1,609,406 requests/s. 0.621 ms avg latency. (total: 8,084,958)
151 | 7s: 1,611,349 requests/s. 0.621 ms avg latency. (total: 9,692,777)
152 | 8s: 1,611,672 requests/s. 0.620 ms avg latency. (total: 11,306,988)
153 | ```
154 |
155 | ## Feedback
156 |
157 | There is an open Google group for general development and usage discussion
158 | available at https://groups.google.com/group/netty-zmtp
159 |
160 | We use the github issue tracker at https://github.com/spotify/netty-zmtp/issues
161 |
162 | ## License
163 |
164 | This software is licensed using the Apache 2.0 license. Details in the file
165 | LICENSE
166 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 | com.spotify
5 | netty4-zmtp
6 | 0.4.2-SNAPSHOT
7 | jar
8 |
9 | netty-zmtp
10 | ZMTP, the ZeroMQ Message Transport Protocol for Netty
11 | https://github.com/spotify/netty-zmtp
12 |
13 |
14 | org.sonatype.oss
15 | oss-parent
16 | 9
17 |
18 |
19 |
20 |
21 | The Apache Software License, Version 2.0
22 | http://www.apache.org/licenses/LICENSE-2.0.txt
23 | repo
24 |
25 |
26 |
27 |
28 | scm:git:https://github.com/spotify/netty-zmtp
29 | scm:git:git@github.com:spotify/netty-zmtp
30 | https://github.com/spotify/netty-zmtp
31 | HEAD
32 |
33 |
34 |
35 | UTF-8
36 | 1.6
37 | 1.6
38 |
39 |
40 |
41 |
42 | dano
43 | dano@spotify.com
44 | Daniel Norberg
45 |
46 |
47 |
48 |
49 |
50 | io.netty
51 | netty-all
52 | 4.0.34.Final
53 |
54 | provided
55 |
56 |
57 | com.google.code.findbugs
58 | jsr305
59 | 3.0.0
60 | provided
61 |
62 |
63 |
64 |
65 | org.zeromq
66 | jeromq
67 | 0.3.4
68 | test
69 |
70 |
71 | junit
72 | junit
73 | 4.12
74 | test
75 |
76 |
77 | org.mockito
78 | mockito-all
79 | 1.10.19
80 | test
81 |
82 |
83 | org.openjdk.jmh
84 | jmh-core
85 | 1.9.3
86 | test
87 |
88 |
89 | org.openjdk.jmh
90 | jmh-generator-annprocess
91 | 1.9.3
92 | test
93 |
94 |
95 | org.openjdk.jmh
96 | jmh-generator-reflection
97 | 1.9.3
98 | test
99 |
100 |
101 | com.google.guava
102 | guava
103 | 18.0
104 | test
105 |
106 |
107 | ch.qos.logback
108 | logback-classic
109 | 1.1.3
110 | test
111 |
112 |
113 | org.hamcrest
114 | hamcrest-library
115 | 1.3
116 | test
117 |
118 |
119 |
120 |
121 |
122 |
123 | org.apache.maven.plugins
124 | maven-compiler-plugin
125 | 3.3
126 |
127 | false
128 | 1.6
129 | 1.6
130 |
131 |
132 |
133 | org.apache.maven.plugins
134 | maven-release-plugin
135 | 2.5.2
136 |
137 | v@{project.version}
138 |
139 |
140 |
141 | org.apache.maven.scm
142 | maven-scm-provider-gitexe
143 | 1.9
144 |
145 |
146 |
147 |
148 | org.sonatype.plugins
149 | nexus-staging-maven-plugin
150 | 1.6.5
151 | true
152 |
153 | ossrh
154 | https://oss.sonatype.org/
155 | true
156 |
157 |
158 |
159 | org.apache.maven.plugins
160 | maven-enforcer-plugin
161 | 1.4
162 |
163 |
164 | enforce
165 |
166 |
167 |
168 |
169 |
170 |
171 | enforce
172 |
173 |
174 |
175 |
176 |
177 | org.apache.maven.plugins
178 | maven-surefire-plugin
179 | 2.18.1
180 |
181 | -Xmx1g -Dio.netty.leakDetectionLevel=paranoid
182 |
183 |
184 |
185 |
186 |
187 |
188 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTP10Protocol.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import java.nio.ByteBuffer;
20 |
21 | import io.netty.buffer.ByteBuf;
22 | import io.netty.buffer.Unpooled;
23 | import io.netty.channel.ChannelHandlerContext;
24 |
25 | import static com.spotify.netty4.handler.codec.zmtp.ZMTP10WireFormat.readIdentity;
26 | import static com.spotify.netty4.handler.codec.zmtp.ZMTP10WireFormat.writeGreeting;
27 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPVersion.ZMTP10;
28 |
29 | class ZMTP10Protocol implements ZMTPProtocol {
30 |
31 | @Override
32 | public ZMTPHandshaker handshaker(final ZMTPConfig config) {
33 | return new Handshaker(config.localIdentity());
34 | }
35 |
36 | static class Handshaker implements ZMTPHandshaker {
37 |
38 | private final ByteBuffer localIdentity;
39 |
40 | Handshaker(final ByteBuffer localIdentity) {
41 | this.localIdentity = localIdentity;
42 | }
43 |
44 | @Override
45 | public ByteBuf greeting() {
46 | final ByteBuf out = Unpooled.buffer();
47 | writeGreeting(out, localIdentity);
48 | return out;
49 | }
50 |
51 | @Override
52 | public ZMTPHandshake handshake(final ByteBuf in, final ChannelHandlerContext ctx)
53 | throws ZMTPException {
54 | final ByteBuffer remoteIdentity = readIdentity(in);
55 | assert remoteIdentity != null;
56 | return ZMTPHandshake.of(ZMTP10, remoteIdentity);
57 | }
58 | }
59 |
60 | @Override
61 | public String toString() {
62 | return "ZMTP/1.0";
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTP10WireFormat.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import java.nio.ByteBuffer;
20 |
21 | import io.netty.buffer.ByteBuf;
22 |
23 | class ZMTP10WireFormat implements ZMTPWireFormat {
24 |
25 | private static final byte FINAL_FLAG = 0x0;
26 | private static final byte MORE_FLAG = 0x1;
27 |
28 | /**
29 | * Read the remote identity octets from a ZMTP/1.0 greeting.
30 | */
31 | static ByteBuffer readIdentity(final ByteBuf buffer) throws ZMTPParsingException {
32 | final long length = readLength(buffer);
33 | if (length == -1) {
34 | return null;
35 | }
36 | final long identityLength = length - 1;
37 | if (identityLength < 0 || identityLength > 255) {
38 | throw new ZMTPParsingException("Bad remote identity length: " + length);
39 | }
40 |
41 | // skip the flags byte
42 | buffer.skipBytes(1);
43 |
44 | final byte[] identity = new byte[(int) identityLength];
45 | buffer.readBytes(identity);
46 | return ByteBuffer.wrap(identity);
47 | }
48 |
49 | /**
50 | * Read a ZMTP/1.0 frame length.
51 | */
52 | static long readLength(final ByteBuf in) {
53 | if (in.readableBytes() < 1) {
54 | return -1;
55 | }
56 | long size = in.readByte() & 0xFF;
57 | if (size == 0xFF) {
58 | if (in.readableBytes() < 8) {
59 | return -1;
60 | }
61 | size = in.readLong();
62 | }
63 |
64 | return size;
65 | }
66 |
67 | /**
68 | * Write a ZMTP/1.0 frame length.
69 | *
70 | * @param length The length.
71 | * @param out Target buffer.
72 | */
73 | static void writeLength(final long length, final ByteBuf out) {
74 | writeLength(out, length, length);
75 | }
76 |
77 | /**
78 | * Write a ZMTP/1.0 frame length.
79 | *
80 | * @param out Target buffer.
81 | * @param maxLength The maximum length of the field.
82 | * @param length The length.
83 | */
84 | static void writeLength(final ByteBuf out, final long maxLength, final long length) {
85 | if (maxLength < 255) {
86 | out.writeByte((byte) length);
87 | } else {
88 | out.writeByte(0xFF);
89 | out.writeLong(length);
90 | }
91 | }
92 |
93 | /**
94 | * Write a ZMTP/1.0 greeting.
95 | *
96 | * @param out Target buffer.
97 | * @param identity Socket identity.
98 | */
99 | static void writeGreeting(final ByteBuf out, final ByteBuffer identity) {
100 | writeLength(identity.remaining() + 1, out);
101 | out.writeByte(0x00);
102 | out.writeBytes(identity.duplicate());
103 | }
104 |
105 | @Override
106 | public Header header() {
107 | return new ZMTP10Header();
108 | }
109 |
110 | @Override
111 | public int frameLength(final int content) {
112 | if (content + 1 < 255) {
113 | return 1 + 1 + content;
114 | } else {
115 | return 1 + 8 + 1 + content;
116 | }
117 | }
118 |
119 | static class ZMTP10Header implements Header {
120 |
121 | int maxLength;
122 | int length;
123 | boolean more;
124 |
125 | @Override
126 | public void set(final int maxLength, final int length, final boolean more) {
127 | this.maxLength = maxLength;
128 | this.length = length;
129 | this.more = more;
130 | }
131 |
132 | @Override
133 | public void write(final ByteBuf out) {
134 | writeLength(out, maxLength + 1, length + 1);
135 | out.writeByte(more ? MORE_FLAG : FINAL_FLAG);
136 | }
137 |
138 | @Override
139 | public boolean read(final ByteBuf in) throws ZMTPParsingException {
140 | final long len = readLength(in);
141 | if (len == -1) {
142 | // Wait for more data
143 | return false;
144 | }
145 |
146 | if (len == 0) {
147 | throw new ZMTPParsingException("Received frame with zero length");
148 | }
149 |
150 | if (in.readableBytes() < 1) {
151 | // Wait for more data
152 | return false;
153 | }
154 |
155 | length = (int) len - 1;
156 | more = (in.readByte() & MORE_FLAG) == MORE_FLAG;
157 |
158 | return true;
159 | }
160 |
161 | @Override
162 | public long length() {
163 | return length;
164 | }
165 |
166 | @Override
167 | public boolean more() {
168 | return more;
169 | }
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTP20Protocol.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import com.spotify.netty4.handler.codec.zmtp.ZMTP20WireFormat.Greeting;
20 |
21 | import java.nio.ByteBuffer;
22 |
23 | import io.netty.buffer.ByteBuf;
24 | import io.netty.buffer.Unpooled;
25 | import io.netty.channel.ChannelHandlerContext;
26 |
27 | import static com.spotify.netty4.handler.codec.zmtp.ZMTP20WireFormat.detectProtocolVersion;
28 | import static com.spotify.netty4.handler.codec.zmtp.ZMTP20WireFormat.readGreeting;
29 | import static com.spotify.netty4.handler.codec.zmtp.ZMTP20WireFormat.readGreetingBody;
30 | import static com.spotify.netty4.handler.codec.zmtp.ZMTP20WireFormat.writeGreetingBody;
31 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPUtils.checkNotNull;
32 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPVersion.ZMTP10;
33 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPVersion.ZMTP20;
34 |
35 | class ZMTP20Protocol implements ZMTPProtocol {
36 |
37 | @Override
38 | public ZMTPHandshaker handshaker(final ZMTPConfig config) {
39 | return new Handshaker(config.socketType(), config.localIdentity(), config.interop());
40 | }
41 |
42 | static class Handshaker implements ZMTPHandshaker {
43 |
44 | private final ZMTPSocketType socketType;
45 | private final ByteBuffer identity;
46 | private final boolean interop;
47 |
48 | private boolean splitHandshake;
49 |
50 | Handshaker(final ZMTPSocketType socketType, final ByteBuffer identity, final boolean interop) {
51 | this.socketType = checkNotNull(socketType, "ZMTP/2.0 requires a socket type");
52 | this.identity = checkNotNull(identity, "identity");
53 | this.interop = interop;
54 | }
55 |
56 | @Override
57 | public ByteBuf greeting() {
58 | final ByteBuf out = Unpooled.buffer();
59 | if (interop) {
60 | ZMTP20WireFormat.writeCompatSignature(out, identity);
61 | } else {
62 | ZMTP20WireFormat.writeGreeting(out, socketType, identity);
63 | }
64 | return out;
65 | }
66 |
67 | @Override
68 | public ZMTPHandshake handshake(final ByteBuf in, final ChannelHandlerContext ctx)
69 | throws ZMTPException {
70 | if (splitHandshake) {
71 | final Greeting remoteGreeting = readGreetingBody(in);
72 | if (remoteGreeting.revision() < 1) {
73 | throw new ZMTPException("Bad ZMTP revision: " + remoteGreeting.revision());
74 | }
75 | return ZMTPHandshake.of(ZMTP20, remoteGreeting.identity(), remoteGreeting.socketType());
76 | }
77 |
78 | if (interop) {
79 | final int mark = in.readerIndex();
80 | final ZMTPVersion version = detectProtocolVersion(in);
81 | switch (version) {
82 | case ZMTP10:
83 | in.readerIndex(mark);
84 | // when a ZMTP/1.0 peer is detected, just send the identity bytes. Together
85 | // with the compatibility signature it makes for a valid ZMTP/1.0 greeting.
86 | ctx.writeAndFlush(Unpooled.wrappedBuffer(identity));
87 | final ByteBuffer remoteIdentity = ZMTP10WireFormat.readIdentity(in);
88 | assert remoteIdentity != null;
89 | return ZMTPHandshake.of(ZMTP10, remoteIdentity);
90 | case ZMTP20:
91 | splitHandshake = true;
92 | final ByteBuf out = Unpooled.buffer();
93 | writeGreetingBody(out, socketType, identity);
94 | ctx.writeAndFlush(out);
95 | return null;
96 | default:
97 | throw new ZMTPException("Unknown ZMTP version: " + version);
98 | }
99 | } else {
100 | final Greeting remoteGreeting = readGreeting(in);
101 | return ZMTPHandshake.of(ZMTP20, remoteGreeting.identity(), remoteGreeting.socketType());
102 | }
103 | }
104 |
105 | }
106 |
107 | @Override
108 | public String toString() {
109 | return "ZMTP/2.0";
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPCodec.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 |
20 | import java.nio.ByteBuffer;
21 | import java.nio.channels.ClosedChannelException;
22 | import java.util.List;
23 |
24 | import io.netty.buffer.ByteBuf;
25 | import io.netty.channel.Channel;
26 | import io.netty.channel.ChannelHandler;
27 | import io.netty.channel.ChannelHandlerContext;
28 | import io.netty.channel.CombinedChannelDuplexHandler;
29 | import io.netty.handler.codec.ReplayingDecoder;
30 |
31 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPUtils.checkNotNull;
32 |
33 | /**
34 | * A ZMTP codec for Netty.
35 | *
36 | * Note: A single codec instance is not {@link Sharable} among multiple {@link Channel} instances.
37 | */
38 | public class ZMTPCodec extends ReplayingDecoder {
39 |
40 | private final ZMTPSession session;
41 | private final ZMTPHandshaker handshaker;
42 |
43 | private final ZMTPConfig config;
44 |
45 | public ZMTPCodec(final ZMTPSession session) {
46 | this.config = session.config();
47 | this.session = checkNotNull(session, "session");
48 | this.handshaker = config.protocol().handshaker(config);
49 | }
50 |
51 | /**
52 | * Get the {@link ZMTPSession} for this codec.
53 | */
54 | public ZMTPSession session() {
55 | return session;
56 | }
57 |
58 | @Override
59 | public void channelActive(final ChannelHandlerContext ctx) throws Exception {
60 | super.channelActive(ctx);
61 | ctx.writeAndFlush(handshaker.greeting());
62 | }
63 |
64 | @Override
65 | public void channelInactive(final ChannelHandlerContext ctx) throws Exception {
66 | super.channelInactive(ctx);
67 | if (!session.handshakeFuture().isDone()) {
68 | session.handshakeFailure(new ClosedChannelException());
69 | ctx.fireUserEventTriggered(new ZMTPHandshakeFailure(session));
70 | }
71 | }
72 |
73 | @Override
74 | protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final List out)
75 | throws Exception {
76 |
77 | // Discard input if handshake failed. It is expected that the user will close the channel.
78 | if (session.handshakeFuture().isDone()) {
79 | assert !session.handshakeFuture().isSuccess();
80 | in.skipBytes(in.readableBytes());
81 | }
82 |
83 | // Shake hands
84 | final ZMTPHandshake handshake;
85 | try {
86 | handshake = handshaker.handshake(in, ctx);
87 | if (handshake == null) {
88 | // Handshake is not yet done. Await more input.
89 | return;
90 | }
91 | } catch (Exception e) {
92 | session.handshakeFailure(e);
93 | ctx.fireUserEventTriggered(new ZMTPHandshakeFailure(session));
94 | throw e;
95 | }
96 |
97 | // Handshake is done.
98 | session.handshakeSuccess(handshake);
99 |
100 | // Replace this handler with the framing encoder and decoder
101 | if (actualReadableBytes() > 0) {
102 | out.add(in.readBytes(actualReadableBytes()));
103 | }
104 | final ZMTPDecoder decoder = config.decoder().decoder(session);
105 | final ZMTPEncoder encoder = config.encoder().encoder(session);
106 | final ZMTPWireFormat wireFormat = ZMTPWireFormats.wireFormat(session.negotiatedVersion());
107 | final ChannelHandler handler =
108 | new CombinedChannelDuplexHandler(
109 | new ZMTPFramingDecoder(wireFormat, decoder),
110 | new ZMTPFramingEncoder(wireFormat, encoder));
111 | ctx.pipeline().replace(this, ctx.name(), handler);
112 |
113 | // Tell the user that the handshake is complete
114 | ctx.fireUserEventTriggered(new ZMTPHandshakeSuccess(session, handshake));
115 | }
116 |
117 | public static Builder builder() {
118 | return new Builder();
119 | }
120 |
121 | public static ZMTPCodec from(final ZMTPConfig config) {
122 | return new ZMTPCodec(ZMTPSession.from(config));
123 | }
124 |
125 | public static ZMTPCodec of(final ZMTPSocketType socketType) {
126 | return builder().socketType(socketType).build();
127 | }
128 |
129 | public static ZMTPCodec from(final ZMTPSession session) {
130 | return new ZMTPCodec(session);
131 | }
132 |
133 | public static class Builder {
134 |
135 | private final ZMTPConfig.Builder config = ZMTPConfig.builder();
136 |
137 | private Builder() {
138 | }
139 |
140 | public Builder protocol(final ZMTPProtocol protocol) {
141 | config.protocol(protocol);
142 | return this;
143 | }
144 |
145 | public Builder interop(final boolean interop) {
146 | config.interop(interop);
147 | return this;
148 | }
149 |
150 | public Builder socketType(final ZMTPSocketType socketType) {
151 | config.socketType(socketType);
152 | return this;
153 | }
154 |
155 | public Builder localIdentity(final CharSequence localIdentity) {
156 | config.localIdentity(localIdentity);
157 | return this;
158 | }
159 |
160 | public Builder localIdentity(final byte[] localIdentity) {
161 | config.localIdentity(localIdentity);
162 | return this;
163 | }
164 |
165 | public Builder localIdentity(final ByteBuffer localIdentity) {
166 | config.localIdentity(localIdentity);
167 | return this;
168 | }
169 |
170 | public Builder encoder(final ZMTPEncoder.Factory encoder) {
171 | config.encoder(encoder);
172 | return this;
173 | }
174 |
175 | public Builder encoder(final Class extends ZMTPEncoder> encoder) {
176 | config.encoder(encoder);
177 | return this;
178 | }
179 |
180 | public Builder decoder(final ZMTPDecoder.Factory decoder) {
181 | config.decoder(decoder);
182 | return this;
183 | }
184 |
185 | public Builder decoder(final Class extends ZMTPDecoder> decoder) {
186 | config.decoder(decoder);
187 | return this;
188 | }
189 |
190 | public Builder identityGenerator(final ZMTPIdentityGenerator identityGenerator) {
191 | config.identityGenerator(identityGenerator);
192 | return this;
193 | }
194 |
195 | public ZMTPCodec build() {
196 | return ZMTPCodec.from(config.build());
197 | }
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPConfig.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import java.lang.reflect.Constructor;
20 | import java.lang.reflect.InvocationTargetException;
21 | import java.nio.ByteBuffer;
22 |
23 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPUtils.checkNotNull;
24 | import static io.netty.util.CharsetUtil.UTF_8;
25 |
26 | /**
27 | * Configuration for a ZMTP session and {@link ZMTPCodec}. Can be reused and shared across multiple
28 | * channel instances.
29 | */
30 | public class ZMTPConfig {
31 |
32 | public static final ByteBuffer ANONYMOUS = ByteBuffer.allocate(0).asReadOnlyBuffer();
33 |
34 | private final ZMTPProtocol protocol;
35 | private final boolean interop;
36 | private final ZMTPSocketType socketType;
37 | private final ByteBuffer localIdentity;
38 | private final ZMTPEncoder.Factory encoder;
39 | private final ZMTPDecoder.Factory decoder;
40 | private final ZMTPIdentityGenerator identityGenerator;
41 |
42 | private ZMTPConfig(final Builder builder) {
43 | this.protocol = checkNotNull(builder.protocol, "protocol");
44 | this.interop = checkNotNull(builder.interop, "interop");
45 | this.socketType = checkNotNull(builder.socketType, "socketType");
46 | this.localIdentity = checkNotNull(builder.localIdentity, "localIdentity");
47 | this.encoder = checkNotNull(builder.encoder, "encoder");
48 | this.decoder = checkNotNull(builder.decoder, "decoder");
49 | this.identityGenerator = checkNotNull(builder.identityGenerator, "identityGenerator");
50 | }
51 |
52 | public ZMTPProtocol protocol() {
53 | return protocol;
54 | }
55 |
56 | public boolean interop() {
57 | return interop;
58 | }
59 |
60 | public ZMTPSocketType socketType() {
61 | return socketType;
62 | }
63 |
64 | public ByteBuffer localIdentity() {
65 | return localIdentity;
66 | }
67 |
68 | public ZMTPEncoder.Factory encoder() {
69 | return encoder;
70 | }
71 |
72 | public ZMTPDecoder.Factory decoder() {
73 | return decoder;
74 | }
75 |
76 | public ZMTPIdentityGenerator identityGenerator() {
77 | return identityGenerator;
78 | }
79 |
80 | public Builder toBuilder() {
81 | return new Builder(this);
82 | }
83 |
84 | public static Builder builder() {
85 | return new Builder();
86 | }
87 |
88 | public static class Builder {
89 |
90 | private ZMTPProtocol protocol = ZMTPProtocols.ZMTP20;
91 | private boolean interop = true;
92 | private ZMTPSocketType socketType;
93 | private ByteBuffer localIdentity = ANONYMOUS;
94 | private ZMTPEncoder.Factory encoder = ZMTPMessageEncoder.FACTORY;
95 | private ZMTPDecoder.Factory decoder = ZMTPMessageDecoder.FACTORY;
96 | private ZMTPIdentityGenerator identityGenerator = ZMTPLongIdentityGenerator.GLOBAL;
97 |
98 | private Builder() {
99 | }
100 |
101 | private Builder(final ZMTPConfig config) {
102 | this.protocol = config.protocol;
103 | this.interop = config.interop;
104 | this.socketType = config.socketType;
105 | this.localIdentity = config.localIdentity;
106 | this.encoder = config.encoder;
107 | this.decoder = config.decoder;
108 |
109 | }
110 |
111 | public Builder protocol(final ZMTPProtocol protocol) {
112 | this.protocol = protocol;
113 | return this;
114 | }
115 |
116 | public Builder interop(final boolean interop) {
117 | this.interop = interop;
118 | return this;
119 | }
120 |
121 | public Builder socketType(final ZMTPSocketType socketType) {
122 | this.socketType = socketType;
123 | return this;
124 | }
125 |
126 | public Builder localIdentity(final CharSequence localIdentity) {
127 | return localIdentity(UTF_8.encode(localIdentity.toString()));
128 | }
129 |
130 | public Builder localIdentity(final byte[] localIdentity) {
131 | return localIdentity(ByteBuffer.wrap(localIdentity));
132 | }
133 |
134 | public Builder localIdentity(final ByteBuffer localIdentity) {
135 | this.localIdentity = localIdentity;
136 | return this;
137 | }
138 |
139 | public Builder encoder(final ZMTPEncoder.Factory encoder) {
140 | this.encoder = encoder;
141 | return this;
142 | }
143 |
144 | public Builder encoder(final Class extends ZMTPEncoder> encoder) {
145 | return encoder(new ZMTPEncoderClassFactory(encoder));
146 | }
147 |
148 | public Builder decoder(final ZMTPDecoder.Factory decoder) {
149 | this.decoder = decoder;
150 | return this;
151 | }
152 |
153 | public Builder decoder(final Class extends ZMTPDecoder> decoder) {
154 | return decoder(new ZMTPDecoderClassFactory(decoder));
155 | }
156 |
157 | public Builder identityGenerator(final ZMTPIdentityGenerator identityGenerator) {
158 | this.identityGenerator = identityGenerator;
159 | return this;
160 | }
161 |
162 | public ZMTPConfig build() {
163 | return new ZMTPConfig(this);
164 | }
165 |
166 | }
167 |
168 | @Override
169 | public String toString() {
170 | return "ZMTPConfig{" +
171 | "protocol=" + protocol +
172 | ", interop=" + interop +
173 | ", socketType=" + socketType +
174 | ", localIdentity=" + localIdentity +
175 | ", encoder=" + encoder +
176 | ", decoder=" + decoder +
177 | '}';
178 | }
179 |
180 | private static class ZMTPEncoderClassFactory implements ZMTPEncoder.Factory {
181 |
182 | private final Constructor extends ZMTPEncoder> constructor;
183 |
184 | public ZMTPEncoderClassFactory(final Class extends ZMTPEncoder> encoder) {
185 | checkNotNull(encoder, "encoder");
186 | try {
187 | constructor = encoder.getDeclaredConstructor();
188 | } catch (NoSuchMethodException e) {
189 | throw new IllegalArgumentException("Class must have default constructor: " + encoder);
190 | }
191 | if (!constructor.isAccessible()) {
192 | constructor.setAccessible(true);
193 | }
194 | }
195 |
196 | @Override
197 | public ZMTPEncoder encoder(final ZMTPSession session) {
198 | try {
199 | return constructor.newInstance();
200 | } catch (InstantiationException e) {
201 | throw new RuntimeException(e);
202 | } catch (IllegalAccessException e) {
203 | throw new RuntimeException(e);
204 | } catch (InvocationTargetException e) {
205 | throw new RuntimeException(e);
206 | }
207 | }
208 |
209 | @Override
210 | public String toString() {
211 | return "ZMTPEncoderClassFactory{" +
212 | "constructor=" + constructor +
213 | '}';
214 | }
215 | }
216 |
217 | private static class ZMTPDecoderClassFactory implements ZMTPDecoder.Factory {
218 |
219 | private final Constructor extends ZMTPDecoder> constructor;
220 |
221 | public ZMTPDecoderClassFactory(final Class extends ZMTPDecoder> decoder) {
222 | checkNotNull(decoder, "decoder");
223 | try {
224 | constructor = decoder.getDeclaredConstructor();
225 | } catch (NoSuchMethodException e) {
226 | throw new IllegalArgumentException("Class must have default constructor: " + decoder);
227 | }
228 | if (!constructor.isAccessible()) {
229 | constructor.setAccessible(true);
230 | }
231 | }
232 |
233 | @Override
234 | public ZMTPDecoder decoder(final ZMTPSession session) {
235 | try {
236 | return constructor.newInstance();
237 | } catch (InstantiationException e) {
238 | throw new RuntimeException(e);
239 | } catch (IllegalAccessException e) {
240 | throw new RuntimeException(e);
241 | } catch (InvocationTargetException e) {
242 | throw new RuntimeException(e);
243 | }
244 | }
245 |
246 | @Override
247 | public String toString() {
248 | return "ZMTPDecoderClassFactory{" +
249 | "constructor=" + constructor +
250 | '}';
251 | }
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPDecoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import java.io.Closeable;
20 | import java.util.List;
21 |
22 | import io.netty.buffer.ByteBuf;
23 | import io.netty.channel.ChannelHandlerContext;
24 |
25 | /**
26 | * A streaming decoder that takes parsed ZMTP frame headers and raw content and (optionally)
27 | * produces some output.
28 | */
29 | public interface ZMTPDecoder extends Closeable {
30 |
31 | /**
32 | * Start a new ZMTP frame.
33 | *
34 | * @param ctx The {@link ChannelHandlerContext} where this decoder is used.
35 | * @param length The total length in bytes of the frame content.
36 | * @param more {@code true} if there are additional frames following this one in the current
37 | * ZMTP message, {@code false otherwise.}
38 | * @param out {@link List} to which decoded messages should be added.
39 | */
40 | void header(final ChannelHandlerContext ctx, final long length, boolean more,
41 | final List out);
42 |
43 | /**
44 | * Read ZMTP frame content. Called repeatedly, at least once, per frame until all of the frame
45 | * content data has been read.
46 | *
47 | * @param ctx The {@link ChannelHandlerContext} where this decoder is used.
48 | * @param data The raw ZMTP frame content.
49 | * @param out {@link List} to which decoded messages should be added.
50 | */
51 | void content(final ChannelHandlerContext ctx, ByteBuf data, final List out);
52 |
53 | /**
54 | * End the ZMTP message. Called once after {@link #header} has been called with {@code more ==
55 | * false}.
56 | *
57 | * @param ctx The {@link ChannelHandlerContext} where this decoder is used.
58 | * @param out {@link List} to which decoded messages should be added.
59 | */
60 | void finish(final ChannelHandlerContext ctx, final List out);
61 |
62 | /**
63 | * Tear down the decoder and release e.g. retained {@link ByteBuf}s. May be called mid-message.
64 | */
65 | @Override
66 | void close();
67 |
68 | /**
69 | * Creates {@link ZMTPDecoder} instances.
70 | */
71 | interface Factory {
72 |
73 | /**
74 | * Create a {@link ZMTPDecoder} for a {@link ZMTPSession};
75 | */
76 | ZMTPDecoder decoder(ZMTPSession session);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPEncoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2014 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import java.io.Closeable;
20 |
21 | import io.netty.buffer.ByteBuf;
22 |
23 | /**
24 | * An encoder that takes implementation defined messages and writes a stream of ZMTP frames.
25 | */
26 | public interface ZMTPEncoder extends Closeable {
27 |
28 | /**
29 | * Estimate ZMTP output for the {@code message} using a {@link ZMTPEstimator}. Called before
30 | * {@link #encode}.
31 | *
32 | * @param message The message to be estimated.
33 | * @param estimator The {@link ZMTPEstimator} to use.
34 | */
35 | void estimate(Object message, ZMTPEstimator estimator);
36 |
37 | /**
38 | * Write ZMTP output for the {@code message} using the {@link ZMTPWriter}. Called after {@link
39 | * #estimate}.
40 | *
41 | * @param message The message to write.
42 | * @param writer The {@link ZMTPWriter} to use.
43 | */
44 | void encode(Object message, ZMTPWriter writer);
45 |
46 | /**
47 | * Tear down the encoder and release e.g. retained {@link ByteBuf}s.
48 | */
49 | @Override
50 | void close();
51 |
52 | /**
53 | * Creates {@link ZMTPEncoder} instances.
54 | */
55 | interface Factory {
56 |
57 | /**
58 | * Create a {@link ZMTPEncoder} for a {@link ZMTPSession};
59 | */
60 | ZMTPEncoder encoder(ZMTPSession session);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPEstimator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | public class ZMTPEstimator {
20 |
21 | private int size;
22 |
23 | private ZMTPWireFormat wireFormat;
24 |
25 | ZMTPEstimator(final ZMTPWireFormat wireFormat) {
26 | this.wireFormat = wireFormat;
27 | }
28 |
29 | public void reset() {
30 | size = 0;
31 | }
32 |
33 | public void frame(final int size) {
34 | this.size += wireFormat.frameLength(size);
35 | }
36 |
37 | public int size() {
38 | return size;
39 | }
40 |
41 | static ZMTPEstimator create(final ZMTPVersion version) {
42 | return new ZMTPEstimator(ZMTPWireFormats.wireFormat(version));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2013 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | /**
20 | * Base for all checked Netty ZMTP exceptions.
21 | */
22 | public class ZMTPException extends Exception {
23 |
24 | public ZMTPException() {
25 | }
26 |
27 | public ZMTPException(final String message) {
28 | super(message);
29 | }
30 |
31 | public ZMTPException(final String message, final Throwable exception) {
32 | super(message, exception);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPFramingDecoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2013 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import java.util.List;
20 |
21 | import io.netty.buffer.ByteBuf;
22 | import io.netty.channel.ChannelHandlerContext;
23 | import io.netty.handler.codec.ByteToMessageDecoder;
24 |
25 | import static java.lang.Math.min;
26 |
27 | /**
28 | * Netty ZMTP decoder.
29 | */
30 | class ZMTPFramingDecoder extends ByteToMessageDecoder {
31 |
32 | private final ZMTPDecoder decoder;
33 | private final ZMTPWireFormat.Header header;
34 |
35 | private long remaining;
36 | private boolean headerParsed;
37 |
38 | public ZMTPFramingDecoder(final ZMTPWireFormat wireFormat, final ZMTPDecoder decoder) {
39 | this.header = wireFormat.header();
40 | this.decoder = decoder;
41 | }
42 |
43 | @Override
44 | protected void handlerRemoved0(final ChannelHandlerContext ctx) {
45 | decoder.close();
46 | }
47 |
48 | @Override
49 | protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final List out)
50 | throws ZMTPParsingException {
51 | while (in.isReadable()) {
52 | if (!headerParsed) {
53 | final int mark = in.readerIndex();
54 | headerParsed = header.read(in);
55 | if (!headerParsed) {
56 | // Wait for more data
57 | in.readerIndex(mark);
58 | return;
59 | }
60 | decoder.header(ctx, header.length(), header.more(), out);
61 | remaining = header.length();
62 | }
63 |
64 | final int writerMark = in.writerIndex();
65 | final int n = (int) min(remaining, in.readableBytes());
66 | final int readerMark = in.readerIndex();
67 | in.writerIndex(readerMark + n);
68 | decoder.content(ctx, in, out);
69 | in.writerIndex(writerMark);
70 | final int read = in.readerIndex() - readerMark;
71 | remaining -= read;
72 | if (remaining > 0) {
73 | // Wait for more data
74 | return;
75 | }
76 | if (!header.more()) {
77 | decoder.finish(ctx, out);
78 | }
79 | headerParsed = false;
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPFramingEncoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2013 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 |
20 | import java.util.ArrayList;
21 | import java.util.List;
22 |
23 | import io.netty.buffer.ByteBuf;
24 | import io.netty.channel.Channel;
25 | import io.netty.channel.ChannelHandlerContext;
26 | import io.netty.channel.ChannelOutboundHandlerAdapter;
27 | import io.netty.channel.ChannelPromise;
28 | import io.netty.channel.DefaultChannelPromise;
29 | import io.netty.util.ReferenceCountUtil;
30 |
31 | /**
32 | * Netty ZMTP encoder.
33 | */
34 | class ZMTPFramingEncoder extends ChannelOutboundHandlerAdapter {
35 |
36 | private final ZMTPEncoder encoder;
37 |
38 | private final List messages = new ArrayList();
39 | private final List promises = new ArrayList();
40 | private ZMTPWriter writer;
41 | private ZMTPEstimator estimator;
42 |
43 | ZMTPFramingEncoder(final ZMTPSession session, final ZMTPEncoder encoder) {
44 | if (session == null) {
45 | throw new NullPointerException("session");
46 | }
47 | if (encoder == null) {
48 | throw new NullPointerException("encoder");
49 | }
50 | this.encoder = encoder;
51 | this.writer = ZMTPWriter.create(session.negotiatedVersion());
52 | this.estimator = ZMTPEstimator.create(session.negotiatedVersion());
53 | }
54 |
55 | public ZMTPFramingEncoder(final ZMTPWireFormat wireFormat, final ZMTPEncoder encoder) {
56 | if (wireFormat == null) {
57 | throw new NullPointerException("wireFormat");
58 | }
59 | if (encoder == null) {
60 | throw new NullPointerException("encoder");
61 | }
62 | this.encoder = encoder;
63 | this.writer = new ZMTPWriter(wireFormat);
64 | this.estimator = new ZMTPEstimator(wireFormat);
65 | }
66 |
67 | @Override
68 | public void handlerRemoved(final ChannelHandlerContext ctx) {
69 | encoder.close();
70 | }
71 |
72 | @Override
73 | public void write(final ChannelHandlerContext ctx, final Object msg,
74 | final ChannelPromise promise) {
75 | messages.add(msg);
76 | promises.add(promise);
77 | }
78 |
79 | @Override
80 | public void flush(final ChannelHandlerContext ctx) throws Exception {
81 | if (messages == null) {
82 | return;
83 | }
84 | estimator.reset();
85 | for (final Object message : messages) {
86 | encoder.estimate(message, estimator);
87 | }
88 | final ByteBuf output = ctx.alloc().buffer(estimator.size());
89 | writer.reset(output);
90 | for (final Object message : messages) {
91 | encoder.encode(message, writer);
92 | ReferenceCountUtil.release(message);
93 | }
94 | final ChannelPromise aggregate = new AggregatePromise(ctx.channel(), promises);
95 | messages.clear();
96 | promises.clear();
97 | ctx.write(output, aggregate);
98 | ctx.flush();
99 | }
100 |
101 | private static class AggregatePromise extends DefaultChannelPromise {
102 |
103 | private final ChannelPromise[] promises;
104 |
105 | private AggregatePromise(final Channel channel,
106 | final List promises) {
107 | super(channel);
108 | this.promises = promises.toArray(new ChannelPromise[promises.size()]);
109 | }
110 |
111 | @Override
112 | public ChannelPromise setSuccess(final Void result) {
113 | super.setSuccess(result);
114 | for (final ChannelPromise promise : promises) {
115 | promise.setSuccess(result);
116 | }
117 | return this;
118 | }
119 |
120 | @Override
121 | public boolean trySuccess() {
122 | final boolean result = super.trySuccess();
123 | for (final ChannelPromise promise : promises) {
124 | promise.trySuccess();
125 | }
126 | return result;
127 | }
128 |
129 | @Override
130 | public ChannelPromise setFailure(final Throwable cause) {
131 | super.setFailure(cause);
132 | for (final ChannelPromise promise : promises) {
133 | promise.setFailure(cause);
134 | }
135 | return this;
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPHandshake.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import java.nio.ByteBuffer;
20 |
21 | import javax.annotation.Nullable;
22 |
23 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPUtils.checkNotNull;
24 |
25 | public class ZMTPHandshake {
26 |
27 | private final ZMTPVersion negotiatedVersion;
28 | private final ByteBuffer remoteIdentity;
29 | private final ZMTPSocketType remoteSocketType;
30 |
31 | private ZMTPHandshake(final ZMTPVersion negotiatedVersion,
32 | final ByteBuffer remoteIdentity, final ZMTPSocketType remoteSocketType) {
33 | this.negotiatedVersion = checkNotNull(negotiatedVersion, "negotiatedVersion");
34 | this.remoteIdentity = checkNotNull(remoteIdentity, "remoteIdentity");
35 | this.remoteSocketType = remoteSocketType;
36 | }
37 |
38 | public ZMTPVersion negotiatedVersion() {
39 | return negotiatedVersion;
40 | }
41 |
42 | public ByteBuffer remoteIdentity() {
43 | return remoteIdentity.asReadOnlyBuffer();
44 | }
45 |
46 | @Nullable
47 | public ZMTPSocketType remoteSocketType() {
48 | return remoteSocketType;
49 | }
50 |
51 | @Override
52 | public boolean equals(final Object o) {
53 | if (this == o) { return true; }
54 | if (o == null || getClass() != o.getClass()) { return false; }
55 |
56 | final ZMTPHandshake that = (ZMTPHandshake) o;
57 |
58 | if (negotiatedVersion != that.negotiatedVersion) { return false; }
59 | if (remoteIdentity != null ? !remoteIdentity.equals(that.remoteIdentity)
60 | : that.remoteIdentity != null) { return false; }
61 | if (remoteSocketType != that.remoteSocketType) { return false; }
62 |
63 | return true;
64 | }
65 |
66 | @Override
67 | public int hashCode() {
68 | int result = negotiatedVersion != null ? negotiatedVersion.hashCode() : 0;
69 | result = 31 * result + (remoteSocketType != null ? remoteSocketType.hashCode() : 0);
70 | result = 31 * result + (remoteIdentity != null ? remoteIdentity.hashCode() : 0);
71 | return result;
72 | }
73 |
74 | @Override
75 | public String toString() {
76 | return "ZMTPHandshake{" +
77 | "negotiatedVersion=" + negotiatedVersion +
78 | ", remoteSocketType=" + remoteSocketType +
79 | '}';
80 | }
81 |
82 | static ZMTPHandshake of(final ZMTPVersion negotiatedVersion,
83 | final ByteBuffer remoteIdentity) {
84 | return new ZMTPHandshake(negotiatedVersion, remoteIdentity, null);
85 | }
86 |
87 | static ZMTPHandshake of(final ZMTPVersion negotiatedVersion,
88 | final ByteBuffer remoteIdentity, final ZMTPSocketType remoteSocketType) {
89 | return new ZMTPHandshake(negotiatedVersion, remoteIdentity, remoteSocketType);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPHandshakeFailure.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | public class ZMTPHandshakeFailure {
20 |
21 | private final ZMTPSession session;
22 |
23 | ZMTPHandshakeFailure(final ZMTPSession session) {
24 | this.session = session;
25 | }
26 |
27 | public ZMTPSession session() {
28 | return session;
29 | }
30 |
31 | @Override
32 | public String toString() {
33 | return "ZMTPHandshakeFailure{" +
34 | "session=" + session +
35 | '}';
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPHandshakeSuccess.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | public class ZMTPHandshakeSuccess {
20 |
21 | private final ZMTPSession session;
22 | private final ZMTPHandshake handshake;
23 |
24 | ZMTPHandshakeSuccess(final ZMTPSession session,
25 | final ZMTPHandshake handshake) {
26 | this.session = session;
27 | this.handshake = handshake;
28 | }
29 |
30 | public ZMTPSession session() {
31 | return session;
32 | }
33 |
34 | public ZMTPHandshake handshake() {
35 | return handshake;
36 | }
37 |
38 | @Override
39 | public String toString() {
40 | return "ZMTPHandshakeSuccess{" +
41 | "session=" + session +
42 | ", handshake=" + handshake +
43 | '}';
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPHandshaker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import io.netty.buffer.ByteBuf;
20 | import io.netty.channel.ChannelHandlerContext;
21 |
22 | interface ZMTPHandshaker {
23 |
24 | /**
25 | * Get a greeting to send immediately when a connection is established.
26 | */
27 | ByteBuf greeting();
28 |
29 | /**
30 | * Continue handshake in response to receiving data from the remote peer. This method is called
31 | * repeatedly until it returns a non-null {@link ZMTPHandshake} result.
32 | *
33 | * @param in Data from the remote peer.
34 | * @param ctx The channel handler context.
35 | * @return A {@link ZMTPHandshake} if the handshake is complete, null otherwise.
36 | * @throws ZMTPException for protocol errors.
37 | */
38 | ZMTPHandshake handshake(ByteBuf in, ChannelHandlerContext ctx) throws ZMTPException;
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPIdentityGenerator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import java.nio.ByteBuffer;
20 |
21 | public interface ZMTPIdentityGenerator {
22 |
23 | ByteBuffer generateIdentity(ZMTPSession session);
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPLongIdentityGenerator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import java.nio.ByteBuffer;
20 | import java.security.SecureRandom;
21 | import java.util.concurrent.atomic.AtomicLong;
22 |
23 | /**
24 | * A {@link ZMTPIdentityGenerator} that generates identities using a {@code long} counter.
25 | */
26 | public class ZMTPLongIdentityGenerator implements ZMTPIdentityGenerator {
27 |
28 | public static ZMTPLongIdentityGenerator GLOBAL = new ZMTPLongIdentityGenerator();
29 |
30 | private static final AtomicLong peerIdCounter = new AtomicLong(new SecureRandom().nextLong());
31 |
32 | @Override
33 | public ByteBuffer generateIdentity(final ZMTPSession session) {
34 | final ByteBuffer generated = ByteBuffer.allocate(9);
35 | generated.put((byte) 0);
36 | generated.putLong(peerIdCounter.incrementAndGet());
37 | generated.flip();
38 | return generated;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPMessage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2013 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import java.nio.CharBuffer;
20 | import java.nio.charset.Charset;
21 | import java.util.ArrayList;
22 | import java.util.Arrays;
23 | import java.util.Collection;
24 | import java.util.Iterator;
25 | import java.util.List;
26 |
27 | import io.netty.buffer.ByteBuf;
28 | import io.netty.buffer.ByteBufAllocator;
29 | import io.netty.util.AbstractReferenceCounted;
30 | import io.netty.util.internal.RecyclableArrayList;
31 |
32 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPUtils.checkNotNull;
33 | import static io.netty.buffer.ByteBufUtil.encodeString;
34 | import static io.netty.util.CharsetUtil.UTF_8;
35 | import static java.util.Arrays.asList;
36 |
37 | public class ZMTPMessage extends AbstractReferenceCounted implements Iterable {
38 |
39 | private final ByteBuf[] frames;
40 |
41 | private ZMTPMessage(final ByteBuf[] frames) {
42 | this.frames = checkNotNull(frames, "frames");
43 | }
44 |
45 | @Override
46 | public ZMTPMessage retain() {
47 | super.retain();
48 | return this;
49 | }
50 |
51 | @Override
52 | public ZMTPMessage retain(final int increment) {
53 | super.retain(increment);
54 | return this;
55 | }
56 |
57 | /**
58 | * Create a new message from a string frames, using UTF-8 encoding.
59 | */
60 | public static ZMTPMessage fromUTF8(final CharSequence... strings) {
61 | return from(ByteBufAllocator.DEFAULT, UTF_8, strings);
62 | }
63 |
64 | /**
65 | * Create a new message from a string frames, using UTF-8 encoding.
66 | */
67 | public static ZMTPMessage fromUTF8(final ByteBufAllocator alloc, final CharSequence... strings) {
68 | return from(alloc, UTF_8, strings);
69 | }
70 |
71 | /**
72 | * Create a new message from a list of string frames, using UTF-8 encoding.
73 | */
74 | public static ZMTPMessage fromUTF8(final Iterable extends CharSequence> strings) {
75 | return from(UTF_8, strings);
76 | }
77 |
78 | /**
79 | * Create a new message from a list of string frames, using UTF-8 encoding.
80 | */
81 | public static ZMTPMessage fromUTF8(final ByteBufAllocator alloc,
82 | final Iterable extends CharSequence> strings) {
83 | return from(alloc, UTF_8, strings);
84 | }
85 |
86 | /**
87 | * Create a new message from a list of string frames, using a specified encoding.
88 | */
89 | public static ZMTPMessage from(final Charset charset, final CharSequence... strings) {
90 | return from(charset, asList(strings));
91 | }
92 |
93 | /**
94 | * Create a new message from a list of string frames, using a specified encoding.
95 | */
96 | public static ZMTPMessage from(final ByteBufAllocator alloc, final Charset charset,
97 | final CharSequence... strings) {
98 | return from(alloc, charset, asList(strings));
99 | }
100 |
101 | /**
102 | * Create a new message from a list of string frames, using a specified encoding.
103 | */
104 | public static ZMTPMessage from(final Charset charset,
105 | final Iterable extends CharSequence> strings) {
106 | return from(ByteBufAllocator.DEFAULT, charset, strings);
107 | }
108 |
109 | /**
110 | * Create a new message from a list of string frames, using a specified encoding.
111 | */
112 | public static ZMTPMessage from(final ByteBufAllocator alloc, final Charset charset,
113 | final Iterable extends CharSequence> strings) {
114 | final List frames = new ArrayList();
115 | for (final CharSequence string : strings) {
116 | frames.add(encodeString(alloc, CharBuffer.wrap(string), charset));
117 | }
118 | return from(frames);
119 | }
120 |
121 | /**
122 | * Create a new message from a list of frames.
123 | */
124 | public static ZMTPMessage from(final Collection frames) {
125 | checkNotNull(frames, "frames");
126 | return new ZMTPMessage(frames.toArray(new ByteBuf[frames.size()]));
127 | }
128 |
129 | /**
130 | * Create a new message from a list of frames.
131 | */
132 | public static ZMTPMessage from(final ByteBuf[] frames) {
133 | return new ZMTPMessage(frames.clone());
134 | }
135 |
136 | public int size() {
137 | return frames.length;
138 | }
139 |
140 | @Override
141 | public Iterator iterator() {
142 | return new FrameIterator();
143 | }
144 |
145 | /**
146 | * Get a specific frame.
147 | */
148 | public ByteBuf frame(final int i) {
149 | return frames[i];
150 | }
151 |
152 | @Override
153 | protected void deallocate() {
154 | for (final ByteBuf frame : frames) {
155 | frame.release();
156 | }
157 | }
158 |
159 | @Override
160 | public boolean equals(final Object o) {
161 | if (this == o) { return true; }
162 | if (o == null || getClass() != o.getClass()) { return false; }
163 |
164 | final ZMTPMessage byteBufs = (ZMTPMessage) o;
165 |
166 | return Arrays.equals(frames, byteBufs.frames);
167 |
168 | }
169 |
170 | @Override
171 | public int hashCode() {
172 | return frames != null ? Arrays.hashCode(frames) : 0;
173 | }
174 |
175 | @Override
176 | public String toString() {
177 | return "ZMTPMessage{" + toString(frames) + '}';
178 | }
179 |
180 | /**
181 | * Create a human readable string representation of binary data, keeping printable ascii and hex
182 | * encoding everything else.
183 | *
184 | * @param data The data
185 | * @return A human readable string representation of the data.
186 | */
187 | private static String toString(final ByteBuf data) {
188 | if (data == null) {
189 | return null;
190 | }
191 | final StringBuilder sb = new StringBuilder();
192 | for (int i = data.readerIndex(); i < data.writerIndex(); i++) {
193 | final byte b = data.getByte(i);
194 | if (b > 31 && b < 127) {
195 | if (b == '%') {
196 | sb.append('%');
197 | }
198 | sb.append((char) b);
199 | } else {
200 | sb.append('%');
201 | sb.append(String.format("%02X", b));
202 | }
203 | }
204 | return sb.toString();
205 | }
206 |
207 | /**
208 | * Create a human readable string representation of a list of ZMTP frames, keeping printable ascii
209 | * and hex encoding everything else.
210 | *
211 | * @param frames The ZMTP frames.
212 | * @return A human readable string representation of the frames.
213 | */
214 | private static String toString(final ByteBuf[] frames) {
215 | final StringBuilder builder = new StringBuilder("[");
216 | for (int i = 0; i < frames.length; i++) {
217 | final ByteBuf frame = frames[i];
218 | builder.append('"');
219 | builder.append(toString(frame));
220 | builder.append('"');
221 | if (i < frames.length - 1) {
222 | builder.append(',');
223 | }
224 | }
225 | builder.append(']');
226 | return builder.toString();
227 | }
228 |
229 | /**
230 | * Convenience method for reading a {@link ZMTPMessage} from a {@link ByteBuf}.
231 | */
232 | public static ZMTPMessage read(final ByteBuf in, final ZMTPVersion version)
233 | throws ZMTPParsingException {
234 | final int mark = in.readerIndex();
235 | final ZMTPWireFormat wireFormat = ZMTPWireFormats.wireFormat(version);
236 | final ZMTPWireFormat.Header header = wireFormat.header();
237 | final RecyclableArrayList frames = RecyclableArrayList.newInstance();
238 | while (true) {
239 | final boolean read = header.read(in);
240 | if (!read) {
241 | frames.recycle();
242 | in.readerIndex(mark);
243 | return null;
244 | }
245 | if (in.readableBytes() < header.length()) {
246 | frames.recycle();
247 | in.readerIndex(mark);
248 | return null;
249 | }
250 | if (header.length() > Integer.MAX_VALUE) {
251 | throw new ZMTPParsingException("frame is too large: " + header.length());
252 | }
253 | final ByteBuf frame = in.readSlice((int) header.length());
254 | frame.retain();
255 | frames.add(frame);
256 | if (!header.more()) {
257 | @SuppressWarnings("unchecked") final ZMTPMessage message =
258 | ZMTPMessage.from((List) (Object) frames);
259 | frames.recycle();
260 | return message;
261 | }
262 | }
263 | }
264 |
265 | /**
266 | * Convenience method for writing a {@link ZMTPMessage} to a {@link ByteBuf}.
267 | */
268 | public ByteBuf write(final ZMTPVersion version) {
269 | return write(ByteBufAllocator.DEFAULT, version);
270 | }
271 |
272 | /**
273 | * Convenience method for writing a {@link ZMTPMessage} to a {@link ByteBuf}.
274 | */
275 | public ByteBuf write(final ByteBufAllocator alloc, final ZMTPVersion version) {
276 | final ZMTPMessageEncoder encoder = new ZMTPMessageEncoder();
277 | final ZMTPEstimator estimator = ZMTPEstimator.create(version);
278 | encoder.estimate(this, estimator);
279 | final ByteBuf out = alloc.buffer(estimator.size());
280 | final ZMTPWriter writer = ZMTPWriter.create(version);
281 | writer.reset(out);
282 | encoder.encode(this, writer);
283 | return out;
284 | }
285 |
286 | /**
287 | * Convenience method for writing a {@link ZMTPMessage} to a {@link ByteBuf}.
288 | */
289 | public void write(final ByteBuf out, final ZMTPVersion version) {
290 | final ZMTPWriter writer = ZMTPWriter.create(version);
291 | final ZMTPMessageEncoder encoder = new ZMTPMessageEncoder();
292 | writer.reset(out);
293 | encoder.encode(this, writer);
294 | }
295 |
296 | /**
297 | * Create a new {@link ZMTPMessage} with a frame added at the front.
298 | */
299 | public ZMTPMessage push(final ByteBuf frame) {
300 | for (final ByteBuf f : frames) {
301 | f.retain();
302 | }
303 | final ByteBuf[] frames = new ByteBuf[this.frames.length + 1];
304 | frames[0] = frame;
305 | System.arraycopy(this.frames, 0, frames, 1, this.frames.length);
306 | return new ZMTPMessage(frames);
307 | }
308 |
309 | /**
310 | * Create a new {@link ZMTPMessage} with the front frame removed.
311 | */
312 | public ZMTPMessage pop() {
313 | if (this.frames.length == 0) {
314 | throw new IllegalStateException("empty message");
315 | }
316 | final ByteBuf[] frames = new ByteBuf[this.frames.length - 1];
317 | System.arraycopy(this.frames, 1, frames, 0, frames.length);
318 | for (final ByteBuf f : frames) {
319 | f.retain();
320 | }
321 | return new ZMTPMessage(frames);
322 | }
323 |
324 | /**
325 | * Iterates over the frames of the {@link ZMTPMessage}.
326 | */
327 | private class FrameIterator implements Iterator {
328 |
329 | int i;
330 |
331 | @Override
332 | public boolean hasNext() {
333 | return i < frames.length;
334 | }
335 |
336 | @Override
337 | public ByteBuf next() {
338 | return frames[i++];
339 | }
340 |
341 | @Override
342 | public void remove() {
343 | throw new UnsupportedOperationException("remove");
344 | }
345 | }
346 | }
347 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPMessageDecoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import java.util.ArrayList;
20 | import java.util.List;
21 |
22 | import io.netty.buffer.ByteBuf;
23 | import io.netty.buffer.Unpooled;
24 | import io.netty.channel.ChannelHandlerContext;
25 |
26 | public class ZMTPMessageDecoder implements ZMTPDecoder {
27 |
28 | public static final Factory FACTORY = new Factory() {
29 | @Override
30 | public ZMTPDecoder decoder(final ZMTPSession session) {
31 | return new ZMTPMessageDecoder();
32 | }
33 | };
34 |
35 | private static final ByteBuf DELIMITER = Unpooled.EMPTY_BUFFER;
36 |
37 | private final List frames = new ArrayList();
38 | private int frameLength;
39 |
40 | /**
41 | * Reset parser in preparation for the next message.
42 | */
43 | private void reset() {
44 | frames.clear();
45 | frameLength = 0;
46 | }
47 |
48 | @Override
49 | public void header(final ChannelHandlerContext ctx, final long length, final boolean more,
50 | final List out) {
51 | frameLength = (int) length;
52 | }
53 |
54 | @Override
55 | public void content(final ChannelHandlerContext ctx, final ByteBuf data, final List out) {
56 | // Wait for more data?
57 | if (data.readableBytes() < frameLength) {
58 | return;
59 | }
60 |
61 | if (frameLength == 0) {
62 | frames.add(DELIMITER);
63 | return;
64 | }
65 |
66 | final ByteBuf frame = data.readSlice(frameLength);
67 | frame.retain();
68 | frames.add(frame);
69 | }
70 |
71 | @Override
72 | public void finish(final ChannelHandlerContext ctx, final List out) {
73 | final ZMTPMessage message = ZMTPMessage.from(frames);
74 | reset();
75 | out.add(message);
76 | }
77 |
78 | @Override
79 | public void close() {
80 | for (final ByteBuf frame : frames) {
81 | frame.release();
82 | }
83 | frames.clear();
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPMessageEncoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2014 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import io.netty.buffer.ByteBuf;
20 |
21 | public class ZMTPMessageEncoder implements ZMTPEncoder {
22 |
23 | public static final Factory FACTORY = new Factory() {
24 | @Override
25 | public ZMTPEncoder encoder(final ZMTPSession session) {
26 | return new ZMTPMessageEncoder();
27 | }
28 | };
29 |
30 | @Override
31 | public void estimate(final Object msg, final ZMTPEstimator estimator) {
32 | final ZMTPMessage message = (ZMTPMessage) msg;
33 | for (int i = 0; i < message.size(); i++) {
34 | final ByteBuf frame = message.frame(i);
35 | estimator.frame(frame.readableBytes());
36 | }
37 | }
38 |
39 | @Override
40 | public void encode(final Object msg, final ZMTPWriter writer) {
41 | final ZMTPMessage message = (ZMTPMessage) msg;
42 | for (int i = 0; i < message.size(); i++) {
43 | final ByteBuf frame = message.frame(i);
44 | final boolean more = i < message.size() - 1;
45 | final ByteBuf dst = writer.frame(frame.readableBytes(), more);
46 | dst.writeBytes(frame, frame.readerIndex(), frame.readableBytes());
47 | }
48 | }
49 |
50 | @Override
51 | public void close() {
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPParsingException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2013 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | public class ZMTPParsingException extends ZMTPException {
20 |
21 | public ZMTPParsingException(final String message) {
22 | super(message);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPProtocol.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | public interface ZMTPProtocol {
20 |
21 | ZMTPHandshaker handshaker(ZMTPConfig config);
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPProtocols.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | public class ZMTPProtocols {
20 |
21 | public static final ZMTPProtocol ZMTP10 = new ZMTP10Protocol();
22 | public static final ZMTPProtocol ZMTP20 = new ZMTP20Protocol();
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPSession.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2013 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import java.nio.ByteBuffer;
20 |
21 | import io.netty.util.concurrent.DefaultPromise;
22 | import io.netty.util.concurrent.Future;
23 | import io.netty.util.concurrent.GlobalEventExecutor;
24 |
25 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPUtils.checkNotNull;
26 |
27 | /**
28 | * Represents one end of a single ZMTP connection.
29 | */
30 | public class ZMTPSession {
31 |
32 | private final DefaultPromise handshake = new DefaultPromise(
33 | GlobalEventExecutor.INSTANCE);
34 |
35 | private final ZMTPConfig config;
36 |
37 | private volatile ByteBuffer peerIdentity;
38 |
39 | ZMTPSession(final ZMTPConfig config) {
40 | this.config = checkNotNull(config, "config");
41 | }
42 |
43 | /**
44 | * The configuration of this ZMTP session.
45 | */
46 | public ZMTPConfig config() {
47 | return config;
48 | }
49 |
50 | /**
51 | * Get the peer identity.
52 | */
53 | public ByteBuffer peerIdentity() {
54 | if (!handshake.isDone()) {
55 | throw new IllegalStateException("handshake not complete");
56 | }
57 | return peerIdentity.asReadOnlyBuffer();
58 | }
59 |
60 | /**
61 | * Check whether the peer is anonymous and the peer identity is generated.
62 | */
63 | public boolean isPeerAnonymous() {
64 | return !handshake().remoteIdentity().hasRemaining();
65 | }
66 |
67 | /**
68 | * The ZMTP framing version negotiated as part of the handshake on connection establishment.
69 | */
70 | public ZMTPVersion negotiatedVersion() {
71 | return handshake().negotiatedVersion();
72 | }
73 |
74 | /**
75 | * Get a future that will be notified when the ZMTP handshake is complete.
76 | */
77 | public Future handshakeFuture() {
78 | return handshake;
79 | }
80 |
81 | /**
82 | * Signal ZMTP handshake success.
83 | */
84 | void handshakeSuccess(final ZMTPHandshake handshake) {
85 | peerIdentity = handshake.remoteIdentity().hasRemaining()
86 | ? handshake.remoteIdentity()
87 | : config.identityGenerator().generateIdentity(this);
88 | this.handshake.setSuccess(handshake);
89 | }
90 |
91 | /**
92 | * Signal ZMTP handshake failure.
93 | */
94 | void handshakeFailure(final Throwable cause) {
95 | this.handshake.tryFailure(cause);
96 | }
97 |
98 | private ZMTPHandshake handshake() {
99 | if (!handshake.isDone()) {
100 | throw new IllegalStateException("handshake not complete");
101 | }
102 | final ZMTPHandshake handshake = this.handshake.getNow();
103 | assert handshake != null;
104 | return handshake;
105 | }
106 |
107 | @Override
108 | public String toString() {
109 | return "ZMTPSession{" +
110 | "config=" + config +
111 | ", handshake=" + handshake +
112 | '}';
113 | }
114 |
115 | public static ZMTPSession from(final ZMTPConfig config) {
116 | return new ZMTPSession(config);
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPSocketType.java:
--------------------------------------------------------------------------------
1 | package com.spotify.netty4.handler.codec.zmtp;
2 |
3 | /**
4 | * Enumerates the different socket types, used to make sure that connecting both peers in a socket
5 | * pair has compatible socket types.
6 | */
7 | public enum ZMTPSocketType {
8 | PAIR,
9 | PUB,
10 | SUB,
11 | REQ,
12 | REP,
13 | DEALER,
14 | ROUTER,
15 | PULL,
16 | PUSH
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2013 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import static java.lang.String.format;
20 |
21 | class ZMTPUtils {
22 |
23 | static T checkNotNull(final T obj, final String message) {
24 | if (obj == null) {
25 | throw new NullPointerException(message);
26 | }
27 | return obj;
28 | }
29 |
30 | static void checkArgument(final boolean expression, final String message, final Object... args) {
31 | if (!expression) {
32 | throw new IllegalArgumentException(format(message, args));
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPVersion.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import java.util.List;
20 |
21 | import static java.util.Arrays.asList;
22 | import static java.util.Collections.unmodifiableList;
23 |
24 | public enum ZMTPVersion {
25 | ZMTP10(0, true),
26 | ZMTP20(1, true);
27 |
28 | private final int revision;
29 | private final boolean supported;
30 |
31 | ZMTPVersion(final int revision, final boolean supported) {
32 | this.revision = revision;
33 | this.supported = supported;
34 | }
35 |
36 | public int revision() {
37 | return revision;
38 | }
39 |
40 | public boolean supported() {
41 | return supported;
42 | }
43 |
44 | private static final List SUPPORTED = unmodifiableList(asList(ZMTP10, ZMTP20));
45 |
46 | public static boolean isSupported(final ZMTPVersion version) {
47 | return SUPPORTED.contains(version);
48 | }
49 |
50 | public static boolean isSupported(final int revision) {
51 | for (final ZMTPVersion version : SUPPORTED) {
52 | if (version.revision == revision) {
53 | return version.supported;
54 | }
55 | }
56 | return false;
57 | }
58 |
59 | public static List supportedVersions() {
60 | return SUPPORTED;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPWireFormat.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import io.netty.buffer.ByteBuf;
20 |
21 | interface ZMTPWireFormat {
22 |
23 | int frameLength(int content);
24 |
25 | Header header();
26 |
27 | interface Header {
28 |
29 | void set(int maxLength, int length, boolean more);
30 |
31 | void write(ByteBuf out);
32 |
33 | boolean read(ByteBuf in) throws ZMTPParsingException;
34 |
35 | long length();
36 |
37 | boolean more();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPWireFormats.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | class ZMTPWireFormats {
20 |
21 | static ZMTPWireFormat wireFormat(final ZMTPVersion version) {
22 | switch (version) {
23 | case ZMTP10:
24 | return new ZMTP10WireFormat();
25 | case ZMTP20:
26 | return new ZMTP20WireFormat();
27 | default:
28 | throw new IllegalArgumentException("Unsupported version: " + version);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/handler/codec/zmtp/ZMTPWriter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2014 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import io.netty.buffer.ByteBuf;
20 |
21 | import static java.lang.Math.min;
22 |
23 | /**
24 | * A writer for encoding ZMTP frames onto a {@link ByteBuf}.
25 | */
26 | public class ZMTPWriter {
27 |
28 | private final ZMTPWireFormat.Header header;
29 |
30 | private ByteBuf buf;
31 | private int frameSize;
32 | private int headerIndex;
33 | private int contentIndex;
34 |
35 | ZMTPWriter(final ZMTPWireFormat wireFormat) {
36 | this(wireFormat.header());
37 | }
38 |
39 | ZMTPWriter(final ZMTPWireFormat.Header header) {
40 | this.header = header;
41 | }
42 |
43 | void reset(final ByteBuf buf) {
44 | this.buf = buf;
45 | }
46 |
47 | /**
48 | * Start a new ZMTP frame.
49 | *
50 | * @param size Payload size.
51 | * @param more true if more frames will be written, false if this is the last frame.
52 | * @return A {@link ByteBuf} for writing the frame payload.
53 | */
54 | public ByteBuf frame(final int size, final boolean more) {
55 | frameSize = size;
56 | headerIndex = buf.writerIndex();
57 | header.set(size, size, more);
58 | header.write(buf);
59 | contentIndex = buf.writerIndex();
60 | return buf;
61 | }
62 |
63 | /**
64 | * Rewrite the ZMTP frame header, optionally writing a different size or changing the MORE flag.
65 | * This can be useful when writing a payload where estimating the exact size is expensive but an
66 | * upper bound can be cheaply computed. E.g. when writing UTF8.
67 | *
68 | * @param size New size. This must not be greater than the size provided in the call to {@link
69 | * #frame}.
70 | * @param more true if more frames will be written, false if this is the last frame.
71 | * @return A {@link ByteBuf} for writing the remainder of the frame payload, if any. The {@link
72 | * ByteBuf#writerIndex()} will be set to directly after the already written payload, or truncated
73 | * down to the end of the new smaller payload, if the written payload exceeds the new frame size.
74 | */
75 | public ByteBuf reframe(final int size, final boolean more) {
76 | if (size > frameSize) {
77 | // Although ByteBufs grow (reallocate) dynamically, the header might end up taking more space,
78 | // forcing us to move the already written payload. We currently do not implement this.
79 | throw new IllegalArgumentException("new frame size is greater than original size");
80 | }
81 | final int mark = buf.writerIndex();
82 | final int written = mark - contentIndex;
83 | if (written < 0) {
84 | throw new IllegalStateException("written < 0");
85 | }
86 | final int newIndex = contentIndex + min(written, size);
87 | buf.writerIndex(headerIndex);
88 | header.set(frameSize, size, more);
89 | header.write(buf);
90 | buf.writerIndex(newIndex);
91 | return buf;
92 | }
93 |
94 | static ZMTPWriter create(final ZMTPVersion version) {
95 | return new ZMTPWriter(ZMTPWireFormats.wireFormat(version));
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/com/spotify/netty4/util/BatchFlusher.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2014 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.util;
18 |
19 | import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
20 |
21 | import io.netty.channel.Channel;
22 | import io.netty.channel.EventLoop;
23 |
24 | /**
25 | * A helper for doing opportunistic batching of netty channel flushes, allowing for a gathering
26 | * write to an underlying {@link java.nio.channels.GatheringByteChannel}, collapsing multiple writes
27 | * into fewer syscalls.
28 | */
29 | public class BatchFlusher {
30 |
31 | private static final int DEFAULT_MAX_PENDING = 64;
32 |
33 | private final Channel channel;
34 | private final EventLoop eventLoop;
35 | private final int maxPending;
36 |
37 | private final AtomicIntegerFieldUpdater WOKEN =
38 | AtomicIntegerFieldUpdater.newUpdater(BatchFlusher.class, "woken");
39 | @SuppressWarnings("UnusedDeclaration") private volatile int woken;
40 |
41 | private int pending;
42 |
43 | /**
44 | * Used to flush all outstanding writes in the outbound channel buffer.
45 | */
46 | private final Runnable flush = new Runnable() {
47 | @Override
48 | public void run() {
49 | pending = 0;
50 | channel.flush();
51 | }
52 | };
53 |
54 | /**
55 | * Used to wake up the event loop and schedule a flush to be performed after all outstanding write
56 | * tasks are run. The outstanding write tasks must be allowed to run before performing the actual
57 | * flush in order to ensure that their payloads have been written to the outbound buffer.
58 | */
59 | private final Runnable wakeup = new Runnable() {
60 | @Override
61 | public void run() {
62 | woken = 0;
63 | eventLoop.execute(flush);
64 | }
65 | };
66 |
67 | public BatchFlusher(final Channel channel) {
68 | this(channel, DEFAULT_MAX_PENDING);
69 | }
70 |
71 | public BatchFlusher(final Channel channel, final int maxPending) {
72 | this.channel = channel;
73 | this.maxPending = maxPending;
74 | this.eventLoop = channel.eventLoop();
75 | }
76 |
77 | /**
78 | * Schedule an asynchronous opportunistically batching flush.
79 | */
80 | public void flush() {
81 | if (eventLoop.inEventLoop()) {
82 | pending++;
83 | if (pending >= maxPending) {
84 | pending = 0;
85 | channel.flush();
86 | }
87 | }
88 | if (woken == 0 && WOKEN.compareAndSet(this, 0, 1)) {
89 | woken = 1;
90 | eventLoop.execute(wakeup);
91 | }
92 | }
93 | }
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/Buffers.java:
--------------------------------------------------------------------------------
1 | package com.spotify.netty4.handler.codec.zmtp;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.buffer.Unpooled;
5 |
6 | class Buffers {
7 |
8 | public static byte[] bytes(int... bytes) {
9 | byte[] bs = new byte[bytes.length];
10 | for (int i = 0; i < bytes.length; i++) {
11 | bs[i] = (byte) bytes[i];
12 | }
13 | return bs;
14 | }
15 |
16 | public static ByteBuf buf(int... bytes) {
17 | return buf(bytes(bytes));
18 | }
19 |
20 | public static ByteBuf buf(byte[] data) {
21 | return Unpooled.copiedBuffer(data);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/CodecBenchmark.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2013 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import com.google.common.collect.Lists;
20 |
21 | import org.openjdk.jmh.annotations.Benchmark;
22 | import org.openjdk.jmh.annotations.Scope;
23 | import org.openjdk.jmh.annotations.State;
24 | import org.openjdk.jmh.infra.Blackhole;
25 | import org.openjdk.jmh.runner.Runner;
26 | import org.openjdk.jmh.runner.RunnerException;
27 | import org.openjdk.jmh.runner.options.Options;
28 | import org.openjdk.jmh.runner.options.OptionsBuilder;
29 |
30 | import java.util.List;
31 |
32 | import io.netty.buffer.ByteBuf;
33 | import io.netty.buffer.PooledByteBufAllocator;
34 | import io.netty.channel.ChannelHandlerContext;
35 | import io.netty.util.ReferenceCountUtil;
36 |
37 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPVersion.ZMTP10;
38 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPVersion.ZMTP20;
39 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPWireFormats.wireFormat;
40 |
41 | // FIXME (dano): this benchmark needs to be in this package because it uses some internals
42 |
43 | @State(Scope.Benchmark)
44 | public class CodecBenchmark {
45 |
46 | private final List out = Lists.newArrayList();
47 |
48 | private final ZMTPMessage message = ZMTPMessage.fromUTF8(
49 | "first identity frame",
50 | "second identity frame",
51 | "",
52 | "datadatadatadatadatadatadatadatadatadata",
53 | "datadatadatadatadatadatadatadatadatadata",
54 | "datadatadatadatadatadatadatadatadatadata",
55 | "datadatadatadatadatadatadatadatadatadata");
56 |
57 | private final ZMTPFramingDecoder messageDecoderZMTP10 =
58 | new ZMTPFramingDecoder(wireFormat(ZMTP10), new ZMTPMessageDecoder());
59 |
60 | private final ZMTPFramingDecoder messageDecoderZMTP20 =
61 | new ZMTPFramingDecoder(wireFormat(ZMTP20), new ZMTPMessageDecoder());
62 |
63 | private final ZMTPFramingDecoder discardingDecoderZMTP10 =
64 | new ZMTPFramingDecoder(wireFormat(ZMTP10), new Discarder());
65 |
66 | private final ZMTPFramingDecoder discardingDecoderZMTP20 =
67 | new ZMTPFramingDecoder(wireFormat(ZMTP20), new Discarder());
68 |
69 | private final ByteBuf incomingZMTP10;
70 | private final ByteBuf incomingZMTP20;
71 |
72 | private final ZMTPMessageEncoder encoder = new ZMTPMessageEncoder();
73 | private final ZMTPWriter writerZMTP10 = ZMTPWriter.create(ZMTP10);
74 | private final ZMTPWriter writerZMTP20 = ZMTPWriter.create(ZMTP20);
75 |
76 | private final ByteBuf tmp = PooledByteBufAllocator.DEFAULT.buffer(4096);
77 |
78 | {
79 | incomingZMTP10 = message.write(PooledByteBufAllocator.DEFAULT, ZMTP10);
80 | incomingZMTP20 = message.write(PooledByteBufAllocator.DEFAULT, ZMTP20);
81 | }
82 |
83 | @SuppressWarnings("ForLoopReplaceableByForEach")
84 | private void consumeAndRelease(final Blackhole bh, final List out) {
85 | for (int i = 0; i < out.size(); i++) {
86 | final Object o = out.get(i);
87 | bh.consume(o);
88 | ReferenceCountUtil.release(o);
89 | }
90 | out.clear();
91 | }
92 |
93 | @Benchmark
94 | public void parsingToMessageZMTP10(final Blackhole bh) throws ZMTPParsingException {
95 | messageDecoderZMTP10.decode(null, incomingZMTP10.resetReaderIndex(), out);
96 | consumeAndRelease(bh, out);
97 | }
98 |
99 | @Benchmark
100 | public void parsingToMessageZMTP20(final Blackhole bh) throws ZMTPParsingException {
101 | messageDecoderZMTP20.decode(null, incomingZMTP20.resetReaderIndex(), out);
102 | consumeAndRelease(bh, out);
103 | }
104 |
105 | @Benchmark
106 | public void discardingZMTP10(final Blackhole bh) throws ZMTPParsingException {
107 | discardingDecoderZMTP10.decode(null, incomingZMTP10.resetReaderIndex(), out);
108 | consumeAndRelease(bh, out);
109 | }
110 |
111 | @Benchmark
112 | public void discardingZMTP20(final Blackhole bh) throws ZMTPParsingException {
113 | discardingDecoderZMTP20.decode(null, incomingZMTP20.resetReaderIndex(), out);
114 | consumeAndRelease(bh, out);
115 | }
116 |
117 | @Benchmark
118 | public Object encodingZMTP10() {
119 | writerZMTP10.reset(tmp.setIndex(0, 0));
120 | encoder.encode(message, writerZMTP10);
121 | return tmp;
122 | }
123 |
124 | @Benchmark
125 | public Object encodingZMTP20() {
126 | writerZMTP20.reset(tmp.setIndex(0, 0));
127 | encoder.encode(message, writerZMTP20);
128 | return tmp;
129 | }
130 |
131 | public static void main(final String... args) throws RunnerException, InterruptedException {
132 | Options opt = new OptionsBuilder()
133 | .include(".*")
134 | .forks(1)
135 | .build();
136 |
137 | new Runner(opt).run();
138 | }
139 |
140 |
141 | private class Discarder implements ZMTPDecoder {
142 |
143 | private int size;
144 |
145 |
146 | @Override
147 | public void header(final ChannelHandlerContext ctx, final long length, final boolean more,
148 | final List out) {
149 | this.size += size;
150 | }
151 |
152 | @Override
153 | public void content(final ChannelHandlerContext ctx, final ByteBuf data,
154 | final List out) {
155 | data.skipBytes(data.readableBytes());
156 | }
157 |
158 | @Override
159 | public void finish(final ChannelHandlerContext ctx, final List out) {
160 | }
161 |
162 | @Override
163 | public void close() {
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/EndToEndTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2014 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import com.google.common.collect.Queues;
20 |
21 | import org.junit.Test;
22 |
23 | import java.net.InetSocketAddress;
24 | import java.net.SocketAddress;
25 | import java.util.concurrent.BlockingQueue;
26 |
27 | import io.netty.bootstrap.Bootstrap;
28 | import io.netty.bootstrap.ServerBootstrap;
29 | import io.netty.channel.Channel;
30 | import io.netty.channel.ChannelHandler;
31 | import io.netty.channel.ChannelHandlerContext;
32 | import io.netty.channel.ChannelInboundHandlerAdapter;
33 | import io.netty.channel.ChannelInitializer;
34 | import io.netty.channel.nio.NioEventLoopGroup;
35 | import io.netty.channel.socket.nio.NioServerSocketChannel;
36 | import io.netty.channel.socket.nio.NioSocketChannel;
37 | import io.netty.util.ReferenceCountUtil;
38 |
39 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPProtocols.ZMTP10;
40 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPProtocols.ZMTP20;
41 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPSocketType.DEALER;
42 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPSocketType.ROUTER;
43 | import static java.util.concurrent.TimeUnit.SECONDS;
44 | import static org.hamcrest.core.Is.is;
45 | import static org.hamcrest.core.IsNull.notNullValue;
46 | import static org.hamcrest.core.IsNull.nullValue;
47 | import static org.junit.Assert.assertThat;
48 |
49 | public class EndToEndTest {
50 |
51 | private static final InetSocketAddress ANY_PORT = new InetSocketAddress("127.0.0.1", 0);
52 |
53 | private Channel bind(final SocketAddress address, final ChannelHandler codec,
54 | final ChannelHandler handler) {
55 | final ServerBootstrap bootstrap = new ServerBootstrap();
56 | bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup());
57 | bootstrap.channel(NioServerSocketChannel.class);
58 | bootstrap.childHandler(new ChannelInitializer() {
59 | @Override
60 | protected void initChannel(final NioSocketChannel ch) throws Exception {
61 | ch.pipeline().addLast(codec, handler);
62 | }
63 | });
64 | return bootstrap.bind(address).awaitUninterruptibly().channel();
65 | }
66 |
67 | private Channel connect(final SocketAddress address, final ChannelHandler codec,
68 | final ChannelHandler handler) {
69 | final Bootstrap bootstrap = new Bootstrap();
70 | bootstrap.group(new NioEventLoopGroup());
71 | bootstrap.channel(NioSocketChannel.class);
72 | bootstrap.handler(new ChannelInitializer() {
73 | @Override
74 | protected void initChannel(final NioSocketChannel ch) throws Exception {
75 | ch.pipeline().addLast(codec, handler);
76 | }
77 | });
78 | return bootstrap.connect(address).awaitUninterruptibly().channel();
79 | }
80 |
81 | private void testRequestReply(final ChannelHandler serverCodec, final ChannelHandler clientCodec)
82 | throws InterruptedException {
83 |
84 | // Set up server & client
85 | final Handler server = new Handler();
86 | final Handler client = new Handler();
87 | final SocketAddress address = bind(ANY_PORT, serverCodec, server).localAddress();
88 | final Channel clientChannel = connect(address, clientCodec, client);
89 | final Channel serverConnectedChannel = server.connected.poll(5, SECONDS);
90 | assertThat(serverConnectedChannel, is(notNullValue()));
91 |
92 | // Make sure there's no left over messages/connections on the wires
93 | Thread.sleep(100);
94 | assertThat("unexpected server message", server.messages.poll(), is(nullValue()));
95 | assertThat("unexpected client message", client.messages.poll(), is(nullValue()));
96 | assertThat("unexpected server connection", server.connected.poll(), is(nullValue()));
97 |
98 | // Send and receive request
99 | final ZMTPMessage helloWorldMessage = ZMTPMessage.fromUTF8("", "hello", "world");
100 | clientChannel.writeAndFlush(helloWorldMessage.retain());
101 | final ZMTPMessage receivedRequest = server.messages.poll(5, SECONDS);
102 | assertThat(receivedRequest, is(notNullValue()));
103 | assertThat(receivedRequest, is(helloWorldMessage));
104 | helloWorldMessage.release();
105 |
106 | // Send and receive reply
107 | final ZMTPMessage fooBarMessage = ZMTPMessage.fromUTF8("", "foo", "bar");
108 | serverConnectedChannel.writeAndFlush(fooBarMessage.retain());
109 | final ZMTPMessage receivedReply = client.messages.poll(5, SECONDS);
110 | assertThat(receivedReply, is(notNullValue()));
111 | assertThat(receivedReply, is(fooBarMessage));
112 | fooBarMessage.release();
113 |
114 | // Make sure there's no left over messages/connections on the wires
115 | Thread.sleep(100);
116 | assertThat("unexpected server message", server.messages.poll(), is(nullValue()));
117 | assertThat("unexpected client message", client.messages.poll(), is(nullValue()));
118 | assertThat("unexpected server connection", server.connected.poll(), is(nullValue()));
119 | }
120 |
121 | @Test
122 | public void test_ZMTP10_Router_VS_ZMTP10_Dealer() throws InterruptedException {
123 | final ZMTPCodec server = ZMTPCodec.builder()
124 | .protocol(ZMTP10)
125 | .socketType(ROUTER)
126 | .build();
127 |
128 | final ZMTPCodec client = ZMTPCodec.builder()
129 | .protocol(ZMTP10)
130 | .socketType(DEALER)
131 | .build();
132 |
133 | testRequestReply(server, client);
134 | }
135 |
136 | @Test
137 | public void test_ZMTP20_Interop_Router_VS_ZMTP20_Interop_Dealer() throws InterruptedException {
138 | final ZMTPCodec server = ZMTPCodec.builder()
139 | .protocol(ZMTP20)
140 | .interop(true)
141 | .socketType(ROUTER)
142 | .build();
143 |
144 | final ZMTPCodec client = ZMTPCodec.builder()
145 | .protocol(ZMTP20)
146 | .interop(true)
147 | .socketType(DEALER)
148 | .build();
149 |
150 | testRequestReply(server, client);
151 | }
152 |
153 | @Test
154 | public void test_ZMTP20_Interop_Router_VS_ZMTP10_Dealer() throws InterruptedException {
155 | final ZMTPCodec server = ZMTPCodec.builder()
156 | .protocol(ZMTP20)
157 | .interop(true)
158 | .socketType(ROUTER)
159 | .build();
160 |
161 | final ZMTPCodec client = ZMTPCodec.builder()
162 | .protocol(ZMTP10)
163 | .socketType(DEALER)
164 | .build();
165 |
166 | testRequestReply(server, client);
167 | }
168 |
169 | @Test
170 | public void test_ZMTP20_NoInterop_Router_VS_ZMTP20_NoInterop_Dealer() throws InterruptedException {
171 | final ZMTPCodec server = ZMTPCodec.builder()
172 | .protocol(ZMTP20)
173 | .interop(false)
174 | .socketType(ROUTER)
175 | .build();
176 |
177 | final ZMTPCodec client = ZMTPCodec.builder()
178 | .protocol(ZMTP20)
179 | .interop(false)
180 | .socketType(DEALER)
181 | .build();
182 |
183 | testRequestReply(server, client);
184 | }
185 |
186 | private static class Handler extends ChannelInboundHandlerAdapter {
187 |
188 | private final BlockingQueue connected = Queues.newLinkedBlockingQueue();
189 | private final BlockingQueue messages = Queues.newLinkedBlockingQueue();
190 |
191 | @Override
192 | public void channelActive(final ChannelHandlerContext ctx) throws Exception {
193 | super.channelActive(ctx);
194 | connected.add(ctx.channel());
195 | }
196 |
197 | @Override
198 | public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
199 | ReferenceCountUtil.releaseLater(msg);
200 | messages.put((ZMTPMessage) msg);
201 | }
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/Fragmenter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2013 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | class Fragmenter {
20 |
21 | interface Consumer {
22 | void fragments(int[] limits, int count) throws Exception;
23 | }
24 |
25 | private final int[] limits;
26 | private final int length;
27 |
28 | public Fragmenter(final int length) {
29 | this.limits = new int[length];
30 | this.length = length;
31 | }
32 |
33 | public void fragment(final Consumer consumer) throws Exception {
34 | fragment(consumer, 0, 0);
35 | }
36 |
37 | private void fragment(final Consumer consumer, final int count, final int limit)
38 | throws Exception {
39 | if (limit == length) {
40 | consumer.fragments(limits, count);
41 | return;
42 | }
43 |
44 | for (int o = limit + 1; o <= length; o++) {
45 | limits[count] = o;
46 | fragment(consumer, count + 1, o);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/FragmenterTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2013 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import com.google.common.collect.Lists;
20 |
21 | import org.junit.Test;
22 |
23 | import java.util.Arrays;
24 | import java.util.List;
25 |
26 | import static org.junit.Assert.assertArrayEquals;
27 | import static org.junit.Assert.assertEquals;
28 |
29 | public class FragmenterTest {
30 |
31 | private static final int SIZE = 4;
32 |
33 | private static final int[][] EXPECTED = {
34 | {1, 2, 3, 4},
35 | {1, 2, 4},
36 | {1, 3, 4},
37 | {1, 4},
38 | {2, 3, 4},
39 | {2, 4},
40 | {3, 4},
41 | {4},
42 | };
43 |
44 | @Test
45 | public void test() throws Exception {
46 | final List output = Lists.newArrayList();
47 | final Fragmenter fragmenter = new Fragmenter(SIZE);
48 | fragmenter.fragment(new Fragmenter.Consumer() {
49 | @Override
50 | public void fragments(final int[] limits, final int count) {
51 | output.add(Arrays.copyOf(limits, count));
52 | }
53 | });
54 |
55 | assertEquals(EXPECTED.length, output.size());
56 | for (int i = 0; i < EXPECTED.length; i++) {
57 | assertArrayEquals(EXPECTED[i], output.get(i));
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/HandshakeTest.java:
--------------------------------------------------------------------------------
1 | package com.spotify.netty4.handler.codec.zmtp;
2 |
3 |
4 | import org.junit.Test;
5 | import org.junit.runner.RunWith;
6 | import org.mockito.Mock;
7 | import org.mockito.runners.MockitoJUnitRunner;
8 |
9 | import java.nio.ByteBuffer;
10 |
11 | import io.netty.buffer.ByteBuf;
12 | import io.netty.buffer.Unpooled;
13 | import io.netty.channel.ChannelHandlerContext;
14 |
15 | import static com.spotify.netty4.handler.codec.zmtp.Buffers.buf;
16 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPSocketType.PUB;
17 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPSocketType.REQ;
18 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPSocketType.ROUTER;
19 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPSocketType.SUB;
20 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPVersion.ZMTP10;
21 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPVersion.ZMTP20;
22 | import static io.netty.util.CharsetUtil.UTF_8;
23 | import static org.hamcrest.Matchers.is;
24 | import static org.hamcrest.Matchers.notNullValue;
25 | import static org.hamcrest.Matchers.nullValue;
26 | import static org.junit.Assert.assertEquals;
27 | import static org.junit.Assert.assertThat;
28 | import static org.junit.Assert.fail;
29 | import static org.mockito.Mockito.verify;
30 | import static org.mockito.Mockito.verifyNoMoreInteractions;
31 | import static org.mockito.Mockito.verifyZeroInteractions;
32 |
33 | /**
34 | * Tests the handshake protocol
35 | */
36 | @RunWith(MockitoJUnitRunner.class)
37 | public class HandshakeTest {
38 |
39 | private static final ByteBuffer FOO = UTF_8.encode("foo");
40 | private static final ByteBuffer BAR = UTF_8.encode("bar");
41 |
42 | @Mock ChannelHandlerContext ctx;
43 |
44 | @Test
45 | public void testGreeting() {
46 | ZMTPHandshaker h = new ZMTP10Protocol.Handshaker(FOO);
47 | assertThat(h.greeting(), is(buf(0x04, 0x00, 0x66, 0x6f, 0x6f)));
48 |
49 | h = new ZMTP10Protocol.Handshaker(ByteBuffer.allocate(0));
50 | assertThat(h.greeting(), is(buf(0x01, 0x00)));
51 |
52 | h = new ZMTP20Protocol.Handshaker(SUB, FOO, true);
53 | assertThat(h.greeting(), is(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 4, 0x7f)));
54 |
55 | h = new ZMTP20Protocol.Handshaker(REQ, FOO, false);
56 | assertThat(h.greeting(), is(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0x7f,
57 | 0x01, 0x03, 0x00, 3, 0x66, 0x6f, 0x6f)));
58 | }
59 |
60 | @Test
61 | public void test1to1Handshake() throws Exception {
62 | final ZMTP10Protocol.Handshaker h = new ZMTP10Protocol.Handshaker(FOO);
63 | assertThat(h.greeting(), is(buf(0x04, 0x00, 0x66, 0x6f, 0x6f)));
64 | final ZMTPHandshake handshake = h.handshake(buf(0x04, 0x00, 0x62, 0x61, 0x72), ctx);
65 | assertThat(handshake, is(notNullValue()));
66 | verifyZeroInteractions(ctx);
67 | assertEquals(ZMTPHandshake.of(ZMTP10, BAR, null), handshake);
68 | }
69 |
70 | @Test
71 | public void test2InteropTo1Handshake() throws Exception {
72 | ZMTPHandshaker h = new ZMTP20Protocol.Handshaker(ROUTER, FOO, true);
73 | assertThat(h.greeting(), is(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 0x04, 0x7f)));
74 | ZMTPHandshake handshake = h.handshake(buf(0x04, 0x00, 0x62, 0x61, 0x72), ctx);
75 | assertThat(handshake, is(notNullValue()));
76 | verify(ctx).writeAndFlush(buf(0x66, 0x6f, 0x6f));
77 | assertEquals(ZMTPHandshake.of(ZMTP10, BAR, null), handshake);
78 | }
79 |
80 | @Test
81 | public void test2InteropTo2InteropHandshake() throws Exception {
82 | ZMTPHandshaker h = new ZMTP20Protocol.Handshaker(PUB, FOO, true);
83 | assertThat(h.greeting(), is(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 0x04, 0x7f)));
84 | ZMTPHandshake handshake;
85 | handshake = h.handshake(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 0x04, 0x7f), ctx);
86 | assertThat(handshake, is(nullValue()));
87 | verify(ctx).writeAndFlush(buf(0x01, 0x01, 0x00, 0x03, 0x66, 0x6f, 0x6f));
88 | handshake = h.handshake(buf(0x01, 0x01, 0x00, 0x03, 0x62, 0x61, 0x72), ctx);
89 | assertThat(handshake, is(notNullValue()));
90 | verifyNoMoreInteractions(ctx);
91 | assertEquals(ZMTPHandshake.of(ZMTPVersion.ZMTP20, BAR, PUB), handshake);
92 | }
93 |
94 | @Test
95 | public void test2InteropTo2Handshake() throws Exception {
96 | ZMTPHandshaker h = new ZMTP20Protocol.Handshaker(PUB, FOO, true);
97 | assertThat(h.greeting(), is(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 0x04, 0x7f)));
98 | ByteBuf cb = buf(0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0x7f, 0x01, 0x01, 0x00, 0x03, 0x62, 0x61, 0x72);
99 | ZMTPHandshake handshake;
100 | handshake = h.handshake(cb, ctx);
101 | assertThat(handshake, is(nullValue()));
102 | verify(ctx).writeAndFlush(buf(0x01, 0x01, 0x00, 0x03, 0x66, 0x6f, 0x6f));
103 | handshake = h.handshake(cb, ctx);
104 | assertThat(handshake, is(notNullValue()));
105 | verifyNoMoreInteractions(ctx);
106 | assertEquals(ZMTPHandshake.of(ZMTPVersion.ZMTP20, BAR, PUB), handshake);
107 | }
108 |
109 | @Test
110 | public void test2To2InteropHandshake() throws Exception {
111 | ZMTPHandshaker h = new ZMTP20Protocol.Handshaker(PUB, FOO, false);
112 | assertThat(h.greeting(), is(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0x7f, 0x1, 0x1, 0, 0x3, 0x66, 0x6f, 0x6f)));
113 |
114 | try {
115 | h.handshake(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 0x4, 0x7f), ctx);
116 | fail("not enough data in greeting (because compat mode) should have thrown exception");
117 | } catch (IndexOutOfBoundsException e) {
118 | // expected
119 | }
120 | ZMTPHandshake handshake = h.handshake(
121 | buf(0xff, 0, 0, 0, 0, 0, 0, 0, 0x4, 0x7f, 0x1, 0x1, 0, 0x03, 0x62, 0x61, 0x72), ctx);
122 | assertThat(handshake, is(notNullValue()));
123 | assertEquals(ZMTPHandshake.of(ZMTPVersion.ZMTP20, BAR, PUB), handshake);
124 | }
125 |
126 | @Test
127 | public void test2To2Handshake() throws Exception {
128 | ZMTPHandshaker h = new ZMTP20Protocol.Handshaker(PUB, FOO, false);
129 | assertThat(h.greeting(), is(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0x7f, 0x1, 0x1, 0, 0x3, 0x66, 0x6f, 0x6f)));
130 | ZMTPHandshake handshake = h.handshake(buf(
131 | 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0x7f, 0x1, 0x1, 0, 0x03, 0x62, 0x61, 0x72), ctx);
132 | assertThat(handshake, is(notNullValue()));
133 | assertEquals(ZMTPHandshake.of(ZMTPVersion.ZMTP20, BAR, PUB), handshake);
134 | }
135 |
136 |
137 | @Test
138 | public void test2To1Handshake() {
139 | ZMTPHandshaker h = new ZMTP20Protocol.Handshaker(PUB, FOO, false);
140 | assertThat(h.greeting(), is(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0x7f, 0x1, 0x1, 0, 0x3, 0x66, 0x6f, 0x6f)));
141 | try {
142 | assertThat(h.handshake(buf(0x04, 0, 0x62, 0x61, 0x72), ctx), is(nullValue()));
143 | fail("An ZMTP/1 greeting is invalid in plain ZMTP/2. Should have thrown exception");
144 | } catch (ZMTPException e) {
145 | // pass
146 | }
147 | }
148 |
149 | @Test
150 | public void test2To2CompatTruncated() throws Exception {
151 | final ByteBuffer identity = UTF_8.encode("identity");
152 | ZMTP20Protocol.Handshaker h = new ZMTP20Protocol.Handshaker(PUB, identity, true);
153 | assertThat(h.greeting(), is(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 9, 0x7f)));
154 | ZMTPHandshake handshake = h.handshake(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 1, 0x7f, 1, 5), ctx);
155 | assertThat(handshake, is(nullValue()));
156 | verify(ctx).writeAndFlush(buf(1, 1, 0, 8, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79));
157 | }
158 |
159 | @Test
160 | public void testReadZMTP2Greeting() throws Exception {
161 | final ByteBuf in = buf(0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0x7f, 0x01, 0x02, 0x00, 0x01, 0x61);
162 | final ZMTP20WireFormat.Greeting greeting = ZMTP20WireFormat.readGreeting(in);
163 | assertThat(greeting.identity(), is(UTF_8.encode("a")));
164 | }
165 |
166 | @Test
167 | public void testReadZMTP1RemoteIdentity() throws Exception {
168 | ByteBuffer identity = ZMTP10WireFormat.readIdentity(buf(0x04, 0x00, 0x62, 0x61, 0x72));
169 | assertThat(identity, is(notNullValue()));
170 | assertEquals(BAR, identity);
171 |
172 | // anonymous handshake
173 | identity = ZMTP10WireFormat.readIdentity(buf(0x01, 0x00));
174 | assertThat(identity, is(notNullValue()));
175 | assert identity != null;
176 | assertThat(identity.remaining(), is(0));
177 | }
178 |
179 | @Test
180 | public void testTypeToConst() {
181 | assertEquals(8, ZMTPSocketType.PUSH.ordinal());
182 | }
183 |
184 | @Test
185 | public void testDetectProtocolVersion() {
186 | try {
187 | ZMTP20WireFormat.detectProtocolVersion(Unpooled.wrappedBuffer(new byte[0]));
188 | fail("Should have thrown IndexOutOfBoundsException");
189 | } catch (IndexOutOfBoundsException e) {
190 | // ignore
191 | }
192 | try {
193 | ZMTP20WireFormat.detectProtocolVersion(buf(0xff, 0, 0, 0));
194 | fail("Should have thrown IndexOutOfBoundsException");
195 | } catch (IndexOutOfBoundsException e) {
196 | // ignore
197 | }
198 |
199 | assertEquals(ZMTP10, ZMTP20WireFormat.detectProtocolVersion(buf(0x07)));
200 | assertEquals(ZMTP10, ZMTP20WireFormat
201 | .detectProtocolVersion(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 1, 0)));
202 | assertEquals(ZMTP20, ZMTP20WireFormat
203 | .detectProtocolVersion(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 1, 1)));
204 | }
205 |
206 | @Test
207 | public void testReadZMTP2GreetingMalformed() {
208 | try {
209 | ByteBuf in = buf(0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0x7f, 0x01, 0x02, 0xf0, 0x01, 0x61);
210 | ZMTP20WireFormat.readGreeting(in);
211 | fail("13th byte is not 0x00, should throw exception");
212 | } catch (ZMTPException e) {
213 | // pass
214 | }
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/ListenableFutureAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import com.google.common.util.concurrent.AbstractFuture;
20 | import com.google.common.util.concurrent.ListenableFuture;
21 |
22 | import io.netty.util.concurrent.Future;
23 | import io.netty.util.concurrent.GenericFutureListener;
24 |
25 | public class ListenableFutureAdapter extends AbstractFuture implements GenericFutureListener> {
26 |
27 | static ListenableFuture listenable(final Future future) {
28 | final ListenableFutureAdapter adapter = new ListenableFutureAdapter();
29 | future.addListener(adapter);
30 | return adapter;
31 | }
32 |
33 | @Override
34 | public void operationComplete(final Future future) throws Exception {
35 | if (future.isSuccess()) {
36 | set(future.getNow());
37 | } else {
38 | setException(future.cause());
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/PipelineTester.java:
--------------------------------------------------------------------------------
1 | package com.spotify.netty4.handler.codec.zmtp;
2 |
3 | import java.util.concurrent.BlockingQueue;
4 | import java.util.concurrent.LinkedBlockingQueue;
5 | import java.util.concurrent.atomic.AtomicInteger;
6 |
7 | import io.netty.bootstrap.Bootstrap;
8 | import io.netty.bootstrap.ServerBootstrap;
9 | import io.netty.buffer.ByteBuf;
10 | import io.netty.channel.Channel;
11 | import io.netty.channel.ChannelHandler;
12 | import io.netty.channel.ChannelHandlerContext;
13 | import io.netty.channel.ChannelInboundHandlerAdapter;
14 | import io.netty.channel.ChannelInitializer;
15 | import io.netty.channel.local.LocalAddress;
16 | import io.netty.channel.local.LocalChannel;
17 | import io.netty.channel.local.LocalEventLoopGroup;
18 | import io.netty.channel.local.LocalServerChannel;
19 | import io.netty.util.ReferenceCountUtil;
20 |
21 | /**
22 | * Tests the behaviours of ChannelPipelines, using the local transport method.
23 | * A PipelineTester instance has two ends named server and client where the server is the
24 | * position furthest upstream in the pipeline, and client is outside of the pipeline reading from
25 | * and writing to it.
26 | */
27 | class PipelineTester {
28 | private final BlockingQueue emittedOutside = new LinkedBlockingQueue();
29 | private final BlockingQueue emittedInside = new LinkedBlockingQueue();
30 | private Channel outerChannel = null;
31 | private Channel innerChannel = null;
32 |
33 | private static final AtomicInteger port = new AtomicInteger();
34 |
35 | /**
36 | * Constructs a server using pipeline and a client communicating with it.
37 | *
38 | * @param handlers Server channel handlers.
39 | */
40 | public PipelineTester(final ChannelHandler... handlers) {
41 | final LocalAddress address = new LocalAddress("pipeline-tester-" + port.incrementAndGet());
42 |
43 | final ServerBootstrap sb = new ServerBootstrap();
44 | sb.group(new LocalEventLoopGroup(1), new LocalEventLoopGroup());
45 | sb.channel(LocalServerChannel.class);
46 | sb.childHandler(new ChannelInitializer() {
47 | @Override
48 | protected void initChannel(final LocalChannel ch) throws Exception {
49 | ch.pipeline().addLast(handlers);
50 | ch.pipeline().addLast("pipelineTesterEndpoint", new ChannelInboundHandlerAdapter() {
51 |
52 | @Override
53 | public void channelRead(final ChannelHandlerContext ctx, final Object msg)
54 | throws Exception {
55 | ReferenceCountUtil.releaseLater(msg);
56 | emittedInside.put(msg);
57 | }
58 |
59 | @Override
60 | public void channelActive(final ChannelHandlerContext ctx) throws Exception {
61 | innerChannel = ctx.channel();
62 | }
63 | });
64 | }
65 | });
66 | sb.bind(address).awaitUninterruptibly();
67 |
68 | final Bootstrap cb = new Bootstrap();
69 | cb.group(new LocalEventLoopGroup());
70 | cb.channel(LocalChannel.class);
71 | cb.handler(new ChannelInitializer() {
72 | @Override
73 | protected void initChannel(final LocalChannel ch) throws Exception {
74 | ch.pipeline().addLast("1", new ChannelInboundHandlerAdapter() {
75 | @Override
76 | public void channelActive(final ChannelHandlerContext ctx) throws Exception {
77 | outerChannel = ctx.channel();
78 | }
79 |
80 | @Override
81 | public void channelRead(final ChannelHandlerContext ctx, final Object msg)
82 | throws Exception {
83 | ReferenceCountUtil.releaseLater(msg);
84 | emittedOutside.put((ByteBuf) msg);
85 | }
86 | });
87 | }
88 | });
89 |
90 | cb.connect(address).awaitUninterruptibly();
91 | }
92 |
93 | /**
94 | * Read the ChannelBuffers emitted from this pipeline in the client end.
95 | *
96 | * @return a ByteBuf from the the client FIFO
97 | */
98 | public ByteBuf readClient() {
99 | try {
100 | return emittedOutside.take();
101 | } catch (InterruptedException e) {
102 | throw new Error(e);
103 | }
104 | }
105 |
106 | /**
107 | * Write a ByteBuf to the pipeline from the client end.
108 | *
109 | * @param buf the ByteBuf to write
110 | */
111 | public void writeClient(ByteBuf buf) {
112 | outerChannel.writeAndFlush(buf);
113 | }
114 |
115 | /**
116 | * Read an Object from the server end of the pipeline. This can be a ByteBuf, or, if
117 | * there is a FrameDecoder some sort of pojo.
118 | *
119 | * @return an Object read from the server end.
120 | */
121 | public Object readServer() {
122 | try {
123 | return emittedInside.take();
124 | } catch (InterruptedException e) {
125 | throw new Error(e);
126 | }
127 | }
128 |
129 | /**
130 | * Write a Object to the server end of the pipeline.
131 | *
132 | * @param message the Object to be written
133 | */
134 | public void writeServer(Object message) {
135 | innerChannel.writeAndFlush(message);
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/PipelineTests.java:
--------------------------------------------------------------------------------
1 | package com.spotify.netty4.handler.codec.zmtp;
2 |
3 | import org.junit.Test;
4 |
5 | import io.netty.buffer.ByteBuf;
6 | import io.netty.buffer.Unpooled;
7 | import io.netty.channel.ChannelHandlerContext;
8 | import io.netty.channel.ChannelInboundHandlerAdapter;
9 |
10 | import static com.google.common.base.Strings.repeat;
11 | import static com.spotify.netty4.handler.codec.zmtp.Buffers.buf;
12 | import static com.spotify.netty4.handler.codec.zmtp.Buffers.bytes;
13 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPProtocols.ZMTP10;
14 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPProtocols.ZMTP20;
15 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPSocketType.REQ;
16 | import static io.netty.util.CharsetUtil.UTF_8;
17 | import static org.hamcrest.Matchers.is;
18 | import static org.junit.Assert.assertThat;
19 |
20 | /**
21 | * These tests has a full pipeline setup.
22 | */
23 | public class PipelineTests {
24 |
25 | // FIXME (dano): Tests are hardcoded to use a message 260 bytes long
26 | private static final byte[] LONG_MSG = repeat("data", 100).substring(0, 260).getBytes(UTF_8);
27 |
28 | /**
29 | * First let's just exercise the PipelineTester a bit.
30 | */
31 | @Test
32 | public void testPipelineTester() {
33 | final ByteBuf buf = Unpooled.copiedBuffer("Hello, world", UTF_8);
34 |
35 | final PipelineTester pipelineTester = new PipelineTester(new ChannelInboundHandlerAdapter() {
36 |
37 | @Override
38 | public void channelActive(final ChannelHandlerContext ctx) throws Exception {
39 | super.channelActive(ctx);
40 | ctx.channel().writeAndFlush(buf);
41 | }
42 | });
43 | assertThat(pipelineTester.readClient(), is(buf));
44 |
45 | final ByteBuf foo = Unpooled.copiedBuffer("foo", UTF_8);
46 | pipelineTester.writeClient(foo.retain());
47 |
48 | assertThat(foo, is(pipelineTester.readServer()));
49 |
50 | final ByteBuf bar = Unpooled.copiedBuffer("bar", UTF_8);
51 | pipelineTester.writeServer(bar.retain());
52 | assertThat(bar, is(pipelineTester.readClient()));
53 | }
54 |
55 | @Test
56 | public void testZMTPPipeline() {
57 |
58 | final PipelineTester pt = new PipelineTester(
59 | ZMTPCodec.builder()
60 | .protocol(ZMTP20)
61 | .socketType(REQ)
62 | .localIdentity("foo")
63 | .build());
64 | assertThat(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 4, 0x7f), is(pt.readClient()));
65 | pt.writeClient(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0x7f, 1, 4, 0, 1, 0x63));
66 | assertThat(buf(1, 3, 0, 3, 0x66, 0x6f, 0x6f), is(pt.readClient()));
67 |
68 | pt.writeClient(buf(1, 1, 0x65, 1, 0, 0, 1, 0x62));
69 | ZMTPMessage m = (ZMTPMessage) pt.readServer();
70 |
71 | assertThat(m.size(), is(3));
72 | assertThat(buf(0x65), is(m.frame(0)));
73 | assertThat(buf(), is(m.frame(1)));
74 | assertThat(buf(0x62), is(m.frame(2)));
75 | }
76 |
77 | @Test
78 | public void testZMTPPipelineFragmented() {
79 | final PipelineTester pt = new PipelineTester(
80 | ZMTPCodec.builder()
81 | .protocol(ZMTP20)
82 | .socketType(REQ)
83 | .localIdentity("foo")
84 | .build());
85 |
86 | assertThat(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 4, 0x7f), is(pt.readClient()));
87 | pt.writeClient(buf(0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0x7f, 1, 4, 0, 1, 0x63, 1, 1, 0x65, 1));
88 | assertThat(buf(1, 3, 0, 3, 0x66, 0x6f, 0x6f), is(pt.readClient()));
89 |
90 | pt.writeClient(buf(0, 0, 1, 0x62));
91 | ZMTPMessage m = (ZMTPMessage) pt.readServer();
92 |
93 | assertThat(m.size(), is(3));
94 | assertThat(buf(0x65), is(m.frame(0)));
95 | assertThat(buf(), is(m.frame(1)));
96 | assertThat(buf(0x62), is(m.frame(2)));
97 | }
98 |
99 | @Test
100 | public void testZMTP1PipelineLongMessage() {
101 | final PipelineTester pt = new PipelineTester(
102 | ZMTPCodec.builder()
103 | .protocol(ZMTP10)
104 | .socketType(REQ)
105 | .localIdentity("foo")
106 | .build());
107 |
108 | assertThat(buf(0x04, 0, 0x66, 0x6f, 0x6f), is(pt.readClient()));
109 |
110 | ByteBuf cb = Unpooled.buffer();
111 | // handshake: length + flag + client identity octets "BAR"
112 | cb.writeBytes(buf(4, 0, 0x62, 0x61, 0x72));
113 | // two octet envelope delimiter
114 | cb.writeBytes(bytes(0x01, 0x01));
115 | // content frame size + flag octet
116 | cb.writeBytes(bytes(0x0ff, 0, 0, 0, 0, 0, 0, 0x01, 0x05, 0));
117 | // payload
118 | cb.writeBytes(LONG_MSG);
119 |
120 | pt.writeClient(cb);
121 | ZMTPMessage m = (ZMTPMessage) pt.readServer();
122 |
123 | assertThat(m.size(), is(2));
124 | assertThat(buf(), is(m.frame(0)));
125 | assertThat(buf(LONG_MSG), is(m.frame(1)));
126 | }
127 |
128 | @Test
129 | public void testZMTP1PipelineFragmentedHandshake() {
130 | doTestZMTP1PipelineFragmentedHandshake(buf(4), buf(0, 0x62, 0x61, 0x72));
131 | doTestZMTP1PipelineFragmentedHandshake(buf(4, 0), buf(0x62, 0x61, 0x72));
132 | doTestZMTP1PipelineFragmentedHandshake(buf(4, 0, 0x62), buf(0x61, 0x72));
133 | doTestZMTP1PipelineFragmentedHandshake(buf(4, 0, 0x62, 0x61), buf(0x72));
134 | }
135 |
136 | private void doTestZMTP1PipelineFragmentedHandshake(ByteBuf first, ByteBuf second) {
137 | final PipelineTester pt = new PipelineTester(
138 | ZMTPCodec.builder()
139 | .protocol(ZMTP10)
140 | .socketType(REQ)
141 | .localIdentity("foo")
142 | .build());
143 |
144 | assertThat(buf(0x04, 0, 0x66, 0x6f, 0x6f), is(pt.readClient()));
145 |
146 | // write both fragments of client handshake
147 | pt.writeClient(first);
148 | pt.writeClient(second);
149 |
150 | ByteBuf cb = Unpooled.buffer();
151 | // two octet envelope delimiter
152 | cb.writeBytes(bytes(0x01, 0x01));
153 | // content frame size + flag octet
154 | cb.writeBytes(bytes(0x0ff, 0, 0, 0, 0, 0, 0, 0x01, 0x05, 0));
155 | // payload
156 | cb.writeBytes(LONG_MSG);
157 |
158 | pt.writeClient(cb);
159 | ZMTPMessage m = (ZMTPMessage) pt.readServer();
160 |
161 | assertThat(m.size(), is(2));
162 | assertThat(buf(), is(m.frame(0)));
163 | assertThat(buf(LONG_MSG), is(m.frame(1)));
164 | }
165 |
166 |
167 | @Test
168 | // tests the case when the message to be parsed is fragmented inside the long long size field
169 | public void testZMTP1PipelineLongMessageFragmentedLong() {
170 | final PipelineTester pt = new PipelineTester(
171 | ZMTPCodec.builder()
172 | .protocol(ZMTP10)
173 | .socketType(REQ)
174 | .localIdentity("foo")
175 | .build());
176 |
177 | assertThat(buf(0x04, 0, 0x66, 0x6f, 0x6f), is(pt.readClient()));
178 |
179 | ByteBuf cb = Unpooled.buffer();
180 | // handshake: length + flag + client identity octets "BAR"
181 | cb.writeBytes(buf(4, 0, 0x62, 0x61, 0x72));
182 | // two octet envelope delimiter
183 | cb.writeBytes(bytes(0x01, 0x01));
184 | // fragmented first part of frame size
185 | cb.writeBytes(bytes(0x0ff, 0, 0));
186 |
187 | pt.writeClient(cb);
188 |
189 | cb = Unpooled.buffer();
190 | // fragmented second part of frame size
191 | cb.writeBytes(bytes(0, 0, 0, 0, 0x01, 0x05, 0));
192 | // payload
193 | cb.writeBytes(LONG_MSG);
194 |
195 | pt.writeClient(cb);
196 |
197 | ZMTPMessage m = (ZMTPMessage) pt.readServer();
198 |
199 | assertThat(m.size(), is(2));
200 | assertThat(buf(), is(m.frame(0)));
201 | assertThat(buf(LONG_MSG), is(m.frame(1)));
202 | }
203 |
204 | @Test
205 | // tests the case when the message to be parsed is fragmented between 0xff flag and 8 octet length
206 | public void testZMTP1PipelineLongMessageFragmentedSize() {
207 | final PipelineTester pt = new PipelineTester(
208 | ZMTPCodec.builder()
209 | .protocol(ZMTP10)
210 | .socketType(REQ)
211 | .localIdentity("foo")
212 | .build());
213 |
214 | assertThat(buf(0x04, 0, 0x66, 0x6f, 0x6f), is(pt.readClient()));
215 |
216 | ByteBuf cb = Unpooled.buffer();
217 | // handshake: length + flag + client identity octets "BAR"
218 | cb.writeBytes(buf(4, 0, 0x62, 0x61, 0x72));
219 | // two octet envelope delimiter
220 | cb.writeBytes(bytes(0x01, 0x01));
221 | // fragmented first part of frame size
222 | cb.writeBytes(bytes(0x0ff));
223 |
224 | pt.writeClient(cb);
225 |
226 | cb = Unpooled.buffer();
227 | // fragmented second part of frame size
228 | cb.writeBytes(bytes(0, 0, 0, 0, 0, 0, 0x01, 0x05, 0));
229 | // payload
230 | cb.writeBytes(LONG_MSG);
231 |
232 | pt.writeClient(cb);
233 |
234 | ZMTPMessage m = (ZMTPMessage) pt.readServer();
235 |
236 | assertThat(m.size(), is(2));
237 | assertThat(buf(), is(m.frame(0)));
238 | assertThat(buf(LONG_MSG), is(m.frame(1)));
239 | }
240 |
241 |
242 | @Test
243 | // tests fragmentation in the size field of the second message
244 | public void testZMTP1PipelineMultiMessage() {
245 | final PipelineTester pt = new PipelineTester(
246 | ZMTPCodec.builder()
247 | .protocol(ZMTP10)
248 | .socketType(REQ)
249 | .localIdentity("foo")
250 | .build());
251 |
252 | assertThat(buf(0x04, 0, 0x66, 0x6f, 0x6f), is(pt.readClient()));
253 |
254 | ByteBuf cb = Unpooled.buffer();
255 | // handshake: length + flag + client identity octets "BAR"
256 | cb.writeBytes(buf(4, 0, 0x62, 0x61, 0x72));
257 | // two octet envelope delimiter
258 | cb.writeBytes(bytes(0x01, 0x01));
259 | // content frame size + flag octet
260 | cb.writeBytes(bytes(0x0ff, 0, 0, 0, 0, 0, 0, 0x01, 0x05, 0));
261 | // payload
262 | cb.writeBytes(LONG_MSG);
263 | // second message, first fragment
264 | // fragmented first part of frame size
265 | cb.writeBytes(bytes(1, 1, 0x0ff, 0, 0));
266 |
267 | pt.writeClient(cb);
268 | ZMTPMessage m = (ZMTPMessage) pt.readServer();
269 |
270 | assertThat(m.size(), is(2));
271 | assertThat(buf(), is(m.frame(0)));
272 | assertThat(buf(LONG_MSG), is(m.frame(1)));
273 |
274 | // send the rest of the second message
275 | cb = Unpooled.buffer();
276 | // fragmented second part of frame size
277 | cb.writeBytes(bytes(0, 0, 0, 0, 0x01, 0x05, 0));
278 | // payload
279 | cb.writeBytes(LONG_MSG);
280 | pt.writeClient(cb);
281 |
282 | m = (ZMTPMessage) pt.readServer();
283 |
284 | assertThat(m.size(), is(2));
285 | assertThat(buf(), is(m.frame(0)));
286 | assertThat(buf(LONG_MSG), is(m.frame(1)));
287 | }
288 |
289 | }
290 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/ProtocolViolationTests.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2013 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import com.google.common.util.concurrent.SettableFuture;
20 |
21 | import org.junit.After;
22 | import org.junit.Before;
23 | import org.junit.experimental.theories.Theories;
24 | import org.junit.experimental.theories.Theory;
25 | import org.junit.experimental.theories.suppliers.TestedOn;
26 | import org.junit.runner.RunWith;
27 |
28 | import java.net.InetSocketAddress;
29 |
30 | import io.netty.bootstrap.Bootstrap;
31 | import io.netty.bootstrap.ServerBootstrap;
32 | import io.netty.buffer.ByteBuf;
33 | import io.netty.buffer.Unpooled;
34 | import io.netty.channel.Channel;
35 | import io.netty.channel.ChannelHandler;
36 | import io.netty.channel.ChannelHandlerContext;
37 | import io.netty.channel.ChannelInboundHandlerAdapter;
38 | import io.netty.channel.ChannelInitializer;
39 | import io.netty.channel.nio.NioEventLoopGroup;
40 | import io.netty.channel.socket.nio.NioServerSocketChannel;
41 | import io.netty.channel.socket.nio.NioSocketChannel;
42 | import io.netty.util.ReferenceCountUtil;
43 |
44 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPProtocols.ZMTP20;
45 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPSocketType.ROUTER;
46 | import static java.util.concurrent.TimeUnit.SECONDS;
47 | import static org.junit.Assert.assertFalse;
48 |
49 | @RunWith(Theories.class)
50 | public class ProtocolViolationTests {
51 |
52 | private Channel serverChannel;
53 | private InetSocketAddress serverAddress;
54 |
55 | private final String identity = "identity";
56 | private NioEventLoopGroup bossGroup;
57 | private NioEventLoopGroup group;
58 |
59 | @ChannelHandler.Sharable
60 | private static class MockHandler extends ChannelInboundHandlerAdapter {
61 |
62 | private SettableFuture active = SettableFuture.create();
63 | private SettableFuture exception = SettableFuture.create();
64 | private SettableFuture inactive = SettableFuture.create();
65 |
66 | private volatile boolean handshaked;
67 | private volatile boolean read;
68 |
69 | @Override
70 | public void channelActive(final ChannelHandlerContext ctx) throws Exception {
71 | active.set(null);
72 | }
73 |
74 | @Override
75 | public void channelInactive(final ChannelHandlerContext ctx) throws Exception {
76 | inactive.set(null);
77 | }
78 |
79 | @Override
80 | public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception {
81 | if (evt instanceof ZMTPHandshakeSuccess) {
82 | handshaked = true;
83 | }
84 | }
85 |
86 | @Override
87 | public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
88 | ReferenceCountUtil.release(msg);
89 | read = true;
90 | }
91 |
92 | @Override
93 | public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause)
94 | throws Exception {
95 | exception.set(cause);
96 | ctx.close();
97 | }
98 | }
99 |
100 | private final MockHandler mockHandler = new MockHandler();
101 |
102 | @Before
103 | public void setup() {
104 | final ServerBootstrap serverBootstrap = new ServerBootstrap();
105 | serverBootstrap.channel(NioServerSocketChannel.class);
106 | bossGroup = new NioEventLoopGroup(1);
107 | group = new NioEventLoopGroup();
108 | serverBootstrap.group(bossGroup, group);
109 | serverBootstrap.childHandler(new ChannelInitializer() {
110 | @Override
111 | protected void initChannel(final NioSocketChannel ch) throws Exception {
112 | ch.pipeline().addLast(
113 | ZMTPCodec.builder()
114 | .protocol(ZMTP20)
115 | .socketType(ROUTER)
116 | .localIdentity(identity)
117 | .build(),
118 | mockHandler);
119 | }
120 | });
121 |
122 | serverChannel = serverBootstrap.bind(new InetSocketAddress("localhost", 0))
123 | .awaitUninterruptibly().channel();
124 | serverAddress = (InetSocketAddress) serverChannel.localAddress();
125 | }
126 |
127 | @After
128 | public void teardown() {
129 | if (serverChannel != null) {
130 | serverChannel.close();
131 | }
132 | if (bossGroup != null) {
133 | bossGroup.shutdownGracefully();
134 | }
135 | if (group != null) {
136 | group.shutdownGracefully();
137 | }
138 | }
139 |
140 | @Theory
141 | public void protocolErrorsCauseException(
142 | @TestedOn(ints = {16, 17, 27, 32, 48, 53}) final int payloadSize) throws Exception {
143 | final Bootstrap b = new Bootstrap();
144 | b.group(new NioEventLoopGroup());
145 | b.channel(NioSocketChannel.class);
146 | b.handler(new ChannelInitializer() {
147 | @Override
148 | protected void initChannel(final NioSocketChannel ch) throws Exception {
149 | ch.pipeline().addLast(new MockHandler());
150 | }
151 | });
152 |
153 | final Channel channel = b.connect(serverAddress).awaitUninterruptibly().channel();
154 |
155 | final ByteBuf payload = Unpooled.buffer(payloadSize);
156 | for (int i = 0; i < payloadSize; i++) {
157 | payload.writeByte(0);
158 | }
159 | channel.writeAndFlush(payload);
160 |
161 | mockHandler.active.get(5, SECONDS);
162 | mockHandler.exception.get(5, SECONDS);
163 | mockHandler.inactive.get(5, SECONDS);
164 | assertFalse(mockHandler.handshaked);
165 | assertFalse(mockHandler.read);
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/VerifyingDecoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import java.util.List;
20 |
21 | import io.netty.buffer.ByteBuf;
22 | import io.netty.channel.ChannelHandlerContext;
23 |
24 | public class VerifyingDecoder implements ZMTPDecoder {
25 |
26 | private ExpectedOutput expected;
27 |
28 | private int readIndex;
29 | private boolean finished;
30 | private long frameSize;
31 |
32 | public VerifyingDecoder(final ExpectedOutput expected) {
33 | this.expected = expected;
34 | }
35 |
36 | public VerifyingDecoder() {
37 | }
38 |
39 | public void expect(ExpectedOutput expected) {
40 | this.expected = expected;
41 | }
42 |
43 | @Override
44 | public void header(final ChannelHandlerContext ctx, final long length, final boolean more,
45 | final List out) {
46 | if (finished) {
47 | throw new IllegalStateException("already finished");
48 | }
49 | if (readIndex >= expected.message.size()) {
50 | throw new IllegalStateException(
51 | "more frames than expected: " +
52 | "readIndex=" + readIndex + ", " +
53 | "expected=" + expected +
54 | ", frame(size=" + length +
55 | ", more=" + more + ")");
56 | }
57 | frameSize = length;
58 | }
59 |
60 | @Override
61 | public void content(final ChannelHandlerContext ctx, final ByteBuf data, final List out) {
62 | if (data.readableBytes() < frameSize) {
63 | return;
64 | }
65 | final ByteBuf expectedFrame = expected.message.frame(readIndex);
66 | final ByteBuf frame = data.readBytes((int) frameSize);
67 | if (!expectedFrame.equals(frame)) {
68 | throw new IllegalStateException(
69 | "read frame did not match expected frame: " +
70 | "readIndex=" + readIndex + ", " +
71 | "expected frame=" + expectedFrame +
72 | "read frame=" + frame);
73 | }
74 | readIndex++;
75 | }
76 |
77 | @Override
78 | public void finish(final ChannelHandlerContext ctx, final List out) {
79 | if (finished) {
80 | throw new IllegalStateException("already finished");
81 | }
82 | if (readIndex != expected.message.size()) {
83 | throw new IllegalStateException(
84 | "less than expected frames read: " +
85 | "readIndex=" + readIndex + ", " +
86 | "expected=" + expected);
87 | }
88 | readIndex = 0;
89 | finished = true;
90 | }
91 |
92 | @Override
93 | public void close() {
94 | }
95 |
96 | public void assertFinished() {
97 | if (!finished) {
98 | throw new AssertionError("not finished");
99 | }
100 | finished = false;
101 | }
102 |
103 | static class ExpectedOutput {
104 |
105 | private final ZMTPMessage message;
106 |
107 | public ExpectedOutput(final ZMTPMessage message) {
108 | this.message = message;
109 | }
110 |
111 | @Override
112 | public String toString() {
113 | return message.toString();
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/ZMQIntegrationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import com.google.common.base.Strings;
20 | import com.google.common.util.concurrent.ListenableFuture;
21 |
22 | import org.junit.After;
23 | import org.junit.Before;
24 | import org.junit.experimental.theories.DataPoints;
25 | import org.junit.experimental.theories.FromDataPoints;
26 | import org.junit.experimental.theories.Theories;
27 | import org.junit.experimental.theories.Theory;
28 | import org.junit.runner.RunWith;
29 | import org.mockito.ArgumentCaptor;
30 | import org.mockito.Mockito;
31 | import org.zeromq.ZMQ;
32 | import org.zeromq.ZMsg;
33 |
34 | import java.net.InetSocketAddress;
35 | import java.util.List;
36 | import java.util.concurrent.ExecutionException;
37 | import java.util.concurrent.TimeUnit;
38 | import java.util.concurrent.TimeoutException;
39 |
40 | import io.netty.util.ReferenceCountUtil;
41 |
42 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPProtocols.ZMTP10;
43 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPProtocols.ZMTP20;
44 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPSocketType.DEALER;
45 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPSocketType.ROUTER;
46 | import static io.netty.util.CharsetUtil.UTF_8;
47 | import static org.hamcrest.Matchers.is;
48 | import static org.hamcrest.Matchers.isEmptyString;
49 | import static org.hamcrest.Matchers.not;
50 | import static org.junit.Assert.assertEquals;
51 | import static org.junit.Assert.assertThat;
52 | import static org.junit.Assume.assumeFalse;
53 | import static org.mockito.Matchers.any;
54 | import static org.mockito.Matchers.eq;
55 | import static org.mockito.Mockito.timeout;
56 | import static org.mockito.Mockito.verify;
57 |
58 | @RunWith(Theories.class)
59 | public class ZMQIntegrationTest {
60 |
61 | private static final String ANONYMOUS = "";
62 | private static final String IDENTITY = "zmq-integration-test";
63 | private static final String MIN_IDENTITY = "z";
64 | private static final String MAX_IDENTITY = Strings.repeat("z", 255);
65 |
66 | @DataPoints("identities")
67 | public static final String[] IDENTITIES = {IDENTITY, MIN_IDENTITY, MAX_IDENTITY, ANONYMOUS};
68 |
69 | @DataPoints("versions")
70 | public static final ZMTPProtocol[] PROTOCOLS = {ZMTP10, ZMTP20};
71 |
72 | private final ZMTPSocket.Handler handler =
73 | Mockito.mock(ZMTPSocket.Handler.class);
74 | private final ArgumentCaptor messageCaptor =
75 | ArgumentCaptor.forClass(ZMTPMessage.class);
76 |
77 | private ZMQ.Context context;
78 |
79 | private ZMTPSocket zmtpSocket;
80 |
81 | private int port;
82 | private ZMQ.Socket zmqSocket;
83 |
84 | @Before
85 | public void setUp() {
86 | context = ZMQ.context(1);
87 | }
88 |
89 | @After
90 | public void tearDown() {
91 | if (zmtpSocket != null) {
92 | zmtpSocket.close();
93 | }
94 | if (zmqSocket != null) {
95 | zmqSocket.close();
96 | }
97 | if (context != null) {
98 | context.close();
99 | }
100 | }
101 |
102 | @Theory
103 | public void test_NettyBindRouter_ZmqConnectDealer(
104 | @FromDataPoints("identities") final String zmqIdentity,
105 | @FromDataPoints("identities") final String nettyIdentity,
106 | @FromDataPoints("versions") final ZMTPProtocol nettyProtocol
107 | )
108 | throws TimeoutException, InterruptedException, ExecutionException {
109 | // XXX (dano): jeromq fails on identities longer than 127 bytes due to a signedness issue
110 | assumeFalse(MAX_IDENTITY.equals(zmqIdentity));
111 |
112 | final ZMTPSocket router = nettyBind(ROUTER, nettyIdentity, nettyProtocol);
113 | final ZMQ.Socket dealer = zmqConnect(ZMQ.DEALER, zmqIdentity);
114 |
115 | testReqRep(dealer, router, zmqIdentity);
116 | }
117 |
118 | @Theory
119 | public void test_NettyBindDealer_ZmqConnectRouter(
120 | @FromDataPoints("identities") final String zmqIdentity,
121 | @FromDataPoints("identities") final String nettyIdentity,
122 | @FromDataPoints("versions") final ZMTPProtocol nettyProtocol
123 | )
124 | throws InterruptedException, TimeoutException, ExecutionException {
125 | // XXX (dano): jeromq fails on identities longer than 127 bytes due to a signedness issue
126 | assumeFalse(MAX_IDENTITY.equals(zmqIdentity));
127 |
128 | final ZMTPSocket dealer = nettyBind(DEALER, nettyIdentity, nettyProtocol);
129 | final ZMQ.Socket router = zmqConnect(ZMQ.ROUTER, zmqIdentity);
130 | testReqRep(dealer, router, zmqIdentity);
131 | }
132 |
133 | @Theory
134 | public void test_ZmqBindRouter_NettyConnectDealer(
135 | @FromDataPoints("identities") final String zmqIdentity,
136 | @FromDataPoints("identities") final String nettyIdentity,
137 | @FromDataPoints("versions") final ZMTPProtocol nettyProtocol
138 | )
139 | throws InterruptedException, TimeoutException, ExecutionException {
140 | // XXX (dano): jeromq fails on identities longer than 127 bytes due to a signedness issue
141 | assumeFalse(MAX_IDENTITY.equals(zmqIdentity));
142 |
143 | final ZMQ.Socket router = zmqBind(ZMQ.ROUTER, zmqIdentity);
144 | final ZMTPSocket dealer = nettyConnect(DEALER, nettyIdentity, nettyProtocol);
145 | testReqRep(dealer, router, zmqIdentity);
146 | }
147 |
148 | @Theory
149 | public void test_ZmqBindDealer_NettyConnectRouter(
150 | @FromDataPoints("identities") final String zmqIdentity,
151 | @FromDataPoints("identities") final String nettyIdentity,
152 | @FromDataPoints("versions") final ZMTPProtocol nettyProtocol
153 | )
154 | throws TimeoutException, InterruptedException, ExecutionException {
155 | // XXX (dano): jeromq fails on identities longer than 127 bytes due to a signedness issue
156 | assumeFalse(MAX_IDENTITY.equals(zmqIdentity));
157 |
158 | final ZMQ.Socket dealer = zmqBind(ZMQ.DEALER, zmqIdentity);
159 | final ZMTPSocket router = nettyConnect(ROUTER, nettyIdentity, nettyProtocol);
160 | testReqRep(dealer, router, zmqIdentity);
161 | }
162 |
163 | private void testReqRep(final ZMQ.Socket req, final ZMTPSocket rep, final String zmqIdentity)
164 | throws InterruptedException, TimeoutException {
165 |
166 | // Verify that sockets are connected
167 | verify(handler, timeout(5000)).connected(eq(rep), any(ZMTPSocket.ZMTPPeer.class));
168 |
169 | // Verify that the peer identity was correctly received
170 | verifyPeerIdentity(rep, zmqIdentity);
171 |
172 | // Send request
173 | final ZMsg request = ZMsg.newStringMsg("envelope", "", "hello", "world");
174 | request.send(req, false);
175 |
176 | // Receive request
177 | verify(handler, timeout(5000)).message(
178 | eq(rep), any(ZMTPSocket.ZMTPPeer.class), messageCaptor.capture());
179 | final ZMTPMessage receivedRequest = messageCaptor.getValue();
180 |
181 | // Send reply
182 | rep.send(receivedRequest);
183 |
184 | // Receive reply
185 | final ZMsg reply = ZMsg.recvMsg(req);
186 |
187 | // Verify echo
188 | assertEquals(request, reply);
189 | }
190 |
191 | private void testReqRep(final ZMTPSocket req, final ZMQ.Socket rep, final String zmqIdentity)
192 | throws InterruptedException, TimeoutException {
193 |
194 | // Verify that sockets are connected
195 | verify(handler, timeout(5000)).connected(eq(req), any(ZMTPSocket.ZMTPPeer.class));
196 |
197 | // Verify that the peer identity was correctly received
198 | verifyPeerIdentity(req, zmqIdentity);
199 |
200 | // Send request
201 | final ZMTPMessage request = ZMTPMessage.fromUTF8("envelope", "", "hello", "world");
202 | request.retain();
203 | req.send(request);
204 |
205 | // Receive request
206 | final ZMsg receivedRequest = ZMsg.recvMsg(rep);
207 |
208 | // Send reply
209 | receivedRequest.send(rep, false);
210 |
211 | // Receive reply
212 | verify(handler, timeout(5000)).message(
213 | eq(req), any(ZMTPSocket.ZMTPPeer.class), messageCaptor.capture());
214 | final ZMTPMessage reply = messageCaptor.getValue();
215 | ReferenceCountUtil.releaseLater(reply);
216 |
217 | // Verify echo
218 | assertEquals(request, reply);
219 | request.release();
220 | }
221 |
222 | private ZMQ.Socket zmqBind(final int zmqType, final String identity) {
223 | zmqSocket = context.socket(zmqType);
224 | setIdentity(zmqSocket, identity);
225 | port = zmqSocket.bindToRandomPort("tcp://127.0.0.1");
226 | return zmqSocket;
227 | }
228 |
229 | private ZMQ.Socket zmqConnect(final int zmqType, final String identity) {
230 | zmqSocket = context.socket(zmqType);
231 | setIdentity(zmqSocket, identity);
232 | zmqSocket.connect("tcp://127.0.0.1:" + port);
233 | return zmqSocket;
234 | }
235 |
236 | private ZMTPSocket nettyConnect(final ZMTPSocketType socketType, final String identity,
237 | final ZMTPProtocol protocol)
238 | throws InterruptedException, ExecutionException, TimeoutException {
239 | zmtpSocket = ZMTPSocket.builder()
240 | .handler(handler)
241 | .protocol(protocol)
242 | .type(socketType)
243 | .identity(identity)
244 | .build();
245 |
246 | final ListenableFuture f = zmtpSocket.connect("tcp://127.0.0.1:" + port);
247 | f.get(5, TimeUnit.SECONDS);
248 |
249 | return zmtpSocket;
250 | }
251 |
252 | private ZMTPSocket nettyBind(final ZMTPSocketType socketType, final String identity,
253 | final ZMTPProtocol protocol)
254 | throws InterruptedException, ExecutionException, TimeoutException {
255 | zmtpSocket = ZMTPSocket.builder()
256 | .handler(handler)
257 | .protocol(protocol)
258 | .type(socketType)
259 | .identity(identity)
260 | .build();
261 |
262 | final ListenableFuture f = zmtpSocket.bind("tcp://127.0.0.1:*");
263 | final InetSocketAddress address = f.get(5, TimeUnit.SECONDS);
264 | port = address.getPort();
265 |
266 | return zmtpSocket;
267 | }
268 |
269 | private void setIdentity(final ZMQ.Socket socket, final String identity) {
270 | if (!identity.equals(ANONYMOUS)) {
271 | socket.setIdentity(identity.getBytes(UTF_8));
272 | }
273 | }
274 |
275 | private void verifyPeerIdentity(final ZMTPSocket socket, final String zmqIdentity) throws InterruptedException {
276 | final List peers = socket.peers();
277 | assertThat(peers.size(), is(1));
278 | final ZMTPSession session = peers.get(0).session();
279 | if (ANONYMOUS.equals(zmqIdentity)) {
280 | assertThat(session.isPeerAnonymous(), is(true));
281 | assertThat(UTF_8.decode(session.peerIdentity()).toString(), not(isEmptyString()));
282 | } else {
283 | assertThat(session.isPeerAnonymous(), is(false));
284 | assertThat(session.peerIdentity(), is(UTF_8.encode(zmqIdentity)));
285 | }
286 | }
287 | }
288 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/ZMTP10WireFormatTest.java:
--------------------------------------------------------------------------------
1 | package com.spotify.netty4.handler.codec.zmtp;
2 |
3 | import org.junit.Rule;
4 | import org.junit.Test;
5 | import org.junit.rules.ExpectedException;
6 |
7 | import io.netty.buffer.ByteBuf;
8 | import io.netty.buffer.Unpooled;
9 |
10 | import static org.hamcrest.Matchers.is;
11 | import static org.junit.Assert.assertThat;
12 |
13 | public class ZMTP10WireFormatTest {
14 |
15 | @Rule public final ExpectedException expectedException = ExpectedException.none();
16 |
17 | @Test
18 | public void testTooLongIdentity() throws Exception {
19 | final ByteBuf buffer = Unpooled.buffer();
20 | buffer.writeByte(0xFF);
21 | buffer.writeLong(256 + 1);
22 | buffer.writeByte(0);
23 | buffer.writeBytes(new byte[256]);
24 |
25 | expectedException.expect(ZMTPException.class);
26 | ZMTP10WireFormat.readIdentity(buffer);
27 | }
28 |
29 | @Test
30 | public void testLongFrameLengthMissingLong() {
31 | final ByteBuf buffer = Unpooled.buffer();
32 | buffer.writeByte(0xFF);
33 | final long size = ZMTP10WireFormat.readLength(buffer);
34 | assertThat("Length shouldn't have been determined", size, is(-1L));
35 | }
36 |
37 | @Test
38 | public void testLongFrameLengthWithLong() {
39 | final ByteBuf buffer = Unpooled.buffer();
40 | buffer.writeByte(0xFF);
41 | buffer.writeLong(4);
42 | final long size = ZMTP10WireFormat.readLength(buffer);
43 | assertThat("Frame length should be after the first byte", size, is(4L));
44 | }
45 |
46 | @Test
47 | public void testFrameLengthEmptyBuffer() {
48 | final ByteBuf buffer = Unpooled.buffer();
49 | final long size = ZMTP10WireFormat.readLength(buffer);
50 | assertThat("Empty buffer should return -1 frame length", size, is(-1L));
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/ZMTPFramingEncoderTest.java:
--------------------------------------------------------------------------------
1 | package com.spotify.netty4.handler.codec.zmtp;
2 |
3 | import com.google.common.base.Strings;
4 |
5 | import org.junit.Before;
6 | import org.junit.Test;
7 | import org.junit.runner.RunWith;
8 | import org.mockito.ArgumentCaptor;
9 | import org.mockito.Captor;
10 | import org.mockito.Mock;
11 | import org.mockito.runners.MockitoJUnitRunner;
12 |
13 | import io.netty.buffer.ByteBuf;
14 | import io.netty.buffer.ByteBufAllocator;
15 | import io.netty.buffer.Unpooled;
16 | import io.netty.buffer.UnpooledByteBufAllocator;
17 | import io.netty.channel.ChannelHandlerContext;
18 | import io.netty.channel.ChannelPromise;
19 | import io.netty.util.concurrent.EventExecutor;
20 |
21 | import static com.spotify.netty4.handler.codec.zmtp.Buffers.buf;
22 | import static com.spotify.netty4.handler.codec.zmtp.Buffers.bytes;
23 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPConfig.ANONYMOUS;
24 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPProtocols.ZMTP10;
25 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPProtocols.ZMTP20;
26 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPSocketType.DEALER;
27 | import static io.netty.util.CharsetUtil.UTF_8;
28 | import static org.hamcrest.Matchers.is;
29 | import static org.junit.Assert.assertThat;
30 | import static org.mockito.Matchers.any;
31 | import static org.mockito.Mockito.when;
32 |
33 | @RunWith(MockitoJUnitRunner.class)
34 | public class ZMTPFramingEncoderTest {
35 |
36 | private final static ByteBufAllocator ALLOC = new UnpooledByteBufAllocator(false);
37 |
38 | @Mock ChannelHandlerContext ctx;
39 | @Mock ChannelPromise promise;
40 | @Mock EventExecutor executor;
41 |
42 | @Captor ArgumentCaptor bufCaptor;
43 |
44 | private static final String LARGE_FILL = Strings.repeat("a", 500);
45 |
46 | @Before
47 | public void setUp() {
48 | when(ctx.write(bufCaptor.capture(), any(ChannelPromise.class))).thenReturn(promise);
49 | when(ctx.alloc()).thenReturn(ByteBufAllocator.DEFAULT);
50 | when(ctx.executor()).thenReturn(executor);
51 | }
52 |
53 | @Test
54 | public void testEncodeZMTP1() throws Exception {
55 |
56 | ZMTPConfig config = ZMTPConfig.builder()
57 | .protocol(ZMTP10)
58 | .socketType(DEALER)
59 | .build();
60 | ZMTPSession session = new ZMTPSession(config);
61 | session.handshakeSuccess(ZMTPHandshake.of(ZMTPVersion.ZMTP10, ANONYMOUS));
62 |
63 | ZMTPFramingEncoder enc = new ZMTPFramingEncoder(session, new ZMTPMessageEncoder());
64 |
65 | ZMTPMessage message = ZMTPMessage.fromUTF8(ALLOC, "id0", "id1", "", "f0");
66 |
67 | enc.write(ctx, message, promise);
68 | enc.flush(ctx);
69 | final ByteBuf buf = bufCaptor.getValue();
70 | assertThat(buf, is(buf(4, 1, 0x69, 0x64, 0x30,
71 | 4, 1, 0x69, 0x64, 0x31,
72 | 1, 1,
73 | 3, 0, 0x66, 0x30)));
74 | buf.release();
75 | }
76 |
77 | @Test
78 | public void testEncodeZMTP2() throws Exception {
79 |
80 | ZMTPMessage message = ZMTPMessage.fromUTF8(ALLOC, "id0", "id1", "", "f0");
81 |
82 | ZMTPConfig config = ZMTPConfig.builder()
83 | .protocol(ZMTP20)
84 | .socketType(DEALER)
85 | .build();
86 | ZMTPSession session = new ZMTPSession(config);
87 | session.handshakeSuccess(ZMTPHandshake.of(ZMTPVersion.ZMTP20, ANONYMOUS));
88 |
89 | ZMTPFramingEncoder enc = new ZMTPFramingEncoder(session, new ZMTPMessageEncoder());
90 |
91 | enc.write(ctx, message, promise);
92 | enc.flush(ctx);
93 | final ByteBuf buf = bufCaptor.getValue();
94 | assertThat(buf, is(buf(1, 3, 0x69, 0x64, 0x30,
95 | 1, 3, 0x69, 0x64, 0x31,
96 | 1, 0,
97 | 0, 2, 0x66, 0x30)));
98 | buf.release();
99 | }
100 |
101 | @Test
102 | public void testEncodeZMTP2Long() throws Exception {
103 | ZMTPMessage message = ZMTPMessage.fromUTF8(ALLOC, "id0", "", LARGE_FILL);
104 | ByteBuf buf = Unpooled.buffer();
105 | buf.writeBytes(bytes(1, 3, 0x69, 0x64, 0x30,
106 | 1, 0,
107 | 2, 0, 0, 0, 0, 0, 0, 0x01, 0xf4));
108 | buf.writeBytes(LARGE_FILL.getBytes(UTF_8));
109 |
110 | ZMTPConfig config = ZMTPConfig.builder()
111 | .protocol(ZMTP20)
112 | .socketType(DEALER)
113 | .build();
114 | ZMTPSession session = new ZMTPSession(config);
115 |
116 | session.handshakeSuccess(ZMTPHandshake.of(ZMTPVersion.ZMTP20, ANONYMOUS));
117 |
118 | ZMTPFramingEncoder enc = new ZMTPFramingEncoder(session, new ZMTPMessageEncoder());
119 |
120 | enc.write(ctx, message, promise);
121 | enc.flush(ctx);
122 | final ByteBuf buf2 = bufCaptor.getValue();
123 |
124 | assertThat(buf, is(buf2));
125 |
126 | buf.release();
127 | buf2.release();
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/ZMTPMessageDecoderTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import com.google.common.collect.Lists;
20 |
21 | import org.junit.Test;
22 | import org.junit.runner.RunWith;
23 | import org.mockito.Mock;
24 | import org.mockito.runners.MockitoJUnitRunner;
25 |
26 | import java.util.List;
27 |
28 | import io.netty.buffer.ByteBuf;
29 | import io.netty.buffer.ByteBufAllocator;
30 | import io.netty.buffer.Unpooled;
31 | import io.netty.buffer.UnpooledByteBufAllocator;
32 | import io.netty.channel.ChannelHandlerContext;
33 |
34 | import static io.netty.util.CharsetUtil.UTF_8;
35 | import static org.hamcrest.Matchers.contains;
36 | import static org.hamcrest.Matchers.hasSize;
37 | import static org.junit.Assert.assertThat;
38 |
39 | @RunWith(MockitoJUnitRunner.class)
40 | public class ZMTPMessageDecoderTest {
41 |
42 | @Mock ChannelHandlerContext ctx;
43 |
44 | private final static ByteBufAllocator ALLOC = new UnpooledByteBufAllocator(false);
45 |
46 | @Test
47 | public void testSingleFrame() throws Exception {
48 | final ZMTPMessageDecoder decoder = new ZMTPMessageDecoder();
49 |
50 | final ByteBuf content = Unpooled.copiedBuffer("hello", UTF_8);
51 |
52 | final List out = Lists.newArrayList();
53 | decoder.header(ctx, content.readableBytes(), false, out);
54 | decoder.content(ctx, content, out);
55 | decoder.finish(ctx, out);
56 |
57 | final Object expected = ZMTPMessage.fromUTF8(ALLOC, "hello");
58 | assertThat(out, hasSize(1));
59 | assertThat(out, contains(expected));
60 | }
61 |
62 | @Test
63 | public void testTwoFrames() throws Exception {
64 | final ZMTPMessageDecoder decoder = new ZMTPMessageDecoder();
65 |
66 | final ByteBuf f0 = Unpooled.copiedBuffer("hello", UTF_8);
67 | final ByteBuf f1 = Unpooled.copiedBuffer("world", UTF_8);
68 |
69 | final List out = Lists.newArrayList();
70 | decoder.header(ctx, f0.readableBytes(), true, out);
71 | decoder.content(ctx, f0, out);
72 | decoder.header(ctx, f1.readableBytes(), false, out);
73 | decoder.content(ctx, f1, out);
74 | decoder.finish(ctx, out);
75 |
76 | final Object expected = ZMTPMessage.fromUTF8(ALLOC, "hello", "world");
77 | assertThat(out, hasSize(1));
78 | assertThat(out, contains(expected));
79 | }
80 | }
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/ZMTPMessageTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2014 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import com.google.common.base.Function;
20 | import com.google.common.collect.Lists;
21 |
22 | import org.junit.Test;
23 | import org.junit.runner.RunWith;
24 | import org.junit.runners.Parameterized;
25 |
26 | import java.nio.CharBuffer;
27 | import java.util.ArrayList;
28 | import java.util.List;
29 |
30 | import io.netty.buffer.ByteBuf;
31 | import io.netty.buffer.ByteBufAllocator;
32 | import io.netty.buffer.ByteBufUtil;
33 | import io.netty.buffer.Unpooled;
34 | import io.netty.buffer.UnpooledByteBufAllocator;
35 |
36 | import static io.netty.util.CharsetUtil.UTF_8;
37 | import static java.util.Arrays.asList;
38 | import static org.hamcrest.Matchers.is;
39 | import static org.hamcrest.Matchers.not;
40 | import static org.junit.Assert.assertEquals;
41 | import static org.junit.Assert.assertThat;
42 |
43 | @RunWith(Parameterized.class)
44 | public class ZMTPMessageTest {
45 |
46 | private final static ByteBufAllocator ALLOC = new UnpooledByteBufAllocator(false);
47 |
48 | @Parameterized.Parameters(name = "{0}")
49 | public static Iterable versions() {
50 | final List versions = new ArrayList();
51 | for (final ZMTPVersion version : ZMTPVersion.supportedVersions()) {
52 | versions.add(new Object[]{version});
53 | }
54 | return versions;
55 | }
56 |
57 | @Parameterized.Parameter(0)
58 | public ZMTPVersion version;
59 |
60 | @Test
61 | public void testNotEquals() {
62 | final ZMTPMessage m1 = ZMTPMessage.fromUTF8(ALLOC, "hello", "world");
63 | final ZMTPMessage m2 = ZMTPMessage.fromUTF8(ALLOC, "foo", "bar");
64 | assertThat(m1, is(not(m2)));
65 | }
66 |
67 | @Test
68 | public void testEquals() {
69 | final ZMTPMessage m1 = ZMTPMessage.fromUTF8(ALLOC, "hello", "world");
70 | final ZMTPMessage m2 = ZMTPMessage.fromUTF8(ALLOC, "hello", "world");
71 | assertThat(m1, is(m2));
72 | }
73 |
74 | @Test
75 | public void testIdentityEquals() {
76 | final ZMTPMessage m = ZMTPMessage.fromUTF8(ALLOC, "hello", "world");
77 | assertThat(m, is(m));
78 | }
79 |
80 | @Test
81 | public void testWriteAndRead() throws ZMTPParsingException {
82 | final ZMTPMessage message = ZMTPMessage.fromUTF8(ALLOC, "hello", "world");
83 | final ByteBuf buffer = message.write(ALLOC, version);
84 | final ZMTPMessage read = ZMTPMessage.read(buffer, version);
85 | assertThat(read, is(message));
86 | }
87 |
88 | @Test
89 | public void testWriteAndReadTwoMessages() throws ZMTPParsingException {
90 | final ZMTPMessage m1 = ZMTPMessage.fromUTF8(ALLOC, "hello", "world");
91 | final ZMTPMessage m2 = ZMTPMessage.fromUTF8(ALLOC, "foo", "bar");
92 | final ByteBuf buffer = Unpooled.buffer();
93 | m1.write(buffer, version);
94 | m2.write(buffer, version);
95 | final ZMTPMessage r1 = ZMTPMessage.read(buffer, version);
96 | final ZMTPMessage r2 = ZMTPMessage.read(buffer, version);
97 | assertThat(r1, is(m1));
98 | assertThat(r2, is(m2));
99 | }
100 |
101 | @Test
102 | public void testFromStringsUTF8() {
103 | assertEquals(ZMTPMessage.fromUTF8(ALLOC, ""), message(""));
104 | assertEquals(ZMTPMessage.fromUTF8(ALLOC, "a"), message("a"));
105 | assertEquals(ZMTPMessage.fromUTF8(ALLOC, "aa"), message("aa"));
106 | assertEquals(ZMTPMessage.fromUTF8(ALLOC, "aa", "bb"), message("aa", "bb"));
107 | assertEquals(ZMTPMessage.fromUTF8(ALLOC, "aa", "", "bb"), message("aa", "", "bb"));
108 | }
109 |
110 | private ZMTPMessage message(final String... frames) {
111 | return ZMTPMessage.from(frames(asList(frames)));
112 | }
113 |
114 | private static List frames(final List frames) {
115 | return Lists.transform(frames, new Function() {
116 | @Override
117 | public ByteBuf apply(final String input) {
118 | return ByteBufUtil.encodeString(ALLOC, CharBuffer.wrap(input), UTF_8);
119 | }
120 | });
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/ZMTPParserTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import com.spotify.netty4.handler.codec.zmtp.VerifyingDecoder.ExpectedOutput;
20 |
21 | import org.junit.experimental.theories.DataPoints;
22 | import org.junit.experimental.theories.FromDataPoints;
23 | import org.junit.experimental.theories.Theories;
24 | import org.junit.experimental.theories.Theory;
25 | import org.junit.runner.RunWith;
26 |
27 | import java.util.List;
28 |
29 | import io.netty.buffer.ByteBuf;
30 | import io.netty.buffer.ByteBufAllocator;
31 | import io.netty.buffer.UnpooledByteBufAllocator;
32 | import io.netty.channel.ChannelHandlerContext;
33 |
34 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPWireFormats.wireFormat;
35 | import static java.util.Arrays.asList;
36 | import static org.mockito.Mockito.mock;
37 |
38 | /**
39 | * This test attempts to thoroughly exercise the {@link ZMTPFramingDecoder} by feeding it input
40 | * fragmented in every possible way using {@link Fragmenter}. Everything from whole un-fragmented
41 | * message parsing to each byte being fragmented in a separate buffer is tested. Generating all
42 | * possible message fragmentations takes some time, so running this test can typically take a few
43 | * minutes.
44 | */
45 | @RunWith(Theories.class)
46 | public class ZMTPParserTest {
47 |
48 | private final static ByteBufAllocator ALLOC = new UnpooledByteBufAllocator(false);
49 | private final ChannelHandlerContext ctx = mock(ChannelHandlerContext.class);
50 |
51 | @DataPoints("frames")
52 | public static String[][] FRAMES = {
53 | {"1"},
54 | {"2", ""},
55 | {"3", "aa"},
56 | {"4", "", "a"},
57 | {"5", "", "a", "bb"},
58 | {"6", "aa", "", "b", "cc"},
59 | {"7", "", "a"},
60 | {"8", "", "b", "cc"},
61 | {"9", "aa", "", "b", "cc"},
62 | };
63 |
64 | @DataPoints("versions")
65 | public static final List VERSIONS = ZMTPVersion.supportedVersions();
66 |
67 | @Theory
68 | public void testParse(@FromDataPoints("frames") final String[] frames,
69 | @FromDataPoints("versions") final ZMTPVersion version) throws Exception {
70 | final List input = asList(frames);
71 | final ZMTPWireFormat wireFormat = wireFormat(version);
72 |
73 | final ZMTPMessage inputMessage = ZMTPMessage.fromUTF8(ALLOC, input);
74 |
75 | final ExpectedOutput expected = new ExpectedOutput(inputMessage);
76 |
77 | final ByteBuf serialized = inputMessage.write(ALLOC, version);
78 | final int serializedLength = serialized.readableBytes();
79 |
80 | // Test parsing the whole message
81 | {
82 | final VerifyingDecoder verifier = new VerifyingDecoder(expected);
83 | final ZMTPFramingDecoder decoder = new ZMTPFramingDecoder(wireFormat, verifier);
84 | decoder.decode(ctx, serialized, null);
85 | verifier.assertFinished();
86 | serialized.setIndex(0, serializedLength);
87 | }
88 |
89 | // Prepare for trivial message parsing test
90 | final ZMTPMessage trivial = ZMTPMessage.fromUTF8(ALLOC, "e", "", "a", "b", "c");
91 | final ByteBuf trivialSerialized = trivial.write(ALLOC, version);
92 | final int trivialLength = trivialSerialized.readableBytes();
93 | final ExpectedOutput trivialExpected = new ExpectedOutput(trivial);
94 |
95 | // Test parsing fragmented input
96 | final VerifyingDecoder verifier = new VerifyingDecoder();
97 | final ZMTPFramingDecoder decoder = new ZMTPFramingDecoder(wireFormat, verifier);
98 | new Fragmenter(serialized.readableBytes()).fragment(new Fragmenter.Consumer() {
99 | @Override
100 | public void fragments(final int[] limits, final int count) throws Exception {
101 | verifier.expect(expected);
102 | serialized.setIndex(0, serializedLength);
103 | for (int i = 0; i < count; i++) {
104 | final int limit = limits[i];
105 | serialized.writerIndex(limit);
106 | decoder.decode(ctx, serialized, null);
107 | }
108 | verifier.assertFinished();
109 |
110 | // Verify that the parser can be reused to parse the same message
111 | serialized.setIndex(0, serializedLength);
112 | decoder.decode(ctx, serialized, null);
113 | verifier.assertFinished();
114 |
115 | // Verify that the parser can be reused to parse a well-behaved message
116 | verifier.expect(trivialExpected);
117 | trivialSerialized.setIndex(0, trivialLength);
118 | decoder.decode(ctx, trivialSerialized, null);
119 | verifier.assertFinished();
120 | }
121 | });
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/ZMTPWriterTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp;
18 |
19 | import com.google.common.collect.Lists;
20 |
21 | import org.junit.Test;
22 | import org.junit.runner.RunWith;
23 | import org.mockito.runners.MockitoJUnitRunner;
24 |
25 | import java.util.List;
26 |
27 | import io.netty.buffer.ByteBuf;
28 | import io.netty.buffer.Unpooled;
29 | import io.netty.channel.ChannelHandlerContext;
30 |
31 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPVersion.ZMTP10;
32 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPWireFormats.wireFormat;
33 | import static io.netty.buffer.Unpooled.copiedBuffer;
34 | import static io.netty.util.CharsetUtil.UTF_8;
35 | import static java.util.Arrays.asList;
36 | import static java.util.Collections.singletonList;
37 | import static org.hamcrest.Matchers.hasSize;
38 | import static org.hamcrest.Matchers.is;
39 | import static org.hamcrest.Matchers.sameInstance;
40 | import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
41 | import static org.junit.Assert.assertThat;
42 |
43 | @RunWith(MockitoJUnitRunner.class)
44 | public class ZMTPWriterTest {
45 |
46 | private final List out = Lists.newArrayList();
47 |
48 | @Test
49 | public void testOneFrame() throws Exception {
50 | final ZMTPWriter writer = ZMTPWriter.create(ZMTP10);
51 | final ByteBuf buf = Unpooled.buffer();
52 | writer.reset(buf);
53 |
54 | ByteBuf frame = writer.frame(11, false);
55 | assertThat(frame, is(sameInstance(buf)));
56 |
57 | final ByteBuf content = copiedBuffer("hello world", UTF_8);
58 |
59 | frame.writeBytes(content.duplicate());
60 |
61 | final ZMTPFramingDecoder decoder = new ZMTPFramingDecoder(wireFormat(ZMTP10), new RawDecoder());
62 | decoder.decode(null, buf, out);
63 |
64 | assertThat(out, hasSize(1));
65 | assertThat(out, contains((Object) singletonList(content)));
66 | }
67 |
68 | @Test
69 | public void testTwoFrames() throws Exception {
70 | final ZMTPWriter writer = ZMTPWriter.create(ZMTP10);
71 | final ByteBuf buf = Unpooled.buffer();
72 | writer.reset(buf);
73 |
74 | final ByteBuf f0 = copiedBuffer("hello ", UTF_8);
75 | final ByteBuf f1 = copiedBuffer("hello ", UTF_8);
76 |
77 | writer.frame(f0.readableBytes(), true).writeBytes(f0.duplicate());
78 | writer.frame(f1.readableBytes(), false).writeBytes(f1.duplicate());
79 |
80 | final ZMTPFramingDecoder decoder = new ZMTPFramingDecoder(wireFormat(ZMTP10), new RawDecoder());
81 | decoder.decode(null, buf, out);
82 |
83 | assertThat(out, hasSize(1));
84 | assertThat(out, contains((Object) asList(f0, f1)));
85 | }
86 |
87 | @Test
88 | public void testReframe() throws Exception {
89 | final ZMTPFramingDecoder decoder = new ZMTPFramingDecoder(wireFormat(ZMTP10), new RawDecoder());
90 | final ZMTPWriter writer = ZMTPWriter.create(ZMTP10);
91 | final ByteBuf buf = Unpooled.buffer();
92 |
93 | writer.reset(buf);
94 |
95 | // Request a frame with margin in anticipation of a larger payload...
96 | // ... but write a smaller payload
97 | final ByteBuf content = copiedBuffer("hello world", UTF_8);
98 | writer.frame(content.readableBytes() * 2, true).writeBytes(content.duplicate());
99 |
100 | // And rewrite the frame accordingly
101 | writer.reframe(content.readableBytes(), false);
102 |
103 | // Verify that the message can be parsed
104 | decoder.decode(null, buf, out);
105 | assertThat(out, hasSize(1));
106 | assertThat(out, contains((Object) singletonList(content)));
107 |
108 | // Write and verify another message
109 | final ByteBuf next = copiedBuffer("next", UTF_8);
110 | writer.frame(next.readableBytes(), false).writeBytes(next.duplicate());
111 |
112 | out.clear();
113 | decoder.decode(null, buf, out);
114 | assertThat(out, hasSize(1));
115 | assertThat(out, contains((Object) singletonList(next)));
116 | }
117 |
118 |
119 | private class RawDecoder implements ZMTPDecoder {
120 |
121 | private long length;
122 |
123 | private List frames = Lists.newArrayList();
124 |
125 | public void header(final ChannelHandlerContext ctx, final long length, final boolean more,
126 | final List out) {
127 | this.length = length;
128 | }
129 |
130 | @Override
131 | public void content(final ChannelHandlerContext ctx, final ByteBuf data,
132 | final List out) {
133 | if (data.readableBytes() < length) {
134 | return;
135 | }
136 | frames.add(data.readBytes((int) length));
137 | }
138 |
139 | @Override
140 | public void finish(final ChannelHandlerContext ctx, final List out) {
141 | out.add(frames);
142 | frames = Lists.newArrayList();
143 | }
144 |
145 | @Override
146 | public void close() {
147 | for (final ByteBuf frame : frames) {
148 | frame.release();
149 | }
150 | frames.clear();
151 | }
152 | }
153 | }
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/benchmarks/AsciiString.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2014 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp.benchmarks;
18 |
19 | import com.google.common.base.Function;
20 |
21 | import java.util.Arrays;
22 |
23 | import io.netty.buffer.ByteBuf;
24 |
25 | import static com.google.common.base.Preconditions.checkNotNull;
26 |
27 | public class AsciiString implements CharSequence {
28 |
29 | public static final Function
30 | ASCII_STRING_FROM_STRING =
31 | new Function() {
32 | @Override
33 | public AsciiString apply(final String input) {
34 | return from(input);
35 | }
36 | };
37 |
38 | private final byte[] chars;
39 |
40 | public AsciiString(final byte[] chars) {
41 | this.chars = checkNotNull(chars, "chars");
42 | }
43 |
44 | @Override
45 | public int length() {
46 | return chars.length;
47 | }
48 |
49 | @Override
50 | public char charAt(final int index) {
51 | return (char) chars[index];
52 | }
53 |
54 | @Override
55 | public CharSequence subSequence(final int start, final int end) {
56 | final byte[] chars = new byte[end - start];
57 | System.arraycopy(this.chars, start, chars, 0, end - start);
58 | return new AsciiString(chars);
59 | }
60 |
61 | @Override
62 | public boolean equals(final Object o) {
63 | if (this == o) { return true; }
64 | if (o == null || getClass() != o.getClass()) { return false; }
65 |
66 | final AsciiString that = (AsciiString) o;
67 |
68 | return Arrays.equals(chars, that.chars);
69 | }
70 |
71 | @Override
72 | public int hashCode() {
73 | return chars != null ? Arrays.hashCode(chars) : 0;
74 | }
75 |
76 | @Override
77 | public String toString() {
78 | final char[] chars = new char[this.chars.length];
79 | for (int i = 0; i < this.chars.length; i++) {
80 | chars[i] = (char) this.chars[i];
81 | }
82 | return new String(chars);
83 | }
84 |
85 | public static AsciiString from(final String s) {
86 | final byte[] chars = new byte[s.length()];
87 | for (int i = 0; i < s.length(); i++) {
88 | chars[i] = (byte) s.charAt(i);
89 | }
90 | return new AsciiString(chars);
91 | }
92 |
93 | public void write(final ByteBuf buf) {
94 | buf.writeBytes(chars);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/benchmarks/ProgressMeter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2013 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp.benchmarks;
18 |
19 | import com.google.common.util.concurrent.AbstractScheduledService;
20 |
21 | import java.util.concurrent.TimeUnit;
22 | import java.util.concurrent.atomic.AtomicLong;
23 |
24 | import static java.lang.System.out;
25 | import static java.util.concurrent.TimeUnit.NANOSECONDS;
26 | import static java.util.concurrent.TimeUnit.SECONDS;
27 |
28 | class ProgressMeter extends AbstractScheduledService {
29 |
30 | private static final double NANOS_PER_MS = TimeUnit.MILLISECONDS.toNanos(1);
31 | private static final long NANOS_PER_S = TimeUnit.SECONDS.toNanos(1);
32 |
33 | private final AtomicLong totalLatency = new AtomicLong();
34 | private final AtomicLong totalOperations = new AtomicLong();
35 |
36 | private final String unit;
37 | private final boolean reportLatency;
38 |
39 | private long startTime;
40 | private long lastRows;
41 | private long lastTime;
42 | private long lastLatency;
43 |
44 | public ProgressMeter(final String unit) {
45 | this(unit, false);
46 | }
47 |
48 | public ProgressMeter(final String unit, final boolean reportLatency) {
49 | this.unit = unit;
50 | this.reportLatency = reportLatency;
51 | startAsync();
52 | }
53 |
54 | public void inc() {
55 | this.totalOperations.incrementAndGet();
56 | }
57 |
58 | public void inc(final long ops) {
59 | this.totalOperations.addAndGet(ops);
60 | }
61 |
62 | public void inc(final long ops, final long latency) {
63 | this.totalOperations.addAndGet(ops);
64 | this.totalLatency.addAndGet(latency);
65 | }
66 |
67 | @Override
68 | protected void runOneIteration() throws Exception {
69 | final long now = System.nanoTime();
70 | final long totalOperations = this.totalOperations.get();
71 | final long totalLatency = this.totalLatency.get();
72 |
73 | final long deltaOps = totalOperations - lastRows;
74 | final long deltaTime = now - lastTime;
75 | final long deltaLatency = totalLatency - lastLatency;
76 |
77 | lastRows = totalOperations;
78 | lastTime = now;
79 | lastLatency = totalLatency;
80 |
81 | // TODO (dano): use HdrHistogram to compute latency percentiles
82 |
83 | final long operations = (deltaTime == 0) ? 0 : (NANOS_PER_S * deltaOps) / deltaTime;
84 | final double avgLatency = (deltaOps == 0) ? 0 : deltaLatency / (NANOS_PER_MS * deltaOps);
85 | final long seconds = NANOSECONDS.toSeconds(now - startTime);
86 |
87 | out.printf("%,4ds: %,12d %s/s.", seconds, operations, unit);
88 | if (reportLatency) {
89 | out.printf(" %,10.3f ms avg latency.", avgLatency);
90 | }
91 | out.printf(" (total: %,12d)\n", totalOperations);
92 | out.flush();
93 | }
94 |
95 | @Override
96 | protected void startUp() throws Exception {
97 | startTime = System.nanoTime();
98 | lastTime = startTime;
99 | }
100 |
101 | @Override
102 | protected Scheduler scheduler() {
103 | return Scheduler.newFixedRateSchedule(1, 1, SECONDS);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/benchmarks/ReqRepBenchmark.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2014 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp.benchmarks;
18 |
19 | import com.google.common.base.Strings;
20 |
21 | import com.spotify.netty4.handler.codec.zmtp.ZMTPCodec;
22 | import com.spotify.netty4.handler.codec.zmtp.ZMTPHandshakeSuccess;
23 | import com.spotify.netty4.handler.codec.zmtp.ZMTPMessage;
24 | import com.spotify.netty4.util.BatchFlusher;
25 |
26 | import java.net.InetSocketAddress;
27 | import java.net.SocketAddress;
28 |
29 | import io.netty.bootstrap.Bootstrap;
30 | import io.netty.bootstrap.ServerBootstrap;
31 | import io.netty.buffer.PooledByteBufAllocator;
32 | import io.netty.channel.Channel;
33 | import io.netty.channel.ChannelHandlerContext;
34 | import io.netty.channel.ChannelInboundHandlerAdapter;
35 | import io.netty.channel.ChannelInitializer;
36 | import io.netty.channel.ChannelOption;
37 | import io.netty.channel.nio.NioEventLoopGroup;
38 | import io.netty.channel.socket.nio.NioServerSocketChannel;
39 | import io.netty.channel.socket.nio.NioSocketChannel;
40 |
41 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPSocketType.DEALER;
42 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPSocketType.ROUTER;
43 |
44 | public class ReqRepBenchmark {
45 |
46 | private static final InetSocketAddress ANY_PORT = new InetSocketAddress("127.0.0.1", 0);
47 |
48 | public static void main(final String... args) throws InterruptedException {
49 | final ProgressMeter meter = new ProgressMeter("requests");
50 |
51 | // Codecs
52 |
53 | // Server
54 | final ServerBootstrap serverBootstrap = new ServerBootstrap()
55 | .group(new NioEventLoopGroup(1), new NioEventLoopGroup())
56 | .channel(NioServerSocketChannel.class)
57 | .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
58 | .childHandler(new ChannelInitializer() {
59 | @Override
60 | protected void initChannel(final NioSocketChannel ch) throws Exception {
61 | ch.pipeline().addLast(ZMTPCodec.builder()
62 | .socketType(ROUTER)
63 | .build());
64 | ch.pipeline().addLast(new ServerHandler());
65 | }
66 | });
67 | final Channel server = serverBootstrap.bind(ANY_PORT).awaitUninterruptibly().channel();
68 |
69 | // Client
70 | final SocketAddress address = server.localAddress();
71 | final Bootstrap clientBootstrap = new Bootstrap()
72 | .group(new NioEventLoopGroup())
73 | .channel(NioSocketChannel.class)
74 | .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
75 | .handler(new ChannelInitializer() {
76 | @Override
77 | protected void initChannel(final NioSocketChannel ch) throws Exception {
78 | ch.pipeline().addLast(
79 | ZMTPCodec.builder()
80 | .socketType(DEALER)
81 | .build());
82 | ch.pipeline().addLast(new ClientHandler(meter));
83 | }
84 | });
85 | final Channel client = clientBootstrap.connect(address).awaitUninterruptibly().channel();
86 |
87 | // Run until client is closed
88 | client.closeFuture().await();
89 | }
90 |
91 | private static class ServerHandler extends ChannelInboundHandlerAdapter {
92 |
93 | private BatchFlusher flusher;
94 |
95 | @Override
96 | public void channelRegistered(final ChannelHandlerContext ctx) throws Exception {
97 | this.flusher = new BatchFlusher(ctx.channel());
98 | }
99 |
100 | @Override
101 | public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
102 | final ZMTPMessage message = (ZMTPMessage) msg;
103 | ctx.write(message);
104 | flusher.flush();
105 | }
106 | }
107 |
108 | private static class ClientHandler extends ChannelInboundHandlerAdapter {
109 |
110 | private static final int CONCURRENCY = 1000;
111 |
112 | private static final ZMTPMessage REQUEST = ZMTPMessage.fromUTF8(
113 | "envelope1", "envelope2",
114 | "",
115 | Strings.repeat("d", 20),
116 | Strings.repeat("d", 40),
117 | Strings.repeat("d", 100));
118 |
119 | private final ProgressMeter meter;
120 |
121 | private BatchFlusher flusher;
122 |
123 | public ClientHandler(final ProgressMeter meter) {
124 | this.meter = meter;
125 | }
126 |
127 | @Override
128 | public void channelRegistered(final ChannelHandlerContext ctx) throws Exception {
129 | this.flusher = new BatchFlusher(ctx.channel());
130 | }
131 |
132 | @Override
133 | public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt)
134 | throws Exception {
135 | if (evt instanceof ZMTPHandshakeSuccess) {
136 | for (int i = 0; i < CONCURRENCY; i++) {
137 | ctx.write(req());
138 | }
139 | flusher.flush();
140 | }
141 | }
142 |
143 | private ZMTPMessage req() {
144 | return REQUEST.retain();
145 | }
146 |
147 | @Override
148 | public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
149 | final ZMTPMessage message = (ZMTPMessage) msg;
150 | meter.inc();
151 | message.release();
152 | ctx.write(req());
153 | flusher.flush();
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/test/java/com/spotify/netty4/handler/codec/zmtp/benchmarks/ThroughputBenchmark.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2015 Spotify AB
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | package com.spotify.netty4.handler.codec.zmtp.benchmarks;
18 |
19 | import com.spotify.netty4.handler.codec.zmtp.ZMTPCodec;
20 | import com.spotify.netty4.handler.codec.zmtp.ZMTPHandshakeSuccess;
21 | import com.spotify.netty4.handler.codec.zmtp.ZMTPMessage;
22 | import com.spotify.netty4.util.BatchFlusher;
23 |
24 | import java.net.InetSocketAddress;
25 | import java.net.SocketAddress;
26 |
27 | import io.netty.bootstrap.Bootstrap;
28 | import io.netty.bootstrap.ServerBootstrap;
29 | import io.netty.buffer.ByteBuf;
30 | import io.netty.buffer.PooledByteBufAllocator;
31 | import io.netty.channel.Channel;
32 | import io.netty.channel.ChannelHandlerContext;
33 | import io.netty.channel.ChannelInboundHandlerAdapter;
34 | import io.netty.channel.ChannelInitializer;
35 | import io.netty.channel.ChannelOption;
36 | import io.netty.channel.MessageSizeEstimator;
37 | import io.netty.channel.nio.NioEventLoopGroup;
38 | import io.netty.channel.socket.nio.NioServerSocketChannel;
39 | import io.netty.channel.socket.nio.NioSocketChannel;
40 | import io.netty.util.ReferenceCountUtil;
41 |
42 | import static com.google.common.base.Strings.repeat;
43 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPSocketType.DEALER;
44 | import static com.spotify.netty4.handler.codec.zmtp.ZMTPSocketType.ROUTER;
45 | import static io.netty.channel.ChannelOption.ALLOCATOR;
46 |
47 | /**
48 | * A raw one-way throughput benchmark.
49 | */
50 | public class ThroughputBenchmark {
51 |
52 | private static final InetSocketAddress ANY_PORT = new InetSocketAddress("127.0.0.1", 0);
53 |
54 | public static void main(final String... args) throws InterruptedException {
55 | final ProgressMeter meter = new ProgressMeter("messages");
56 |
57 | // Server
58 | final ServerBootstrap serverBootstrap = new ServerBootstrap()
59 | .group(new NioEventLoopGroup(1), new NioEventLoopGroup(1))
60 | .channel(NioServerSocketChannel.class)
61 | .childOption(ALLOCATOR, PooledByteBufAllocator.DEFAULT)
62 | .childHandler(new ChannelInitializer() {
63 | @Override
64 | protected void initChannel(final NioSocketChannel ch) throws Exception {
65 | ch.pipeline().addLast(ZMTPCodec.of(ROUTER));
66 | ch.pipeline().addLast(new ServerHandler(meter));
67 | }
68 | });
69 | final Channel server = serverBootstrap.bind(ANY_PORT).awaitUninterruptibly().channel();
70 |
71 | // Client
72 | final SocketAddress address = server.localAddress();
73 | final Bootstrap clientBootstrap = new Bootstrap()
74 | .group(new NioEventLoopGroup(1))
75 | .channel(NioSocketChannel.class)
76 | .option(ALLOCATOR, PooledByteBufAllocator.DEFAULT)
77 | .option(ChannelOption.MESSAGE_SIZE_ESTIMATOR, ByteBufSizeEstimator.INSTANCE)
78 | .handler(new ChannelInitializer() {
79 | @Override
80 | protected void initChannel(final NioSocketChannel ch) throws Exception {
81 | ch.pipeline().addLast(ZMTPCodec.of(DEALER));
82 | ch.pipeline().addLast(new ClientHandler());
83 | }
84 | });
85 | final Channel client = clientBootstrap.connect(address).awaitUninterruptibly().channel();
86 |
87 | // Run until client is closed
88 | client.closeFuture().await();
89 | }
90 |
91 | private static class ServerHandler extends ChannelInboundHandlerAdapter {
92 |
93 | private final ProgressMeter meter;
94 |
95 | public ServerHandler(final ProgressMeter meter) {
96 | this.meter = meter;
97 | }
98 |
99 | @Override
100 | public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
101 | ReferenceCountUtil.release(msg);
102 | meter.inc();
103 | }
104 | }
105 |
106 | private static class ClientHandler extends ChannelInboundHandlerAdapter {
107 |
108 | private static final int BATCH_SIZE = 128;
109 |
110 | private static final ZMTPMessage MESSAGE = ZMTPMessage.fromUTF8(repeat(".", 100));
111 |
112 | private BatchFlusher flusher;
113 |
114 | @Override
115 | public void channelRegistered(final ChannelHandlerContext ctx) throws Exception {
116 | this.flusher = new BatchFlusher(ctx.channel());
117 | }
118 |
119 | @Override
120 | public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt)
121 | throws Exception {
122 | if (evt instanceof ZMTPHandshakeSuccess) {
123 | send(ctx);
124 | }
125 | }
126 |
127 | @Override
128 | public void channelWritabilityChanged(final ChannelHandlerContext ctx) throws Exception {
129 | send(ctx);
130 | }
131 |
132 | private void send(final ChannelHandlerContext ctx) {
133 | while(ctx.channel().isWritable()) {
134 | for (int i = 0; i < BATCH_SIZE; i++) {
135 | ctx.write(MESSAGE.retain());
136 | }
137 | flusher.flush();
138 | }
139 | }
140 | }
141 |
142 | private static class ByteBufSizeEstimator implements MessageSizeEstimator,
143 | MessageSizeEstimator.Handle {
144 |
145 | public static final ByteBufSizeEstimator INSTANCE = new ByteBufSizeEstimator();
146 |
147 | @Override
148 | public Handle newHandle() {
149 | return this;
150 | }
151 |
152 | @Override
153 | public int size(final Object msg) {
154 | if (msg instanceof ByteBuf) {
155 | return ((ByteBuf) msg).readableBytes();
156 | }
157 | return 0;
158 | }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/test/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 | %d{HH:mm:ss.SSS} [%thread] %level %logger{20-}: %msg%n
22 |
23 | System.err
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------