4d6f07166947ec03099d50c86d2cc401b5803376
[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 arrow.core.identity
24 import io.netty.buffer.Unpooled
25 import io.netty.buffer.UnpooledByteBufAllocator
26 import org.assertj.core.api.Assertions.assertThat
27 import org.assertj.core.api.Assertions.fail
28 import org.assertj.core.api.ObjectAssert
29 import org.jetbrains.spek.api.Spek
30 import org.jetbrains.spek.api.dsl.describe
31 import org.jetbrains.spek.api.dsl.given
32 import org.jetbrains.spek.api.dsl.it
33 import org.onap.dcae.collectors.veshv.domain.WireFrameDecoder.Companion.MAX_PAYLOAD_SIZE
34 import java.nio.charset.Charset
35 import kotlin.test.assertTrue
36
37 /**
38  * @author Piotr Jaszczyk <piotr.jaszczyk@nokia.com>
39  * @since June 2018
40  */
41 object WireFrameCodecsTest : Spek({
42     val payloadAsString = "coffeebabe"
43     val encoder = WireFrameEncoder(UnpooledByteBufAllocator.DEFAULT)
44     val decoder = WireFrameDecoder()
45
46     fun createSampleFrame() =
47             WireFrame(payloadAsString.toByteArray(Charset.defaultCharset()))
48
49     fun encodeSampleFrame() =
50             createSampleFrame().let {
51                 encoder.encode(it)
52             }
53
54     describe("Wire Frame invariants") {
55
56         given("input with unsupported version") {
57             val input = WireFrame(
58                     payload = ByteData.EMPTY,
59                     version = 100,
60                     payloadTypeRaw = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
61                     payloadSize = 0)
62
63             it("should fail validation") {
64                 assertThat(input.isValid()).isFalse()
65             }
66         }
67
68         given("input with unsupported payload type") {
69             val input = WireFrame(
70                     payload = ByteData.EMPTY,
71                     version = 1,
72                     payloadTypeRaw = 0x69,
73                     payloadSize = 0)
74
75             it("should fail validation") {
76                 assertThat(input.isValid()).isFalse()
77             }
78         }
79
80         given("input with too small payload size") {
81             val input = WireFrame(
82                     payload = ByteData(byteArrayOf(1, 2, 3)),
83                     version = 1,
84                     payloadTypeRaw = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
85                     payloadSize = 1)
86
87             it("should fail validation") {
88                 assertThat(input.isValid()).isFalse()
89             }
90         }
91
92         given("input with too big payload size") {
93             val input = WireFrame(
94                     payload = ByteData(byteArrayOf(1, 2, 3)),
95                     version = 1,
96                     payloadTypeRaw = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
97                     payloadSize = 8)
98
99             it("should fail validation") {
100                 assertThat(input.isValid()).isFalse()
101             }
102         }
103
104         given("valid input") {
105             val payload = byteArrayOf(6, 9, 8, 6)
106             val input = WireFrame(
107                     payload = ByteData(payload),
108                     version = 1,
109                     payloadTypeRaw = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
110                     payloadSize = payload.size)
111
112             it("should pass validation") {
113                 assertThat(input.isValid()).isTrue()
114             }
115         }
116
117
118     }
119
120     describe("Wire Frame codec") {
121
122         describe("encode-decode methods' compatibility") {
123             val frame = createSampleFrame()
124             val encoded = encodeSampleFrame()
125             val decoded = decoder.decodeFirst(encoded).getOrFail()
126
127             it("should decode version") {
128                 assertThat(decoded.version).isEqualTo(frame.version)
129             }
130
131             it("should decode payload type") {
132                 assertThat(decoded.payloadTypeRaw).isEqualTo(frame.payloadTypeRaw)
133             }
134
135             it("should decode payload size") {
136                 assertThat(decoded.payloadSize).isEqualTo(frame.payloadSize)
137             }
138
139             it("should decode payload") {
140                 assertThat(decoded.payload.asString())
141                         .isEqualTo(payloadAsString)
142             }
143         }
144
145         describe("TCP framing") {
146             // see "Dealing with a Stream-based Transport" on http://netty.io/wiki/user-guide-for-4.x.html#wiki-h3-11
147
148             it("should decode message leaving rest unread") {
149                 val buff = Unpooled.buffer()
150                         .writeBytes(encodeSampleFrame())
151                         .writeByte(0xAA)
152                 val decoded = decoder.decodeFirst(buff).getOrFail()
153
154                 assertThat(decoded.isValid()).describedAs("should be valid").isTrue()
155                 assertThat(buff.readableBytes()).isEqualTo(1)
156             }
157
158             it("should return error when not even header fits") {
159                 val buff = Unpooled.buffer()
160                         .writeByte(0xFF)
161
162                 decoder.decodeFirst(buff).assertFailedWithError { it.isInstanceOf(MissingWireFrameHeaderBytes::class.java) }
163
164             }
165
166             it("should return error when first byte is not 0xFF but length looks ok") {
167                 val buff = Unpooled.buffer()
168                         .writeByte(0xAA)
169                         .writeBytes("some garbage".toByteArray())
170
171                 decoder.decodeFirst(buff).assertFailedWithError { it.isInstanceOf(InvalidWireFrameMarker::class.java) }
172             }
173
174             it("should return error when first byte is not 0xFF and length is to short") {
175                 val buff = Unpooled.buffer()
176                         .writeByte(0xAA)
177
178                 decoder.decodeFirst(buff).assertFailedWithError { it.isInstanceOf(MissingWireFrameHeaderBytes::class.java) }
179             }
180
181             it("should return error when payload doesn't fit") {
182                 val buff = Unpooled.buffer()
183                         .writeBytes(encodeSampleFrame())
184                 buff.writerIndex(buff.writerIndex() - 2)
185
186                 decoder.decodeFirst(buff).assertFailedWithError { it.isInstanceOf(MissingWireFramePayloadBytes::class.java) }
187             }
188
189         }
190
191         describe("payload size limit"){
192
193             it("should decode successfully when payload size is equal 1 MiB") {
194
195                 val payload = ByteArray(MAX_PAYLOAD_SIZE)
196                 val input = WireFrame(
197                         payload = ByteData(payload),
198                         version = 1,
199                         payloadTypeRaw = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
200                         payloadSize = payload.size)
201
202
203                 assertTrue(decoder.decodeFirst(encoder.encode(input)).isRight())
204             }
205
206             it("should return error when payload exceeds 1 MiB") {
207
208                 val payload = ByteArray(MAX_PAYLOAD_SIZE + 1)
209                 val input = WireFrame(
210                         payload = ByteData(payload),
211                         version = 1,
212                         payloadTypeRaw = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
213                         payloadSize = payload.size)
214
215
216                 decoder.decodeFirst(encoder.encode(input))
217                         .assertFailedWithError { it.isInstanceOf(PayloadSizeExceeded::class.java) }
218             }
219
220             it("should validate only first message") {
221
222                 val payload = ByteArray(MAX_PAYLOAD_SIZE)
223                 val input = WireFrame(
224                         payload = ByteData(payload),
225                         version = 1,
226                         payloadTypeRaw = PayloadContentType.GOOGLE_PROTOCOL_BUFFER.hexValue,
227                         payloadSize = payload.size)
228
229
230                 assertTrue(decoder.decodeFirst(encoder.encode(input).writeByte(0xFF)).isRight())
231             }
232         }
233     }
234 })
235
236 private fun <A, B> Either<A, B>.assertFailedWithError(assertj: (ObjectAssert<A>) -> Unit) {
237     fold({ assertj(assertThat(it)) }, { fail("Error expected") })
238 }
239
240 private fun Either<WireFrameDecodingError, WireFrame>.getOrFail(): WireFrame =
241         fold({ fail(it.message) }, ::identity) as WireFrame