From d40f64c05387482787ed4bf731e0c73b25183ca4 Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 22 Sep 2016 00:35:13 -0400 Subject: [PATCH] encoding and decoding of byte-aligned Lists now working --- .../scala/net/psforever/packet/PSPacket.scala | 79 +++++++++++++++++-- common/src/test/scala/GamePacketTest.scala | 4 +- 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/common/src/main/scala/net/psforever/packet/PSPacket.scala b/common/src/main/scala/net/psforever/packet/PSPacket.scala index 12d79609..2a34e6df 100644 --- a/common/src/main/scala/net/psforever/packet/PSPacket.scala +++ b/common/src/main/scala/net/psforever/packet/PSPacket.scala @@ -3,12 +3,15 @@ package net.psforever.packet import java.nio.charset.Charset -import scodec.{DecodeResult, Err, Codec, Attempt} +import scodec.Attempt.Successful +import scodec.{Attempt, Codec, DecodeResult, Err} import scodec.bits._ import scodec.codecs._ import scodec._ import shapeless._ +import scala.util.Success + /** The base of all packets */ sealed trait PlanetSidePacket extends Serializable { def encode : Attempt[BitVector] @@ -204,6 +207,17 @@ object PacketHelpers { def encodedStringWithLimit(limit : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithLimit(limit), ascii) */ + /** + * Encode and decode a byte-aligned `List`.
+ *
+ * This function is copied almost verbatim from its source, with exception of swapping the normal `ListCodec` for a new `AlignedListCodec`. + * @param countCodec the codec that represents the prefixed size of the List + * @param alignment the number of bits padded between the List size and the List contents + * @param valueCodec a codec that describes each of the contents of the List + * @tparam A the type of the List contents + * @see codec\package.scala, listOfN + * @return a codec that works on a List of A + */ def listOfNAligned[A](countCodec: Codec[Int], alignment : Int, valueCodec: Codec[A]): Codec[List[A]] = { countCodec. flatZip { count => new AlignedListCodec(valueCodec, alignment, Some(count)) }. @@ -215,15 +229,68 @@ object PacketHelpers { } } -private final class AlignedListCodec[A](codec: Codec[A], alignment : Int, limit: Option[Int] = None) extends Codec[List[A]] { +/** + * The codec that encodes and decodes a byte-aligned `List`.
+ *
+ * This class is copied almost verbatim from its source, with only heavy modifications to its `encode` process. + * @param codec a codec that describes each of the contents of the `List` + * @param alignment the number of bits padded between the `List` size and the `List` contents (on successful) + * @param limit the number of elements in the `List` + * @tparam A the type of the `List` contents + * @see ListCodec.scala + */ +private class AlignedListCodec[A](codec: Codec[A], alignment : Int, limit: Option[Int] = None) extends Codec[List[A]] { + /** + * Convert a `List` of elements into a byte-aligned `BitVector`.
+ *
+ * Bit padding after the encoded size of the `List` is only added if the `alignment` value is greater than zero and the initial encoding process was successful. + * The padding is rather heavy-handed and a completely different `BitVector` is returned if successful. + * Performance hits for this complexity are not expected to be significant.
+ *
+ * __Warning__:
+ * A significant assumption is present in the code! + * The algorithm never confirms the bit size of the encoded size of the `List` and assumes it is equivalent to a `uint8`. + * The encoding is always split after its first eight bits. + * Obviously, if the bit size is a `uint16` or greater, the aligned encoding process will produce garbage. + * No `Exception`s will be thrown. + * @param list the `List` to be encoded + * @return the `BitVector` encoding, if successful + */ + def encode(list: List[A]) : Attempt[BitVector] = { + val solve : Attempt[BitVector] = Encoder.encodeSeq(codec)(list) + if(alignment > 0) { + solve match { + case Attempt.Successful(vector) => + return Successful(vector.take(8L) ++ BitVector.fill(alignment)(false) ++ vector.drop(8L)) + case _ => + } + } + solve + } + + /** + * Convert a byte-aligned `BitVector` into a `List` of elements. + * @param buffer the encoded bits in the `List`, preceded by the alignment bits + * @return the decoded `List` + */ + def decode(buffer: BitVector) = Decoder.decodeCollect[List, A](codec, limit)(buffer.drop(alignment)) + + /** + * The size of the encoded `List`.
+ *
+ * Unchanged from original. + * @return the size as calculated by the size of each element for each element + */ def sizeBound = limit match { case None => SizeBound.unknown case Some(lim) => codec.sizeBound * lim.toLong } - def encode(list: List[A]) = Encoder.encodeSeq(codec)(list) - - def decode(buffer: BitVector) = Decoder.decodeCollect[List, A](codec, limit)(buffer.drop(alignment)) - + /** + * Get a `String` representation of this `List`.
+ *
+ * Unchanged from original. + * @return the `String` representation + */ override def toString = s"list($codec)" } diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala index e1afabaa..457791c5 100644 --- a/common/src/test/scala/GamePacketTest.scala +++ b/common/src/test/scala/GamePacketTest.scala @@ -975,9 +975,9 @@ class GamePacketTest extends Specification { } "encode (two)" in { - val msg = HotSpotUpdateMessage(PlanetSideGUID(5),1, HotSpotInfo(0,2000,0,2700,128)::HotSpotInfo(0,2750,0,1100,128)::Nil) + val msg = HotSpotUpdateMessage(PlanetSideGUID(5),5, HotSpotInfo(0,2000,0,2700,128)::HotSpotInfo(0,2750,0,1100,128)::Nil) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector - pkt mustEqual stringOne + pkt mustEqual stringTwo } }