Bump checkstyle version
[dcaegen2/collectors/hv-ves.git] / hv-collector-domain / src / test / kotlin / org / onap / dcae / collectors / veshv / domain / WireFrameCodecsTest.kt
1 /*
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
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
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=========================================================
19  */
20 package org.onap.dcae.collectors.veshv.domain
21
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
34
35 /**
36  * @author Piotr Jaszczyk <piotr.jaszczyk@nokia.com>
37  * @since June 2018
38  */
39 object WireFrameCodecsTest : Spek({
40     val payloadAsString = "coffeebabe"
41     val maxPayloadSizeBytes = 1024
42     val encoder = WireFrameEncoder()
43     val decoder = WireFrameDecoder(maxPayloadSizeBytes)
44
45     fun createSampleFrame() = WireFrameMessage(payloadAsString.toByteArray(Charset.defaultCharset()))
46
47     fun encodeSampleFrame() =
48             createSampleFrame().let {
49                 encoder.encode(it)
50             }
51
52     describe("Wire Frame invariants") {
53
54         given("input with unsupported major version") {
55             val input = WireFrameMessage(
56                     payload = ByteData.EMPTY,
57                     versionMajor = 100,
58                     versionMinor = 0,
59                     payloadType = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
60                     payloadSize = 0)
61
62             it("should fail validation") {
63                 assertThat(input.isValid()).isFalse()
64             }
65         }
66
67         given("input with unsupported minor version") {
68             val input = WireFrameMessage(
69                     payload = ByteData.EMPTY,
70                     versionMajor = 1,
71                     versionMinor = 6,
72                     payloadType = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
73                     payloadSize = 0)
74
75             it("should pass validation") {
76                 assertThat(input.isValid()).isTrue()
77             }
78         }
79
80         given("input with unsupported payload type") {
81             val input = WireFrameMessage(
82                     payload = ByteData.EMPTY,
83                     versionMajor = 1,
84                     versionMinor = 0,
85                     payloadType = 0x69,
86                     payloadSize = 0)
87
88             it("should fail validation") {
89                 assertThat(input.isValid()).isFalse()
90             }
91         }
92
93         given("input with too small payload size") {
94             val input = WireFrameMessage(
95                     payload = ByteData(byteArrayOf(1, 2, 3)),
96                     versionMajor = 1,
97                     versionMinor = 0,
98                     payloadType = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
99                     payloadSize = 1)
100
101             it("should fail validation") {
102                 assertThat(input.isValid()).isFalse()
103             }
104         }
105
106         given("input with too big payload size") {
107             val input = WireFrameMessage(
108                     payload = ByteData(byteArrayOf(1, 2, 3)),
109                     versionMajor = 1,
110                     versionMinor = 0,
111                     payloadType = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
112                     payloadSize = 8)
113
114             it("should fail validation") {
115                 assertThat(input.isValid()).isFalse()
116             }
117         }
118
119         given("valid input") {
120             val payload = byteArrayOf(6, 9, 8, 6)
121             val input = WireFrameMessage(
122                     payload = ByteData(payload),
123                     versionMajor = 1,
124                     versionMinor = 0,
125                     payloadType = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
126                     payloadSize = payload.size)
127
128             it("should pass validation") {
129                 assertThat(input.isValid()).isTrue()
130             }
131         }
132
133
134     }
135
136     describe("Wire Frame codec") {
137
138         describe("encode-decode methods' compatibility") {
139             val frame = createSampleFrame()
140             val encoded = encodeSampleFrame()
141             val decoded = decoder.decodeFirst(encoded).getMessageOrFail()
142
143             it("should decode major version") {
144                 assertThat(decoded.versionMajor).isEqualTo(frame.versionMajor)
145             }
146
147             it("should decode minor version") {
148                 assertThat(decoded.versionMinor).isEqualTo(frame.versionMinor)
149             }
150
151             it("should decode payload type") {
152                 assertThat(decoded.payloadType).isEqualTo(frame.payloadType)
153             }
154
155             it("should decode payload size") {
156                 assertThat(decoded.payloadSize).isEqualTo(frame.payloadSize)
157             }
158
159             it("should decode payload") {
160                 assertThat(decoded.payload.asString())
161                         .isEqualTo(payloadAsString)
162             }
163         }
164
165
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
168
169             it("should return error when buffer is empty") {
170                 val buff = Unpooled.buffer()
171
172                 decoder.decodeFirst(buff).assertFailedWithError { it.isInstanceOf(EmptyWireFrame::class.java) }
173                 assertBufferIntact(buff)
174             }
175
176             it("should return error when given any single byte other than marker byte") {
177                 val buff = Unpooled.buffer()
178                         .writeByte(0xEE)
179
180                 decoder.decodeFirst(buff).assertFailedWithError { it.isInstanceOf(MissingWireFrameHeaderBytes::class.java) }
181                 assertBufferIntact(buff)
182             }
183
184             it("should return error when payload message header does not fit") {
185                 val buff = Unpooled.buffer()
186                         .writeByte(0xAA)
187                         .writeBytes("MOMOM".toByteArray())
188
189                 decoder.decodeFirst(buff).assertFailedWithError { it.isInstanceOf(MissingWireFrameHeaderBytes::class.java) }
190                 assertBufferIntact(buff)
191             }
192
193             it("should return error when length looks ok but first byte is not 0xAA") {
194                 val buff = Unpooled.buffer()
195                         .writeByte(0xFF)
196                         .writeBytes("some garbage".toByteArray())
197
198                 decoder.decodeFirst(buff).assertFailedWithError { it.isInstanceOf(InvalidWireFrameMarker::class.java) }
199                 assertBufferIntact(buff)
200             }
201
202             it("should return error when payload doesn't fit") {
203                 val buff = Unpooled.buffer()
204                         .writeBytes(encodeSampleFrame())
205                 buff.writerIndex(buff.writerIndex() - 2)
206
207                 decoder.decodeFirst(buff).assertFailedWithError { it.isInstanceOf(MissingWireFramePayloadBytes::class.java) }
208                 assertBufferIntact(buff)
209             }
210
211             it("should decode payload message leaving rest unread") {
212                 val buff = Unpooled.buffer()
213                         .writeBytes(encodeSampleFrame())
214                         .writeByte(0xAB)
215                 val decoded = decoder.decodeFirst(buff).getMessageOrFail()
216
217                 assertThat(decoded.isValid()).describedAs("should be valid").isTrue()
218                 assertThat(buff.readableBytes()).isEqualTo(1)
219             }
220         }
221
222         describe("payload size limit") {
223
224             it("should decode successfully when payload size is equal 1 MiB") {
225
226                 val payload = ByteArray(maxPayloadSizeBytes)
227                 val input = WireFrameMessage(
228                         payload = ByteData(payload),
229                         versionMajor = 1,
230                         versionMinor = 0,
231                         payloadType = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
232                         payloadSize = payload.size)
233
234
235                 assertTrue(decoder.decodeFirst(encoder.encode(input)).isRight())
236             }
237
238             it("should return error when payload exceeds 1 MiB") {
239
240                 val payload = ByteArray(maxPayloadSizeBytes + 1)
241                 val input = WireFrameMessage(
242                         payload = ByteData(payload),
243                         versionMajor = 1,
244                         versionMinor = 0,
245                         payloadType = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
246                         payloadSize = payload.size)
247                 val buff = encoder.encode(input)
248
249                 decoder.decodeFirst(buff)
250                         .assertFailedWithError { it.isInstanceOf(PayloadSizeExceeded::class.java) }
251                 assertBufferIntact(buff)
252             }
253
254             it("should validate only first message") {
255
256                 val payload = ByteArray(maxPayloadSizeBytes)
257                 val input = WireFrameMessage(
258                         payload = ByteData(payload),
259                         versionMajor = 1,
260                         versionMinor = 0,
261                         payloadType = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
262                         payloadSize = payload.size)
263
264
265                 assertTrue(decoder.decodeFirst(encoder.encode(input).writeByte(0xAA)).isRight())
266             }
267         }
268     }
269 })
270
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)
274 }
275
276 private fun <A, B> Either<A, B>.assertFailedWithError(assertj: (ObjectAssert<A>) -> Unit) {
277     fold({ assertj(assertThat(it)) }, { fail("Error expected") })
278 }
279
280 private fun Either<WireFrameDecodingError, WireFrameMessage>.getMessageOrFail(): WireFrameMessage =
281         fold({ fail(it.message) }, { it })
282