diff --git a/common/src/main/scala/net/psforever/packet/control/MultiPacketEx.scala b/common/src/main/scala/net/psforever/packet/control/MultiPacketEx.scala index 41b5b149..ff80169f 100644 --- a/common/src/main/scala/net/psforever/packet/control/MultiPacketEx.scala +++ b/common/src/main/scala/net/psforever/packet/control/MultiPacketEx.scala @@ -2,9 +2,10 @@ package net.psforever.packet.control import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideControlPacket} -import scodec.{Attempt, Codec, DecodeResult, SizeBound} +import scodec.{Attempt, Codec, DecodeResult, Err, SizeBound} import scodec.bits._ import scodec.codecs._ +import shapeless.HNil final case class MultiPacketEx(packets : Vector[ByteVector]) extends PlanetSideControlPacket { @@ -14,53 +15,57 @@ final case class MultiPacketEx(packets : Vector[ByteVector]) } object MultiPacketEx extends Marshallable[MultiPacketEx] { - val ffFound: Codec[Boolean] = new Codec[Boolean] { - def sizeBound = SizeBound.exact(0) - def encode(b: Boolean) = - if(b) - Attempt.successful(hex"ff".bits) - else - Attempt.successful(bin"") - def decode(b: BitVector) = { - if (b.length >= 8 && b.take(8) == hex"ff".bits) - Attempt.successful(DecodeResult(true, b.drop(8))) - else - Attempt.successful(DecodeResult(false, b)) + val sizeCodec : Codec[Long] = new Codec[Long] { + private def description = s"variable-bit unsigned integer" + override def sizeBound = SizeBound.bounded(8, 32) + + val MaxValue = (1L << 31) - 1 + val MinValue = 0 + + override def encode(i: Long) = { + if (i > MaxValue) { + Attempt.failure(Err(s"$i is greater than maximum value $MaxValue for $description")) + } else if (i < MinValue) { + Attempt.failure(Err(s"$i is less than minimum value $MinValue for $description")) + } else { + if(i < 0xff) { + uint8L.encode(i.toInt) + } else if(i < 0xffff) { + (constant(hex"ff") :: uint16L).dropUnits.encode(i.toInt :: HNil) + } else { + (constant(hex"ffffff") :: uint32L).dropUnits.encode(i :: HNil) + } + } } - override def toString = "bitsRemaining" - } + override def decode(buffer: BitVector) : Attempt[DecodeResult[Long]] = { + val sizeTypes = List(8, 16, 32) + val guards = List(hex"ff".bits, hex"ffff".bits) - val twoffFound: Codec[Boolean] = new Codec[Boolean] { - def sizeBound = SizeBound.exact(0) - def encode(b: Boolean) = - if(b) - Attempt.successful(hex"ffff".bits) - else - Attempt.successful(bin"") - def decode(b: BitVector) = { - if (b.length >= 16 && b.take(16) == hex"ffff".bits) - Attempt.successful(DecodeResult(true, b.drop(16))) - else - Attempt.successful(DecodeResult(false, b)) + var buf = buffer + + for(i <- sizeTypes.indices) { + val s = sizeTypes{i} + + if(!buf.sizeGreaterThanOrEqual(s)) + return Attempt.failure(Err.insufficientBits(s, buf.size)) + + val value = buf.take(s) + buf = buf.drop(s) + + if(i == guards.length || value != guards{i}) + return Attempt.successful( + DecodeResult(value.toLong(signed = false, ByteOrdering.LittleEndian), + buf) + ) + } + + // will never reach here + Attempt.failure(Err("unknown error")) } - override def toString = "bitsRemaining" + override def toString = description } - val sizeCodec = either(ffFound, uint8L, - either(twoffFound, uint16L, uint32L).xmap[Long]( - (a : Either[Int, Long]) => a.fold[Long](a => a, a => a), - (a : Long) => - if(a < 0xffff) Left(a.toInt) else Right(a) - ) - ).xmap[Long]( - (a : Either[Int, Long]) => a.fold[Long](a => a, a => a), - (a : Long) => - if(a < 0xff) Left(a.toInt) else Right(a) - ) - - implicit val codec : Codec[MultiPacketEx] = ( - ("packets" | vector(variableSizeBytesLong(sizeCodec, bytes))) - ).as[MultiPacketEx] + implicit val codec : Codec[MultiPacketEx] = ("packets" | vector(variableSizeBytesLong(sizeCodec, bytes))).as[MultiPacketEx] } \ No newline at end of file