2 * ============LICENSE_START=======================================================
3 * dcaegen2-collectors-veshv
4 * ================================================================================
5 * Copyright (C) 2018 NOKIA
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
20 package org.onap.dcae.collectors.veshv.domain
22 import arrow.core.Either
23 import io.netty.buffer.ByteBuf
24 import io.netty.buffer.Unpooled
25 import org.assertj.core.api.Assertions.assertThat
26 import org.assertj.core.api.ObjectAssert
27 import org.jetbrains.spek.api.Spek
28 import org.jetbrains.spek.api.dsl.describe
29 import org.jetbrains.spek.api.dsl.given
30 import org.jetbrains.spek.api.dsl.it
31 import java.nio.charset.Charset
32 import kotlin.test.assertTrue
33 import kotlin.test.fail
36 * @author Piotr Jaszczyk <piotr.jaszczyk@nokia.com>
39 object WireFrameCodecsTest : Spek({
40 val payloadAsString = "coffeebabe"
41 val maxPayloadSizeBytes = 1024
42 val encoder = WireFrameEncoder()
43 val decoder = WireFrameDecoder(maxPayloadSizeBytes)
45 fun createSampleFrame() = WireFrameMessage(payloadAsString.toByteArray(Charset.defaultCharset()))
47 fun encodeSampleFrame() =
48 createSampleFrame().let {
52 describe("Wire Frame invariants") {
54 given("input with unsupported major version") {
55 val input = WireFrameMessage(
56 payload = ByteData.EMPTY,
59 payloadType = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
62 it("should fail validation") {
63 assertThat(input.isValid()).isFalse()
67 given("input with unsupported minor version") {
68 val input = WireFrameMessage(
69 payload = ByteData.EMPTY,
72 payloadType = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
75 it("should pass validation") {
76 assertThat(input.isValid()).isTrue()
80 given("input with unsupported payload type") {
81 val input = WireFrameMessage(
82 payload = ByteData.EMPTY,
88 it("should fail validation") {
89 assertThat(input.isValid()).isFalse()
93 given("input with too small payload size") {
94 val input = WireFrameMessage(
95 payload = ByteData(byteArrayOf(1, 2, 3)),
98 payloadType = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
101 it("should fail validation") {
102 assertThat(input.isValid()).isFalse()
106 given("input with too big payload size") {
107 val input = WireFrameMessage(
108 payload = ByteData(byteArrayOf(1, 2, 3)),
111 payloadType = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
114 it("should fail validation") {
115 assertThat(input.isValid()).isFalse()
119 given("valid input") {
120 val payload = byteArrayOf(6, 9, 8, 6)
121 val input = WireFrameMessage(
122 payload = ByteData(payload),
125 payloadType = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
126 payloadSize = payload.size)
128 it("should pass validation") {
129 assertThat(input.isValid()).isTrue()
136 describe("Wire Frame codec") {
138 describe("encode-decode methods' compatibility") {
139 val frame = createSampleFrame()
140 val encoded = encodeSampleFrame()
141 val decoded = decoder.decodeFirst(encoded).getMessageOrFail()
143 it("should decode major version") {
144 assertThat(decoded.versionMajor).isEqualTo(frame.versionMajor)
147 it("should decode minor version") {
148 assertThat(decoded.versionMinor).isEqualTo(frame.versionMinor)
151 it("should decode payload type") {
152 assertThat(decoded.payloadType).isEqualTo(frame.payloadType)
155 it("should decode payload size") {
156 assertThat(decoded.payloadSize).isEqualTo(frame.payloadSize)
159 it("should decode payload") {
160 assertThat(decoded.payload.asString())
161 .isEqualTo(payloadAsString)
166 describe("TCP framing") {
167 // see "Dealing with a Stream-based Transport" on http://netty.io/wiki/user-guide-for-4.x.html#wiki-h3-11
169 it("should return error when buffer is empty") {
170 val buff = Unpooled.buffer()
172 decoder.decodeFirst(buff).assertFailedWithError { it.isInstanceOf(EmptyWireFrame::class.java) }
173 assertBufferIntact(buff)
176 it("should return error when given any single byte other than marker byte") {
177 val buff = Unpooled.buffer()
180 decoder.decodeFirst(buff).assertFailedWithError { it.isInstanceOf(MissingWireFrameHeaderBytes::class.java) }
181 assertBufferIntact(buff)
184 it("should return error when payload message header does not fit") {
185 val buff = Unpooled.buffer()
187 .writeBytes("MOMOM".toByteArray())
189 decoder.decodeFirst(buff).assertFailedWithError { it.isInstanceOf(MissingWireFrameHeaderBytes::class.java) }
190 assertBufferIntact(buff)
193 it("should return error when length looks ok but first byte is not 0xAA") {
194 val buff = Unpooled.buffer()
196 .writeBytes("some garbage".toByteArray())
198 decoder.decodeFirst(buff).assertFailedWithError { it.isInstanceOf(InvalidWireFrameMarker::class.java) }
199 assertBufferIntact(buff)
202 it("should return error when payload doesn't fit") {
203 val buff = Unpooled.buffer()
204 .writeBytes(encodeSampleFrame())
205 buff.writerIndex(buff.writerIndex() - 2)
207 decoder.decodeFirst(buff).assertFailedWithError { it.isInstanceOf(MissingWireFramePayloadBytes::class.java) }
208 assertBufferIntact(buff)
211 it("should decode payload message leaving rest unread") {
212 val buff = Unpooled.buffer()
213 .writeBytes(encodeSampleFrame())
215 val decoded = decoder.decodeFirst(buff).getMessageOrFail()
217 assertThat(decoded.isValid()).describedAs("should be valid").isTrue()
218 assertThat(buff.readableBytes()).isEqualTo(1)
222 describe("payload size limit") {
224 it("should decode successfully when payload size is equal 1 MiB") {
226 val payload = ByteArray(maxPayloadSizeBytes)
227 val input = WireFrameMessage(
228 payload = ByteData(payload),
231 payloadType = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
232 payloadSize = payload.size)
235 assertTrue(decoder.decodeFirst(encoder.encode(input)).isRight())
238 it("should return error when payload exceeds 1 MiB") {
240 val payload = ByteArray(maxPayloadSizeBytes + 1)
241 val input = WireFrameMessage(
242 payload = ByteData(payload),
245 payloadType = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
246 payloadSize = payload.size)
247 val buff = encoder.encode(input)
249 decoder.decodeFirst(buff)
250 .assertFailedWithError { it.isInstanceOf(PayloadSizeExceeded::class.java) }
251 assertBufferIntact(buff)
254 it("should validate only first message") {
256 val payload = ByteArray(maxPayloadSizeBytes)
257 val input = WireFrameMessage(
258 payload = ByteData(payload),
261 payloadType = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
262 payloadSize = payload.size)
265 assertTrue(decoder.decodeFirst(encoder.encode(input).writeByte(0xAA)).isRight())
271 private fun assertBufferIntact(buff: ByteBuf) {
272 assertThat(buff.refCnt()).describedAs("buffer should not be released").isEqualTo(1)
273 assertThat(buff.readerIndex()).describedAs("buffer reader index should be intact").isEqualTo(0)
276 private fun <A, B> Either<A, B>.assertFailedWithError(assertj: (ObjectAssert<A>) -> Unit) {
277 fold({ assertj(assertThat(it)) }, { fail("Error expected") })
280 private fun Either<WireFrameDecodingError, WireFrameMessage>.getMessageOrFail(): WireFrameMessage =
281 fold({ fail(it.message) }, { it })