diff --git a/protocols/shared/src/main/scala-2/fs2/protocols/pcapng/BlockCodec.scala b/protocols/shared/src/main/scala-2/fs2/protocols/pcapng/BlockCodec.scala new file mode 100644 index 0000000000..3d8805b83d --- /dev/null +++ b/protocols/shared/src/main/scala-2/fs2/protocols/pcapng/BlockCodec.scala @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.protocols +package pcapng + +import scodec.Codec +import scodec.bits._ +import scodec.codecs._ +import shapeless.ops.hlist.{Init, Last, Prepend} +import shapeless.{::, HList, HNil} + +object BlockCodec { + + private def commonStructure[A, L <: HList, LB <: HList]( + blockType: Codec[A] + )(f: Length => Codec[L])(implicit + prepend: Prepend.Aux[L, Unit :: HNil, LB], + init: Init.Aux[LB, L], + last: Last.Aux[LB, Unit] + ): Codec[A :: Length :: LB] = + // format: off + ("Block Type" | blockType ) :: + ("Block Total Length" | bytes(4).xmapc(Length)(_.bv) ).flatPrepend { length => + ("Block Bytes" | f(length) ) :+ + ("Block Total Length" | constant(length.bv) )} + // format: on + + def unknownByteOrder[L <: HList, LB <: HList](hexConstant: ByteVector)(f: Length => Codec[L])( + implicit + prepend: Prepend.Aux[L, Unit :: HNil, LB], + init: Init.Aux[LB, L], + last: Last.Aux[LB, Unit] + ): Codec[Unit :: Length :: LB] = commonStructure(constant(hexConstant))(f) + + def byBlockBytesCodec[L <: HList, LB <: HList]( + hexConstant: ByteVector, + blockBytesCodec: Codec[L] + )(implicit + prepend: Prepend.Aux[L, Unit :: HNil, LB], + init: Init.Aux[LB, L], + last: Last.Aux[LB, Unit], + ord: ByteOrdering + ): Codec[Unit :: Length :: LB] = + unknownByteOrder(hexConstant)(length => fixedSizeBytes(length.toLong - 12, blockBytesCodec)) + + def ignored( + hexConstant: ByteVector + )(implicit ord: ByteOrdering): Codec[Length :: ByteVector :: HNil] = + unknownByteOrder(hexConstant) { length => + fixedSizeBytes(length.toLong - 12, bytes) :: Codec.deriveHNil + }.dropUnits + + def unrecognizedBlockType(implicit + ord: ByteOrdering + ): Codec[ByteVector :: Length :: ByteVector :: HNil] = + commonStructure(bytes(4)) { length => + fixedSizeBytes(length.toLong - 12, bytes) :: Codec.deriveHNil + }.dropUnits +} diff --git a/protocols/shared/src/main/scala-3/BlockCodec.scala b/protocols/shared/src/main/scala-3/BlockCodec.scala new file mode 100644 index 0000000000..df45e7e5cc --- /dev/null +++ b/protocols/shared/src/main/scala-3/BlockCodec.scala @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.protocols +package pcapng + +import scodec.Codec +import scodec.bits._ +import scodec.codecs._ + +object BlockCodec { + + private inline def commonStructure[A, L <: Tuple]( + blockType: Codec[A] + )(f: Length => Codec[L]): Codec[Tuple.Concat[A *: Length *: L, Unit *: EmptyTuple]] = + // format: off + ("Block Type" | blockType ) :: + ("Block Total Length" | bytes(4).xmapc(Length(_))(_.bv) ).flatPrepend { length => + ("Block Bytes" | f(length) ) :+ + ("Block Total Length" | constant(length.bv) )} + // format: on + + inline def unknownByteOrder[L <: Tuple]( + hexConstant: ByteVector + )(f: Length => Codec[L]): Codec[Tuple.Concat[Unit *: Length *: L, Unit *: EmptyTuple]] = + commonStructure(constant(hexConstant))(f) + + inline def byBlockBytesCodec[L <: Tuple](hexConstant: ByteVector, blockBytesCodec: Codec[L])( + implicit ord: ByteOrdering + ): Codec[Tuple.Concat[Unit *: Length *: L, Unit *: EmptyTuple]] = + unknownByteOrder(hexConstant)(length => fixedSizeBytes(length.toLong - 12, blockBytesCodec)) + + def ignored( + hexConstant: ByteVector + )(implicit ord: ByteOrdering): Codec[Length *: ByteVector *: EmptyTuple] = + unknownByteOrder(hexConstant) { length => + fixedSizeBytes(length.toLong - 12, bytes).tuple + }.dropUnits + + def unrecognizedBlockType(implicit + ord: ByteOrdering + ): Codec[ByteVector *: Length *: ByteVector *: EmptyTuple] = + commonStructure(bytes(4)) { length => + fixedSizeBytes(length.toLong - 12, bytes).tuple + }.dropUnits +} diff --git a/protocols/shared/src/main/scala/fs2/protocols/pcap/LinkType.scala b/protocols/shared/src/main/scala/fs2/protocols/pcap/LinkType.scala index 04ecd741a6..7fb4f9128a 100644 --- a/protocols/shared/src/main/scala/fs2/protocols/pcap/LinkType.scala +++ b/protocols/shared/src/main/scala/fs2/protocols/pcap/LinkType.scala @@ -41,14 +41,17 @@ object LinkType { case object MPEG2TS extends LinkType case class Unknown(value: Long) extends LinkType - def toLong(lt: LinkType): Long = lt match { + def fromInt(l: Int): LinkType = + fromLong(l.toLong) + + def toInt(lt: LinkType): Int = lt match { case Null => 0 case Ethernet => 1 case Raw => 101 case IPv4 => 228 case IPv6 => 229 case MPEG2TS => 243 - case Unknown(value) => value + case Unknown(value) => value.toInt } def fromLong(l: Long): LinkType = l match { @@ -61,6 +64,8 @@ object LinkType { case other => Unknown(other) } + def toLong(lt: LinkType): Long = toInt(lt).toLong + implicit def codec(implicit bo: ByteOrdering): Codec[LinkType] = guint32.xmap[LinkType](fromLong, toLong) } diff --git a/protocols/shared/src/main/scala/fs2/protocols/pcapng/BodyBlock.scala b/protocols/shared/src/main/scala/fs2/protocols/pcapng/BodyBlock.scala new file mode 100644 index 0000000000..939df85dc8 --- /dev/null +++ b/protocols/shared/src/main/scala/fs2/protocols/pcapng/BodyBlock.scala @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.protocols +package pcapng + +import scodec.bits._ +import scodec.Decoder + +trait BodyBlock + +object BodyBlock { + + def decoder(implicit ord: ByteOrdering): Decoder[BodyBlock] = + Decoder.choiceDecoder( + InterfaceDescriptionBlock.codec, + EnhancedPacketBlock.codec, + NameResolutionBlock.codec, + InterfaceStatisticsBlock.codec, + ProcessInformationBlock.codec, + UnrecognizedBlock.codec + ) +} diff --git a/protocols/shared/src/main/scala/fs2/protocols/pcapng/ByteOrderMagic.scala b/protocols/shared/src/main/scala/fs2/protocols/pcapng/ByteOrderMagic.scala new file mode 100644 index 0000000000..0168df9252 --- /dev/null +++ b/protocols/shared/src/main/scala/fs2/protocols/pcapng/ByteOrderMagic.scala @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.protocols +package pcapng + +import scodec._ +import scodec.bits._ +import scodec.codecs._ + +object ByteOrderMagic extends Codec[ByteOrdering] { + + private val BigEndian = hex"1a2b3c4d" + + private val LittleEndian = hex"4d3c2b1a" + + def sizeBound = SizeBound.exact(32) + + def encode(bo: ByteOrdering) = bo match { + case ByteOrdering.BigEndian => Attempt.successful(BigEndian.bits) + case ByteOrdering.LittleEndian => Attempt.successful(LittleEndian.bits) + } + + def decode(buf: BitVector) = + bytes(4).decode(buf).flatMap { + case DecodeResult(BigEndian, rest) => + Attempt.successful(DecodeResult(ByteOrdering.BigEndian, rest)) + case DecodeResult(LittleEndian, rest) => + Attempt.successful(DecodeResult(ByteOrdering.LittleEndian, rest)) + case DecodeResult(other, _) => + Attempt.failure( + Err(s"unable to detect byte ordering due to unrecognized magic number $other") + ) + } +} diff --git a/protocols/shared/src/main/scala/fs2/protocols/pcapng/CaptureFile.scala b/protocols/shared/src/main/scala/fs2/protocols/pcapng/CaptureFile.scala new file mode 100644 index 0000000000..3e708bd148 --- /dev/null +++ b/protocols/shared/src/main/scala/fs2/protocols/pcapng/CaptureFile.scala @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.protocols +package pcapng + +import cats.effect.MonadCancelThrow +import fs2.interop.scodec.StreamDecoder +import fs2.protocols.pcap.LinkType +import fs2.timeseries.TimeStamped +import fs2.{Pipe, Pull, Stream} +import scodec.bits.ByteVector + +object CaptureFile { + + val streamDecoder: StreamDecoder[BodyBlock] = + StreamDecoder.once(SectionHeaderBlock.codec).flatMap { shb => + StreamDecoder.many(BodyBlock.decoder(shb.ordering)) + } + + def parse[F[_]: MonadCancelThrow, A]( + f: (LinkType, ByteVector) => Option[A] + ): Pipe[F, Byte, TimeStamped[A]] = { bytes => + def go( + idbs: Vector[InterfaceDescriptionBlock], + blocks: Stream[F, BodyBlock] + ): Pull[F, TimeStamped[A], Unit] = + blocks.pull.uncons1.flatMap { + case Some((idb: InterfaceDescriptionBlock, tail)) => + go(idbs :+ idb, tail) + case Some((epb: EnhancedPacketBlock, tail)) => + val idb = idbs(epb.interfaceId.toInt) + val ts = ((epb.timestampHigh << 32) | epb.timestampLow) * idb.if_tsresol + val timeStamped = f(idb.linkType, epb.packetData).map(TimeStamped(ts, _)) + Pull.outputOption1(timeStamped) >> go(idbs, tail) + case Some((_, tail)) => go(idbs, tail) + case None => Pull.done + } + + val blocks = bytes.through(streamDecoder.toPipeByte) + blocks.through(go(Vector.empty, _).stream) + } +} diff --git a/protocols/shared/src/main/scala/fs2/protocols/pcapng/EnhancedPacketBlock.scala b/protocols/shared/src/main/scala/fs2/protocols/pcapng/EnhancedPacketBlock.scala new file mode 100644 index 0000000000..da718b2ec3 --- /dev/null +++ b/protocols/shared/src/main/scala/fs2/protocols/pcapng/EnhancedPacketBlock.scala @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.protocols +package pcapng + +import pcap._ +import scodec.Codec +import scodec.bits._ +import scodec.codecs._ + +case class EnhancedPacketBlock( + length: Length, + interfaceId: Long, + timestampHigh: Long, + timestampLow: Long, + capturedPacketLength: Long, + originalPacketLength: Long, + packetData: ByteVector, + options: ByteVector +) extends BodyBlock + +object EnhancedPacketBlock { + + def codec(implicit ord: ByteOrdering): Codec[EnhancedPacketBlock] = + "EPB" | BlockCodec.byBlockBytesCodec(hexConstant, epbCodec).dropUnits.as[EnhancedPacketBlock] + + private def hexConstant(implicit ord: ByteOrdering): ByteVector = + orderDependent(hex"00000006", hex"06000000") + + private def epbCodec(implicit ord: ByteOrdering) = + // format: off + ("Interface ID" | guint32 ) :: + ("Timestamp (High)" | guint32 ) :: + ("Timestamp (Low)" | guint32 ) :: + ("Captured Packet Length" | guint32 ).flatPrepend { packetLength => + ("Original Packet Length" | guint32 ) :: + ("Packet Data" | bytes(packetLength.toInt) ) :: + ("Packet padding" | ignore(padTo32Bits(packetLength.toInt)) ) :: + ("Options" | bytes )} + // format: on + + private def padTo32Bits(length: Int): Long = { + val rem = length % 4 + val bytes = if (rem == 0) 0 else 4 - rem + bytes.toLong * 8 + } +} diff --git a/protocols/shared/src/main/scala/fs2/protocols/pcapng/InterfaceDescriptionBlock.scala b/protocols/shared/src/main/scala/fs2/protocols/pcapng/InterfaceDescriptionBlock.scala new file mode 100644 index 0000000000..b49c6d786b --- /dev/null +++ b/protocols/shared/src/main/scala/fs2/protocols/pcapng/InterfaceDescriptionBlock.scala @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.protocols +package pcapng + +import fs2.protocols.pcap._ +import scodec.Codec +import scodec.bits._ +import scodec.codecs._ + +import scala.concurrent.duration._ + +case class InterfaceDescriptionBlock( + length: Length, + linkType: LinkType, + snapLen: Long, + bytes: ByteVector +) extends BodyBlock { + + // Should be extracted from options. Instead, we currently assume + // that required option wasn't found and fallback to a default value. + def if_tsresol: FiniteDuration = 1.microsecond +} + +object InterfaceDescriptionBlock { + + def codec(implicit ord: ByteOrdering): Codec[InterfaceDescriptionBlock] = { + val rawCodec = BlockCodec.byBlockBytesCodec(hexConstant, idbCodec) + "IDB" | rawCodec.dropUnits.as[InterfaceDescriptionBlock] + } + + private def hexConstant(implicit ord: ByteOrdering): ByteVector = + orderDependent(hex"00000001", hex"01000000") + + private def idbCodec(implicit ord: ByteOrdering) = + // format: off + ("LinkType" | guint16.xmap[LinkType](LinkType.fromInt, LinkType.toInt) ) :: + ("Reserved" | ignore(16) ) :: + ("SnapLen" | guint32 ) :: + ("Block Bytes" | bytes ) + // format: on +} diff --git a/protocols/shared/src/main/scala/fs2/protocols/pcapng/InterfaceStatisticsBlock.scala b/protocols/shared/src/main/scala/fs2/protocols/pcapng/InterfaceStatisticsBlock.scala new file mode 100644 index 0000000000..602db53108 --- /dev/null +++ b/protocols/shared/src/main/scala/fs2/protocols/pcapng/InterfaceStatisticsBlock.scala @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.protocols +package pcapng + +import scodec.bits._ +import scodec.codecs._ +import scodec.Codec + +case class InterfaceStatisticsBlock(length: Length, bytes: ByteVector) extends BodyBlock + +object InterfaceStatisticsBlock { + + def codec(implicit ord: ByteOrdering): Codec[InterfaceStatisticsBlock] = + "ISB" | BlockCodec.ignored(hexConstant).as[InterfaceStatisticsBlock] + + private def hexConstant(implicit ord: ByteOrdering) = + orderDependent(hex"00000005", hex"05000000") +} diff --git a/protocols/shared/src/main/scala/fs2/protocols/pcapng/Length.scala b/protocols/shared/src/main/scala/fs2/protocols/pcapng/Length.scala new file mode 100644 index 0000000000..513bd42471 --- /dev/null +++ b/protocols/shared/src/main/scala/fs2/protocols/pcapng/Length.scala @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.protocols +package pcapng + +import scodec.bits.{ByteOrdering, ByteVector} + +case class Length(bv: ByteVector) extends AnyVal { + def toLong(implicit ord: ByteOrdering): Long = bv.toLong(signed = false, ord) +} diff --git a/protocols/shared/src/main/scala/fs2/protocols/pcapng/NameResolutionBlock.scala b/protocols/shared/src/main/scala/fs2/protocols/pcapng/NameResolutionBlock.scala new file mode 100644 index 0000000000..1429f29774 --- /dev/null +++ b/protocols/shared/src/main/scala/fs2/protocols/pcapng/NameResolutionBlock.scala @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.protocols +package pcapng + +import scodec.bits._ +import scodec.codecs._ +import scodec.Codec + +case class NameResolutionBlock(length: Length, bytes: ByteVector) extends BodyBlock + +object NameResolutionBlock { + + def codec(implicit ord: ByteOrdering): Codec[NameResolutionBlock] = + "NRB" | BlockCodec.ignored(hexConstant).as[NameResolutionBlock] + + private def hexConstant(implicit ord: ByteOrdering) = + orderDependent(hex"00000004", hex"04000000") +} diff --git a/protocols/shared/src/main/scala/fs2/protocols/pcapng/ProcessInformationBlock.scala b/protocols/shared/src/main/scala/fs2/protocols/pcapng/ProcessInformationBlock.scala new file mode 100644 index 0000000000..2d009cfc8d --- /dev/null +++ b/protocols/shared/src/main/scala/fs2/protocols/pcapng/ProcessInformationBlock.scala @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.protocols +package pcapng + +import scodec.Codec +import scodec.bits._ +import scodec.codecs._ + +case class ProcessInformationBlock(length: Length, bytes: ByteVector) extends BodyBlock + +object ProcessInformationBlock { + + def codec(implicit ord: ByteOrdering): Codec[ProcessInformationBlock] = + "PIB" | BlockCodec.ignored(hexConstant).as[ProcessInformationBlock] + + private def hexConstant(implicit ord: ByteOrdering) = + orderDependent(hex"80000001", hex"01000080") +} diff --git a/protocols/shared/src/main/scala/fs2/protocols/pcapng/SectionHeaderBlock.scala b/protocols/shared/src/main/scala/fs2/protocols/pcapng/SectionHeaderBlock.scala new file mode 100644 index 0000000000..d84f5fa959 --- /dev/null +++ b/protocols/shared/src/main/scala/fs2/protocols/pcapng/SectionHeaderBlock.scala @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.protocols +package pcapng + +import pcap._ +import scodec.Codec +import scodec.bits._ +import scodec.codecs._ + +case class SectionHeaderBlock( + length: Length, + ordering: ByteOrdering, + majorVersion: Int, + minorVersion: Int, + bytes: ByteVector +) + +object SectionHeaderBlock { + + private val hexConstant = hex"0A0D0D0A" + + val codec: Codec[SectionHeaderBlock] = + "SHB" | BlockCodec.unknownByteOrder(hexConstant)(shbCodec).dropUnits.as[SectionHeaderBlock] + + private def shbCodec(length: Length) = + // format: off + ("Byte-Order Magic" | ByteOrderMagic ).flatPrepend { implicit byteOrder => + ("Major Version" | guint16 ) :: + ("Minor Version" | guint16 ) :: + ("Block Bytes" | bytes(length.toLong.toInt - 20) )} + // format: on +} diff --git a/protocols/shared/src/main/scala/fs2/protocols/pcapng/SimplePacketBlock.scala b/protocols/shared/src/main/scala/fs2/protocols/pcapng/SimplePacketBlock.scala new file mode 100644 index 0000000000..80e46c503d --- /dev/null +++ b/protocols/shared/src/main/scala/fs2/protocols/pcapng/SimplePacketBlock.scala @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.protocols.pcapng + +import scodec.Codec +import scodec.bits._ +import scodec.codecs._ + +case class SimplePacketBlock(length: Length, bytes: ByteVector) extends BodyBlock + +object SimplePacketBlock { + + def codec(implicit ord: ByteOrdering): Codec[ProcessInformationBlock] = + "SPB" | BlockCodec.ignored(hexConstant).as[ProcessInformationBlock] + + private def hexConstant(implicit ord: ByteOrdering) = + orderDependent(hex"00000003", hex"03000000") +} diff --git a/protocols/shared/src/main/scala/fs2/protocols/pcapng/UnrecognizedBlock.scala b/protocols/shared/src/main/scala/fs2/protocols/pcapng/UnrecognizedBlock.scala new file mode 100644 index 0000000000..64ccebb0b9 --- /dev/null +++ b/protocols/shared/src/main/scala/fs2/protocols/pcapng/UnrecognizedBlock.scala @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.protocols +package pcapng + +import scodec.Codec +import scodec.bits._ +import scodec.codecs._ + +case class UnrecognizedBlock(blockType: ByteVector, length: Length, bytes: ByteVector) + extends BodyBlock + +object UnrecognizedBlock { + + def codec(implicit ord: ByteOrdering): Codec[UnrecognizedBlock] = + "UB" | BlockCodec.unrecognizedBlockType.as[UnrecognizedBlock] +} diff --git a/protocols/shared/src/main/scala/fs2/protocols/pcapng/package.scala b/protocols/shared/src/main/scala/fs2/protocols/pcapng/package.scala new file mode 100644 index 0000000000..f7b0e8ffe2 --- /dev/null +++ b/protocols/shared/src/main/scala/fs2/protocols/pcapng/package.scala @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.protocols + +import scodec.bits.{ByteOrdering, ByteVector} + +package object pcapng { + + def orderDependent[T]( + big: ByteVector, + little: ByteVector + )(implicit ord: ByteOrdering): ByteVector = + ord match { + case ByteOrdering.BigEndian => big + case ByteOrdering.LittleEndian => little + } +} diff --git a/protocols/shared/src/test/scala/fs2/protocols/PcapNgExample.scala b/protocols/shared/src/test/scala/fs2/protocols/PcapNgExample.scala new file mode 100644 index 0000000000..e11753205f --- /dev/null +++ b/protocols/shared/src/test/scala/fs2/protocols/PcapNgExample.scala @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2 +package protocols + +import cats.effect.{IO, IOApp} +import cats.syntax.foldable._ +import cats.syntax.option._ +import fs2.io.file.{Files, Path} +import fs2.protocols.pcap.LinkType +import fs2.protocols.pcapng.CaptureFile + +object PcapNgExample extends IOApp.Simple { + + def run: IO[Unit] = + output.compile.toList.flatMap(_.traverse_(IO.println)) + + private def byteStream: Stream[IO, Byte] = + Files[IO].readAll(Path("/path/to/pcapng")) + + private def output = + byteStream.through( + CaptureFile.parse { + case (LinkType.Ethernet, bv) => bv.some + case _ => none + } + ) +} diff --git a/protocols/shared/src/test/scala/fs2/protocols/pcapng/BlockTest.scala b/protocols/shared/src/test/scala/fs2/protocols/pcapng/BlockTest.scala new file mode 100644 index 0000000000..6a962b607d --- /dev/null +++ b/protocols/shared/src/test/scala/fs2/protocols/pcapng/BlockTest.scala @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.protocols +package pcapng + +import fs2.protocols.pcap.LinkType.Ethernet +import scodec.Attempt.Successful +import scodec.DecodeResult +import scodec.bits.ByteOrdering.LittleEndian +import scodec.bits._ + +class BlockTest extends munit.FunSuite { + import BlockTest.DHCP._ + import BlockTest.{RealData => RD} + + test("dhcp shb") { + val actual = SectionHeaderBlock.codec.decode(SHB.bytes.bits) + assertEquals(actual, fullyDecoded(SHB.expected)) + } + + test("dhcp idb") { + val actual = InterfaceDescriptionBlock.codec(LittleEndian).decode(Interface.bytes.bits) + assertEquals(actual, fullyDecoded(Interface.expected)) + } + + test("dhcp epb1") { + val actual = EnhancedPacketBlock.codec(LittleEndian).decode(Enhanced1.bytes.bits) + assertEquals(actual, fullyDecoded(Enhanced1.expected)) + } + + test("dhcp epb2") { + val actual = EnhancedPacketBlock.codec(LittleEndian).decode(Enhanced2.bytes.bits) + assertEquals(actual, fullyDecoded(Enhanced2.expected)) + } + + test("dhcp epb3") { + val actual = EnhancedPacketBlock.codec(LittleEndian).decode(Enhanced3.bytes.bits) + assertEquals(actual, fullyDecoded(Enhanced3.expected)) + } + + test("dhcp epb4") { + val actual = EnhancedPacketBlock.codec(LittleEndian).decode(Enhanced4.bytes.bits) + assertEquals(actual, fullyDecoded(Enhanced4.expected)) + } + + test("real-data UnrecognizedBlock") { + val actual = UnrecognizedBlock.codec(LittleEndian).decode(RD.bytes.bits) + assertEquals(actual, fullyDecoded(RD.expectedDummy)) + } + + test("real-data epb") { + val actual = EnhancedPacketBlock.codec(LittleEndian).decode(RD.bytes.bits) + assertEquals(actual, fullyDecoded(RD.expectedEPB)) + } + + private def fullyDecoded[V](v: V) = + Successful(DecodeResult(v, BitVector.empty)) +} + +private object BlockTest { + + // This data is taken from dhcp.pcapng file from + // https://gitlab.com/wireshark/wireshark/-/wikis/Development/PcapNg + object DHCP { + object SHB { + val header = hex"0a0d0d0a" + val length = hex"1c000000" + val parsed = hex"4d3c2b1a01000000" + val nonParsed = hex"ffffffffffffffff" + val bytes = header ++ length ++ parsed ++ nonParsed ++ length + + val expected = SectionHeaderBlock(Length(length), LittleEndian, 1, 0, nonParsed) + } + + object Interface { + val header = hex"01000000" + val length = hex"20000000" + val parsed = hex"01000000ffff0000" + val nonParsed = hex"090001000600000000000000" + val bytes = header ++ length ++ parsed ++ nonParsed ++ length + val expected = InterfaceDescriptionBlock(Length(length), Ethernet, 65535, nonParsed) + } + + val enhancedHeader = hex"06000000" + + val opts = ByteVector.empty + + object Enhanced1 { + val length = hex"5c010000" + val parsed = hex"0000000083ea03000d8a33353a0100003a010000" + val data = + hex"""ffffffffffff000b + 8201fc4208004500012ca8360000fa11178b00000000ffffffff004400430118 + 591f0101060000003d1d0000000000000000000000000000000000000000000b + 8201fc4200000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000638253633501013d0701000b8201fc423204 + 0000000037040103062aff00000000000000""" + val padding = hex"0000" + val bytes = enhancedHeader ++ length ++ parsed ++ data ++ padding ++ length + + val expected = EnhancedPacketBlock(Length(length), 0, 256643, 892570125, 314, 314, data, opts) + } + + object Enhanced2 { + val length = hex"78010000" + val parsed = hex"0000000083ea0300348b33355601000056010000" + val data = + hex"""000b8201fc42000874adf19b + 0800450001480445000080110000c0a80001c0a8000a00430044013422330201 + 060000003d1d0000000000000000c0a8000ac0a8000100000000000b8201fc42 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000638253633501020104ffffff003a04000007083b0400 + 000c4e330400000e103604c0a80001ff00000000000000000000000000000000 + 00000000000000000000""" + val padding = hex"0000" + val bytes = enhancedHeader ++ length ++ parsed ++ data ++ padding ++ length + + val expected = EnhancedPacketBlock(Length(length), 0, 256643, 892570420, 342, 342, data, opts) + } + + object Enhanced3 { + val length = hex"5c010000" + val pared = hex"0000000083ea03009c9b34353a0100003a010000" + val data = + hex"""ffffffff + ffff000b8201fc4208004500012ca8370000fa11178a00000000ffffffff0044 + 004301189fbd0101060000003d1e000000000000000000000000000000000000 + 0000000b8201fc42000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000638253633501033d0701000b8201 + fc423204c0a8000a3604c0a8000137040103062aff00""" + val padding = hex"0000" + val bytes = enhancedHeader ++ length ++ pared ++ data ++ padding ++ length + + val expected = EnhancedPacketBlock(Length(length), 0, 256643, 892640156, 314, 314, data, opts) + } + + object Enhanced4 { + val length = hex"78010000" + val parsed = hex"0000000083ea0300d69c34355601000056010000" + val data = + hex"""000b8201fc420008 + 74adf19b0800450001480446000080110000c0a80001c0a8000a004300440134 + dfdb0201060000003d1e0000000000000000c0a8000a0000000000000000000b + 8201fc4200000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000638253633501053a04000007083b0400000c + 4e330400000e103604c0a800010104ffffff00ff000000000000000000000000 + 0000000000000000000000000000""" + val padding = hex"0000" + val bytes = enhancedHeader ++ length ++ parsed ++ data ++ padding ++ length + + val expected = EnhancedPacketBlock(Length(length), 0, 256643, 892640470, 342, 342, data, opts) + } + } + + object RealData { + val header = hex"06000000" + val length = Length(hex"88000000") + val props = hex"0000000003d205008418cd174200000042000000" + val data = + hex"""a483e7e0b1ad0200c9690a01080045000034ead7400022067cb336f4a8700a01 + 07d40050cc0c19b4409fe64f4fb1801000953a4800000101080a2ff46c4b386d + 1949""" + val padding = hex"0000" + val opts = hex"018004000400000002000400010000000280040000000000048004000800000000000000" + val bytes = header ++ length.bv ++ props ++ data ++ padding ++ opts ++ length.bv + + val expectedEPB = EnhancedPacketBlock(length, 0, 381443, 399317124, 66, 66, data, opts) + val expectedDummy = UnrecognizedBlock(header, length, props ++ data ++ padding ++ opts) + } +}