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 })