combined initial friend and formal list of friends into one list

This commit is contained in:
FateJH 2017-01-09 23:39:16 -05:00
parent 58d7d922e4
commit 8cb5165c8b
3 changed files with 163 additions and 38 deletions

View file

@ -209,13 +209,130 @@ object PacketHelpers {
* <br> * <br>
* This function is copied almost verbatim from its source, with exception of swapping the parameter that is normally a `Nat` `literal`. * This function is copied almost verbatim from its source, with exception of swapping the parameter that is normally a `Nat` `literal`.
* The modified function takes a normal unsigned `Integer` and assures that the parameter is non-negative before further processing. * The modified function takes a normal unsigned `Integer` and assures that the parameter is non-negative before further processing.
* @param size the known size of the `List` * It casts to a `Long` and passes onto an overloaded method.
* @param size the known size of the `List`
* @param codec a codec that describes each of the contents of the `List` * @param codec a codec that describes each of the contents of the `List`
* @tparam A the type of the `List` contents * @tparam A the type of the `List` contents
* @see codec\package.scala, sizedList * @see codec\package.scala, sizedList
* @see codec\package.scala, listOfN * @see codec\package.scala, listOfN
* @see codec\package.scala, provides
* @return a codec that works on a List of A but excludes the size from the encoding * @return a codec that works on a List of A but excludes the size from the encoding
*/ */
def sizedList[A](size : Int, codec : Codec[A]) : Codec[List[A]] = listOfN(provide(if(size < 0) 0 else size), codec) def listOfNSized[A](size : Int, codec : Codec[A]) : Codec[List[A]] = listOfNSized(if(size < 0) 0L else size.asInstanceOf[Long], codec)
/**
* Codec that encodes/decodes a list of `n` elements, where `n` is known at compile time.<br>
* <br>
* This function is copied almost verbatim from its source, with exception of swapping the parameter that is normally a `Nat` `literal`.
* The modified function takes a normal unsigned `Long` and assures that the parameter is non-negative before further processing.
* @param size the known size of the `List`
* @param codec a codec that describes each of the contents of the `List`
* @tparam A the type of the `List` contents
* @see codec\package.scala, sizedList
* @see codec\package.scala, listOfN
* @see codec\package.scala, provide
* @return a codec that works on a List of A but excludes the size from the encoding
*/
def listOfNSized[A](size : Long, codec : Codec[A]) : Codec[List[A]] = listOfNAligned(provide(if(size < 0) 0 else size), 0, codec)
/**
* Encode and decode a byte-aligned `List`.<br>
* <br>
* This function is copied almost verbatim from its source, but swapping the normal `ListCodec` for a new `AlignedListCodec`.
* It also changes the type of the list length `Codec` from `Int` to `Long`.
* Due to type erasure, this method can not be overloaded for both `Codec[Int]` and `Codec[Long]`.
* The compiler would resolve both internally into type `Codec[T]` and their function definitions would be identical.
* For the purposes of use, `longL(n)` will cast to an `Int` for the same acceptable values of `n` as in `uintL(n)`.
* @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[Long], alignment : Int, valueCodec : Codec[A]) : Codec[List[A]] = {
countCodec.
flatZip {
count =>
new AlignedListCodec(countCodec, valueCodec, alignment, Some(count))
}.
narrow[List[A]] (
{
case (cnt, xs) =>
if(xs.size == cnt)
Attempt.successful(xs)
else
Attempt.failure(Err(s"Insufficient number of elements: decoded ${xs.size} but should have decoded $cnt"))
},
{
xs =>
(xs.size, xs)
}
).
withToString(s"listOfN($countCodec, $valueCodec)")
}
}
/**
* The greater `Codec` class that encodes and decodes a byte-aligned `List`.<br>
* <br>
* This class is copied almost verbatim from its source, with two major modifications.
* First, heavy modifications to its `encode` process account for the alignment value.
* Second, the length field is parsed as a `Codec[Long]` value and type conversion is accounted for at several points.
* @param countCodec the codec that represents the prefixed size of the `List`
* @param valueCodec 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](countCodec : Codec[Long], valueCodec: Codec[A], alignment : Int, limit: Option[Long] = None) extends Codec[List[A]] {
/**
* Convert a `List` of elements into a byte-aligned `BitVector`.<br>
* <br>
* 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.
* @param list the `List` to be encoded
* @return the `BitVector` encoding, if successful
*/
override def encode(list : List[A]) : Attempt[BitVector] = {
var solve : Attempt[BitVector] = Encoder.encodeSeq(valueCodec)(list)
if(alignment > 0) {
solve match {
case Attempt.Successful(vector) =>
val countCodecSize : Long = countCodec.sizeBound.lowerBound
solve = Attempt.successful(vector.take(countCodecSize) ++ BitVector.fill(alignment)(false) ++ vector.drop(countCodecSize))
case _ =>
solve = Attempt.failure(Err("failed to create a list"))
}
}
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`
*/
override def decode(buffer: BitVector) = {
val lim = Option( if(limit.isDefined) limit.get.asInstanceOf[Int] else 0 ) //TODO potentially unsafe size conversion
Decoder.decodeCollect[List, A](valueCodec, lim)(buffer.drop(alignment))
}
/**
* The size of the encoded `List`.
* @return the size as calculated by the size of each element for each element
*/
override def sizeBound = limit match {
case None =>
SizeBound.unknown
case Some(lim : Long) =>
valueCodec.sizeBound * lim
}
/**
* Get a `String` representation of this `List`.
* Unchanged from original.
* @return the `String` representation
*/
override def toString = s"list($valueCodec)"
} }

View file

@ -4,6 +4,7 @@ package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import scodec.Codec import scodec.Codec
import scodec.codecs._ import scodec.codecs._
import shapeless.{::, HNil}
/** /**
* An entry in the list of players known to and tracked by this player. * An entry in the list of players known to and tracked by this player.
@ -34,16 +35,12 @@ final case class Friend(name : String,
* @param unk1 na; always 0? * @param unk1 na; always 0?
* @param unk2 na; always `true`? * @param unk2 na; always `true`?
* @param unk3 na; always `true`? * @param unk3 na; always `true`?
* @param number_of_friends the number of `Friend` entries handled by this packet; max is 15 per packet * @param friends a list of `Friend`s
* @param friend the first `Friend` entry
* @param friends all the other `Friend` entries
*/ */
final case class FriendsResponse(action : Int, final case class FriendsResponse(action : Int,
unk1 : Int, unk1 : Int,
unk2 : Boolean, unk2 : Boolean,
unk3 : Boolean, unk3 : Boolean,
number_of_friends : Int,
friend : Option[Friend] = None,
friends : List[Friend] = Nil) friends : List[Friend] = Nil)
extends PlanetSideGamePacket { extends PlanetSideGamePacket {
type Packet = FriendsResponse type Packet = FriendsResponse
@ -75,7 +72,23 @@ object FriendsResponse extends Marshallable[FriendsResponse] {
("unk3" | bool) :: ("unk3" | bool) ::
(("number_of_friends" | uint4L) >>:~ { len => (("number_of_friends" | uint4L) >>:~ { len =>
conditional(len > 0, "friend" | Friend.codec) :: conditional(len > 0, "friend" | Friend.codec) ::
("friends" | PacketHelpers.sizedList(len-1, Friend.codec_list)) //List of 'Friend(String, Boolean)'s without a size field when encoded ("friends" | PacketHelpers.listOfNSized(len-1, Friend.codec_list))
}) })
).as[FriendsResponse] ).xmap[FriendsResponse] (
{
case act :: u1 :: u2 :: u3 :: num :: friend1 :: friends :: HNil =>
val friendList : List[Friend] = if(friend1.isDefined) { friend1.get :: friends } else { friends }
FriendsResponse(act, u1, u2, u3, friendList)
},
{
case FriendsResponse(act, u1, u2, u3, friends) =>
var friend1 : Option[Friend] = None
var friendList : List[Friend] = Nil
if(friends.nonEmpty) {
friend1 = Some(friends.head)
friendList = friends.drop(1)
}
act :: u1 :: u2 :: u3 :: friends.size :: friend1 :: friendList :: HNil
}
)
} }

View file

@ -837,16 +837,14 @@ class GamePacketTest extends Specification {
"decode (one friend)" in { "decode (one friend)" in {
PacketCoding.DecodePacket(stringOneFriend).require match { PacketCoding.DecodePacket(stringOneFriend).require match {
case FriendsResponse(unk1, unk2, unk3, unk4, number_of_friends, friend, list) => case FriendsResponse(action, unk2, unk3, unk4, list) =>
unk1 mustEqual 3 action mustEqual 3
unk2 mustEqual 0 unk2 mustEqual 0
unk3 mustEqual true unk3 mustEqual true
unk4 mustEqual true unk4 mustEqual true
number_of_friends mustEqual 1 list.size mustEqual 1
friend.isDefined mustEqual true list.head.name mustEqual "KurtHectic-G"
friend.get.name mustEqual "KurtHectic-G" list.head.online mustEqual false
friend.get.online mustEqual false
list.size mustEqual 0
case default => case default =>
ko ko
} }
@ -854,24 +852,22 @@ class GamePacketTest extends Specification {
"decode (multiple friends)" in { "decode (multiple friends)" in {
PacketCoding.DecodePacket(stringManyFriends).require match { PacketCoding.DecodePacket(stringManyFriends).require match {
case FriendsResponse(unk1, unk2, unk3, unk4, number_of_friends, friend, list) => case FriendsResponse(action, unk2, unk3, unk4, list) =>
unk1 mustEqual 0 action mustEqual 0
unk2 mustEqual 0 unk2 mustEqual 0
unk3 mustEqual true unk3 mustEqual true
unk4 mustEqual true unk4 mustEqual true
number_of_friends mustEqual 5 list.size mustEqual 5
friend.isDefined mustEqual true list.head.name mustEqual "Angello-W"
friend.get.name mustEqual "Angello-W"
friend.get.online mustEqual false
list.size mustEqual 4
list.head.name mustEqual "thephattphrogg"
list.head.online mustEqual false list.head.online mustEqual false
list(1).name mustEqual "Kimpossible12" list(1).name mustEqual "thephattphrogg"
list(1).online mustEqual false list(1).online mustEqual false
list(2).name mustEqual "Zearthling" list(2).name mustEqual "Kimpossible12"
list(2).online mustEqual false list(2).online mustEqual false
list(3).name mustEqual "KurtHectic-G" list(3).name mustEqual "Zearthling"
list(3).online mustEqual false list(3).online mustEqual false
list(4).name mustEqual "KurtHectic-G"
list(4).online mustEqual false
case default => case default =>
ko ko
} }
@ -879,13 +875,11 @@ class GamePacketTest extends Specification {
"decode (short)" in { "decode (short)" in {
PacketCoding.DecodePacket(stringShort).require match { PacketCoding.DecodePacket(stringShort).require match {
case FriendsResponse(unk1, unk2, unk3, unk4, number_of_friends, friend, list) => case FriendsResponse(action, unk2, unk3, unk4, list) =>
unk1 mustEqual 4 action mustEqual 4
unk2 mustEqual 0 unk2 mustEqual 0
unk3 mustEqual true unk3 mustEqual true
unk4 mustEqual true unk4 mustEqual true
number_of_friends mustEqual 0
friend.isDefined mustEqual false
list.size mustEqual 0 list.size mustEqual 0
case default => case default =>
ko ko
@ -893,24 +887,25 @@ class GamePacketTest extends Specification {
} }
"encode (one friend)" in { "encode (one friend)" in {
val msg = FriendsResponse(3, 0, true, true, 1, Option(Friend("KurtHectic-G", false))) val msg = FriendsResponse(3, 0, true, true, Friend("KurtHectic-G", false) :: Nil)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringOneFriend pkt mustEqual stringOneFriend
} }
"encode (multiple friends)" in { "encode (multiple friends)" in {
val msg = FriendsResponse(0, 0, true, true, 5, Option(Friend("Angello-W", false)), Friend("thephattphrogg", false) :: val msg = FriendsResponse(0, 0, true, true, Friend("Angello-W", false) ::
Friend("Kimpossible12", false) :: Friend("thephattphrogg", false) ::
Friend("Zearthling", false) :: Friend("Kimpossible12", false) ::
Friend("KurtHectic-G", false) :: Nil) Friend("Zearthling", false) ::
Friend("KurtHectic-G", false) :: Nil)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringManyFriends pkt mustEqual stringManyFriends
} }
"encode (short)" in { "encode (short)" in {
val msg = FriendsResponse(4, 0, true, true, 0) val msg = FriendsResponse(4, 0, true, true)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringShort pkt mustEqual stringShort