Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fs2-protocols - pcapng #2740

Merged
merged 20 commits into from
Nov 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
}
64 changes: 64 additions & 0 deletions protocols/shared/src/main/scala-3/BlockCodec.scala
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
Original file line number Diff line number Diff line change
@@ -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
)
}
Original file line number Diff line number Diff line change
@@ -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")
)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading