combining hamfisted approach and hlist approach to better manage failure cases

This commit is contained in:
FateJH 2016-12-06 13:26:28 -05:00
parent d66489a572
commit 9adb077d8c
13 changed files with 205 additions and 304 deletions

View file

@ -3,7 +3,8 @@ package net.psforever.packet.game
import net.psforever.packet.game.objectcreate.{ConstructorData, ObjectClass}
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import scodec.{Attempt, Codec, Err}
import scodec.bits.BitVector
import scodec.{Attempt, Codec, DecodeResult, Err}
import scodec.codecs._
import shapeless.{::, HNil}
@ -40,15 +41,12 @@ case class ObjectCreateMessageParent(guid : PlanetSideGUID,
* (The GM-level command `/sync` tests for objects that "do not match" between the server and the client.
* It's implementation and scope are undefined.)<br>
* <br>
* Knowing the object's class is essential for parsing the specific information passed by the `data` parameter.<br>
* <br>
* Exploration:<br>
* Can we build a `case class` "foo" that can accept the `objectClass` and the `data` and construct any valid object automatically?
* Knowing the object's class is essential for parsing the specific information passed by the `data` parameter.
* @param streamLength the total length of the data that composes this packet in bits, excluding the opcode and end padding
* @param objectClass the code for the type of object being constructed
* @param guid the GUID this object will be assigned
* @param parentInfo if defined, the relationship between this object and another object (its parent)
* @param data the data used to construct this type of object
* @param data if defined, the data used to construct this type of object
*/
case class ObjectCreateMessage(streamLength : Long,
objectClass : Int,
@ -61,52 +59,133 @@ case class ObjectCreateMessage(streamLength : Long,
}
object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] {
type Pattern = Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: Option[ConstructorData] :: HNil
type Pattern = Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: HNil
type outPattern = Long :: Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: Option[ConstructorData] :: HNil
/**
* Codec for formatting around the lack of parent data in the stream.
*/
val noParent : Codec[Pattern] = (
("objectClass" | uintL(0xb)) >>:~ { cls => //11u
("guid" | PlanetSideGUID.codec) :: //16u
("data" | ObjectClass.selectDataCodec(cls))
}
).xmap[Pattern] (
private val noParent : Codec[Pattern] = (
("objectClass" | uintL(0xb)) :: //11u
("guid" | PlanetSideGUID.codec) //16u
).xmap[Pattern](
{
case cls :: guid :: data :: HNil =>
cls :: guid :: None :: data :: HNil
},
{
case cls :: guid :: None :: data :: HNil =>
cls :: guid :: data :: HNil
case cls :: guid :: HNil =>
cls :: guid :: None :: HNil
}, {
case cls :: guid :: None :: HNil =>
cls :: guid :: HNil
}
)
/**
* Codec for reading and formatting parent data from the stream.
*/
val parent : Codec[Pattern] = (
private val parent : Codec[Pattern] = (
("parentGuid" | PlanetSideGUID.codec) :: //16u
(("objectClass" | uintL(0xb)) >>:~ { cls => //11u
("guid" | PlanetSideGUID.codec) :: //16u
("parentSlotIndex" | PacketHelpers.encodedStringSize) :: //8u or 16u
("data" | ObjectClass.selectDataCodec(cls))
})
).xmap[Pattern] (
("objectClass" | uintL(0xb)) :: //11u
("guid" | PlanetSideGUID.codec) :: //16u
("parentSlotIndex" | PacketHelpers.encodedStringSize) //8u or 16u
).xmap[Pattern](
{
case pguid :: cls :: guid :: slot :: data :: HNil =>
cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: data :: HNil
},
{
case cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: data :: HNil =>
pguid :: cls :: guid :: slot :: data :: HNil
case pguid :: cls :: guid :: slot :: HNil =>
cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil
}, {
case cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil =>
pguid :: cls :: guid :: slot :: HNil
}
)
/**
* Calculate the stream length in number of bits by factoring in the two variable fields.<br>
* Take bit data and transform it into an object that expresses the important information of a game piece.<br>
* <br>
* Constant fields have already been factored into the results.
* This function is fail-safe because it catches errors involving bad parsing of the bitstream data.
* Generally, the `Exception` messages themselves are not useful.
* The important parts are what the packet thought the object class should be and what it actually processed.
* The bit data that failed to parse is retained for debugging at a later time.
* @param objectClass the code for the type of object being constructed
* @param data the bitstream data
* @return the optional constructed object
*/
private def decodeData(objectClass : Int, data : BitVector) : Option[ConstructorData] = {
var out : Option[ConstructorData] = None
val copy = data.drop(0)
try {
val outOpt : Option[DecodeResult[_]] = ObjectClass.selectDataCodec(objectClass).decode(copy).toOption
if(outOpt.isDefined)
out = outOpt.get.value.asInstanceOf[ConstructorData.genericPattern]
}
catch {
case ex : Exception =>
//catch and release, any sort of parse error
}
out
}
/**
* Take the important information of a game piece and transform it into bit data.<br>
* <br>
* This function is fail-safe because it catches errors involving bad parsing of the object data.
* Generally, the `Exception` messages themselves are not useful.
* If parsing fails, all data pertinent to debugging the failure is retained in the constructor.
* @param objClass the code for the type of object being deconstructed
* @param obj the object data
* @return the bitstream data
*/
private def encodeData(objClass : Int, obj : ConstructorData) : BitVector = {
var out = BitVector.empty
try {
val outOpt : Option[BitVector] = ObjectClass.selectDataCodec(objClass).encode(Some(obj.asInstanceOf[ConstructorData])).toOption
if(outOpt.isDefined)
out = outOpt.get
}
catch {
case ex : Exception =>
//catch and release, any sort of parse error
}
out
}
/**
* Calculate the stream length in number of bits by factoring in the whole message in two portions.
* @param parentInfo if defined, information about the parent
* @param data the data length is indeterminate until it is read
* @return the total length of the stream in bits
*/
private def streamLen(parentInfo : Option[ObjectCreateMessageParent], data : BitVector) : Long = {
//knowable length
val first : Long = commonMsgLen(parentInfo)
//data length
var second : Long = data.size
val secondMod4 : Long = second % 4L
if(secondMod4 > 0L) {
//pad to include last whole nibble
second += 4L - secondMod4
}
first + second
}
/**
* Calculate the stream length in number of bits by factoring in the whole message in two portions.
* @param parentInfo if defined, information about the parent
* @param data the data length is indeterminate until it is read
* @return the total length of the stream in bits
*/
private def streamLen(parentInfo : Option[ObjectCreateMessageParent], data : ConstructorData) : Long = {
//knowable length
val first : Long = commonMsgLen(parentInfo)
//data length
var second : Long = data.bitsize
val secondMod4 : Long = second % 4L
if(secondMod4 > 0L) {
//pad to include last whole nibble
second += 4L - secondMod4
}
first + second
}
/**
* Calculate the length (in number of bits) of the basic packet message region.<br>
* <br>
* Ignoring the parent data, constant field lengths have already been factored into the results.
* That includes:
* the length of the stream length field (32u),
* the object's class (11u),
@ -114,164 +193,58 @@ object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] {
* and the bit to determine if there will be parent data.
* In total, these fields form a known fixed length of 60u.
* @param parentInfo if defined, the parentInfo adds either 24u or 32u
* @param data the data length is indeterminate until it is read
* @return the total length of the stream in bits
* @return the length, including the optional parent data
*/
private def streamLen(parentInfo : Option[ObjectCreateMessageParent], data : Option[ConstructorData]) : Long = {
//msg length
val first : Long = if(parentInfo.isDefined) { //(32u + 1u + 11u + 16u) ?+ (16u + (8u | 16u))
private def commonMsgLen(parentInfo : Option[ObjectCreateMessageParent]) : Long = {
if(parentInfo.isDefined) {
//(32u + 1u + 11u + 16u) ?+ (16u + (8u | 16u))
if(parentInfo.get.slot > 127) 92L else 84L
}
else {
60L
}
//data length
var second : Long = if(data.isDefined) data.get.bsize else 0L
val secondMod4 : Long = second % 4L
if(secondMod4 > 0L) { //pad to include last whole nibble
second += 4L - secondMod4
}
first + second
}
implicit val codec : Codec[ObjectCreateMessage] = (
("streamLength" | uint32L) ::
either(bool, parent, noParent).exmap[Pattern] (
(either(bool, parent, noParent).exmap[Pattern] (
{
case Left(a :: b :: Some(c) :: d :: HNil) =>
Attempt.successful(a :: b :: Some(c) :: d :: HNil) //true, _, _, Some(c)
case Right(a :: b :: None :: d :: HNil) =>
Attempt.successful(a :: b :: None :: d :: HNil) //false, _, _, None
case Left(a :: b :: Some(c) :: HNil) =>
Attempt.successful(a :: b :: Some(c) :: HNil) //true, _, _, Some(c)
case Right(a :: b :: None :: HNil) =>
Attempt.successful(a :: b :: None :: HNil) //false, _, _, None
// failure cases
case Left(a :: b :: None :: _ :: HNil) =>
case Left(a :: b :: None :: HNil) =>
Attempt.failure(Err("missing parent structure")) //true, _, _, None
case Right(a :: b :: Some(c) :: _ :: HNil) =>
case Right(a :: b :: Some(c) :: HNil) =>
Attempt.failure(Err("unexpected parent structure")) //false, _, _, Some(c)
},
{
case a :: b :: Some(c) :: d :: HNil =>
Attempt.successful(Left(a :: b :: Some(c) :: d :: HNil))
case a :: b :: None :: d :: HNil =>
Attempt.successful(Right(a :: b :: None :: d :: HNil))
}, {
case a :: b :: Some(c) :: HNil =>
Attempt.successful(Left(a :: b :: Some(c) :: HNil))
case a :: b :: None :: HNil =>
Attempt.successful(Right(a :: b :: None :: HNil))
}
)
).xmap[outPattern] (
) :+
("data" | bits)) //greed is good
).xmap[outPattern](
{
case len :: cls :: guid :: par :: data :: HNil =>
len :: cls :: guid :: par :: data :: HNil
},
len :: cls :: guid :: par :: decodeData(cls, data) :: HNil
}, {
case _ :: cls :: guid :: par :: Some(obj) :: HNil =>
streamLen(par, obj) :: cls :: guid :: par :: encodeData(cls, obj) :: HNil
case _ :: cls :: guid :: par :: None :: HNil =>
streamLen(par, BitVector.empty) :: cls :: guid :: par :: BitVector.empty :: HNil
}
).exmap[ObjectCreateMessage](
{
case _ :: cls :: guid :: par :: data :: HNil =>
streamLen(par, data) :: cls :: guid :: par :: data :: HNil
case len :: cls :: guid :: par :: obj :: HNil =>
Attempt.successful(ObjectCreateMessage(len, cls, guid, par, obj))
}, {
case ObjectCreateMessage(_, _, _, _, None) =>
Attempt.failure(Err("no object to encode"))
case ObjectCreateMessage(len, cls, guid, par, obj) =>
Attempt.successful(len :: cls :: guid :: par :: obj :: HNil)
}
).as[ObjectCreateMessage]
}
//import net.psforever.packet.game.objectcreate.Mold
//import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
//import scodec.bits._
//import scodec.{Attempt, Codec, Err}
//import scodec.codecs._
//import shapeless.{::, HNil}
//
//case class ObjectCreateMessageParent(guid : PlanetSideGUID,
// slot : Int)
//
//case class ObjectCreateMessage(streamLength : Long,
// objectClass : Int,
// guid : PlanetSideGUID,
// parentInfo : Option[ObjectCreateMessageParent],
// mold : Mold)
// extends PlanetSideGamePacket {
// def opcode = GamePacketOpcode.ObjectCreateMessage
// def encode = ObjectCreateMessage.encode(this)
//}
//
//object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] {
// type Pattern = Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: HNil
// /**
// * Codec for formatting around the lack of parent data in the stream.
// */
// val noParent : Codec[Pattern] = (
// ("objectClass" | uintL(0xb)) :: //11u
// ("guid" | PlanetSideGUID.codec) //16u
// ).xmap[Pattern] (
// {
// case cls :: guid :: HNil =>
// cls :: guid :: None :: HNil
// },
// {
// case cls :: guid :: None :: HNil =>
// cls :: guid :: HNil
// }
// )
//
// /**
// * Codec for reading and formatting parent data from the stream.
// */
// val parent : Codec[Pattern] = (
// ("parentGuid" | PlanetSideGUID.codec) :: //16u
// ("objectClass" | uintL(0xb)) :: //11u
// ("guid" | PlanetSideGUID.codec) :: //16u
// ("parentSlotIndex" | PacketHelpers.encodedStringSize) //8u or 16u
// ).xmap[Pattern] (
// {
// case pguid :: cls :: guid :: slot :: HNil =>
// cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil
// },
// {
// case cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil =>
// pguid :: cls :: guid :: slot :: HNil
// }
// )
//
// private def streamLen(parentInfo : Option[ObjectCreateMessageParent], data : BitVector) : Long = {
// //known length
// val first : Long = if(parentInfo.isDefined) {
// if(parentInfo.get.slot > 127) 92L else 84L //60u + 16u + (8u or 16u)
// }
// else {
// 60L
// }
// //variant length
// var second : Long = data.size
// val secondMod4 : Long = second % 4L
// if(secondMod4 > 0L) { //pad to include last whole nibble
// second += 4L - secondMod4
// }
// first + second
// }
//
// implicit val codec : Codec[ObjectCreateMessage] = (
// ("streamLength" | uint32L) ::
// (either(bool, parent, noParent).exmap[Pattern] (
// {
// case Left(a :: b :: Some(c) :: HNil) =>
// Attempt.successful(a :: b :: Some(c) :: HNil) //true, _, _, Some(c)
// case Right(a :: b :: None :: HNil) =>
// Attempt.successful(a :: b :: None :: HNil) //false, _, _, None
// // failure cases
// case Left(a :: b :: None :: HNil) =>
// Attempt.failure(Err("missing parent structure")) //true, _, _, None
// case Right(a :: b :: Some(c) :: HNil) =>
// Attempt.failure(Err("unexpected parent structure")) //false, _, _, Some(c)
// },
// {
// case a :: b :: Some(c) :: HNil =>
// Attempt.successful(Left(a :: b :: Some(c) :: HNil))
// case a :: b :: None :: HNil =>
// Attempt.successful(Right(a :: b :: None :: HNil))
// }
// ) :+
// ("data" | bits) )
// ).xmap[ObjectCreateMessage] (
// {
// case len :: cls :: guid :: info :: data :: HNil =>
// ObjectCreateMessage(len, cls, guid, info, Mold(cls, data))
// },
// {
// case ObjectCreateMessage(_, cls, guid, info, mold) =>
// streamLen(info, mold.data) :: cls :: guid :: info :: mold.data :: HNil
// }
// ).as[ObjectCreateMessage]
//}
}

View file

@ -8,7 +8,7 @@ import shapeless.{::, HNil}
case class AmmoBoxData(magazine : Int
) extends ConstructorData {
override def bsize : Long = 39L
override def bitsize : Long = 39L
}
object AmmoBoxData extends Marshallable[AmmoBoxData] {

View file

@ -42,8 +42,8 @@ case class CharacterData(pos : Vector3,
tutorial_list : List[String],
inventory : InventoryData
) extends ConstructorData {
override def bsize : Long = {
//represents static fields
override def bitsize : Long = {
//represents static fields (includes medals.bitsize)
val first : Long = 1194L //TODO due to changing understanding of the bit patterns in this data, this value will change
//name
val second : Long = CharacterData.stringBitSize(name, 16) + 4L //plus the padding
@ -60,7 +60,7 @@ case class CharacterData(pos : Vector3,
for(str <- tutorial_list) {
fourth += CharacterData.stringBitSize(str)
}
first + second + third + fourth + inventory.bsize
first + second + third + fourth + inventory.bitsize
}
}

View file

@ -2,7 +2,7 @@
package net.psforever.packet.game.objectcreate
abstract class ConstructorData() {
def bsize : Long = 0L
def bitsize : Long = 0L
}
object ConstructorData {

View file

@ -19,9 +19,9 @@ case class InternalSlot(objectClass : Int,
guid : PlanetSideGUID,
parentSlot : Int,
obj : Option[ConstructorData]) {
def bsize : Long = {
def bitsize : Long = {
val first : Long = if(parentSlot > 127) 44L else 36L
val second : Long = if(obj.isDefined) obj.get.bsize else 0L
val second : Long = if(obj.isDefined) obj.get.bitsize else 0L
first + second
}
}

View file

@ -3,14 +3,13 @@ package net.psforever.packet.game.objectcreate
import net.psforever.packet.{Marshallable, PacketHelpers}
import scodec.Codec
import scodec.bits.BitVector
import scodec.codecs._
case class InventoryData(unk1 : Boolean,
size : Int,
unk2 : Boolean){//,
//inv : List[InventoryItem]) {
def bsize : Long = {
def bitsize : Long = {
10L
}
}
@ -18,7 +17,7 @@ case class InventoryData(unk1 : Boolean,
object InventoryData extends Marshallable[InventoryData] {
implicit val codec : Codec[InventoryData] = (
("unk1" | bool) ::
(("size" | uint8L) >>:~ { len =>
(("len" | uint8L) >>:~ { len =>
("unk2" | bool).hlist// ::
//("inv" | PacketHelpers.listOfNSized(len, InventoryItem.codec))
})

View file

@ -1,66 +0,0 @@
// Copyright (c) 2016 PSForever.net to present
package net.psforever.packet.game.objectcreate
import scodec.DecodeResult
import scodec.bits.BitVector
case class Mold(objectClass : Int,
data : BitVector) {
private var obj : Option[ConstructorData] = Mold.decode(objectClass, data)
def isDefined : Boolean = this.obj.isDefined
def get : ConstructorData = this.obj.get
def set(data : ConstructorData) : Boolean = {
var ret = false
if(Some(data).isDefined) {
obj = Some(data)
ret = true
}
ret
}
}
object Mold {
def apply(objectClass : Int, obj : ConstructorData) : Mold =
new Mold( objectClass, Mold.encode(objectClass, obj) )
def decode(objClass : Int, data : BitVector) : Option[ConstructorData] = {
var out : Option[ConstructorData] = None
if(!data.isEmpty) {
val codec = ObjectClass.selectDataCodec(objClass)
var outOpt : Option[DecodeResult[_]] = None
try {
outOpt = codec.decode(data).toOption
if(outOpt.isDefined)
out = Some(outOpt.get.value.asInstanceOf[ConstructorData])
}
catch {
case ex : ClassCastException =>
//TODO generate and log wrong class error message
case ex : Exception =>
//TODO generic error
}
}
out
}
def encode(objClass : Int, obj : ConstructorData) : BitVector = {
var out = BitVector.empty
try {
val codec = ObjectClass.selectDataCodec(objClass)
var outOpt : Option[BitVector] = None
outOpt = codec.encode(obj.asInstanceOf[ConstructorData.genericPattern]).toOption
if(outOpt.isDefined)
out = outOpt.get
}
catch {
case ex : ClassCastException =>
//TODO generate and log wrong class error message
case ex : Exception =>
//TODO generic error
}
out
}
}

View file

@ -1,7 +1,8 @@
// Copyright (c) 2016 PSForever.net to present
package net.psforever.packet.game.objectcreate
import scodec.Codec
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import scala.annotation.switch
@ -36,18 +37,17 @@ object ObjectClass {
case ObjectClass.SUPPRESSOR => WeaponData.genericCodec
case ObjectClass.REK => REKData.genericCodec
case ObjectClass.SLOT_BLOCKER => AmmoBoxData.genericCodec
case _ => RecoveredData.genericCodec
//failure case
case _ => conditional(false, bool).exmap[ConstructorData.genericPattern] (
{
case None | _ =>
Attempt.failure(Err("decoding unknown object class - "+objClass))
},
{
case None | _ =>
Attempt.failure(Err("encoding unknown object class - "+objClass))
}
)
}
}
// val failureCodec : Codec[ConstructorData.genericPattern] = conditional(false, bool).exmap[ConstructorData.genericPattern] (
// {
// case None | _ =>
// Attempt.failure(Err("object class unrecognized during decoding"))
// },
// {
// case None | _ =>
// Attempt.failure(Err("object class unrecognized during encoding"))
// }
// )
}
}

View file

@ -8,7 +8,7 @@ import shapeless.{::, HNil}
case class REKData(unk : Int
) extends ConstructorData {
override def bsize : Long = 72L
override def bitsize : Long = 72L
}
object REKData extends Marshallable[REKData] {

View file

@ -1,29 +0,0 @@
// Copyright (c) 2016 PSForever.net to present
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import scodec.bits.BitVector
case class RecoveredData(data : BitVector
) extends ConstructorData {
override def bsize : Long = data.size
}
object RecoveredData extends Marshallable[RecoveredData] {
implicit val codec : Codec[RecoveredData] = (
"data" | bits
).as[RecoveredData]
val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
{
case _ =>
Attempt.failure(Err("un-parsed byte data preserved when decoding failed"))
},
{
case _ =>
Attempt.failure(Err("can not encode object"))
}
)
}

View file

@ -8,7 +8,9 @@ import scodec.codecs._
case class RibbonBars(upper : Long = 0xFFFFFFFFL, //0xFFFFFFFF means no merit (for all ...)
middle : Long = 0xFFFFFFFFL,
lower : Long = 0xFFFFFFFFL,
tos : Long = 0xFFFFFFFFL)
tos : Long = 0xFFFFFFFFL) {
def bitsize : Long = 128L
}
object RibbonBars extends Marshallable[RibbonBars] {
implicit val codec : Codec[RibbonBars] = (

View file

@ -9,7 +9,7 @@ import shapeless.{::, HNil}
case class WeaponData(unk : Int,
ammo : InternalSlot) extends ConstructorData {
override def bsize : Long = 59L + ammo.bsize
override def bitsize : Long = 59L + ammo.bitsize
}
object WeaponData extends Marshallable[WeaponData] {

View file

@ -148,6 +148,8 @@ class GamePacketTest extends Specification {
"ObjectCreateMessage" should {
val packet = hex"18 CF 13 00 00 BC 87 00 0A F0 16 C3 43 A1 30 90 00 02 C0 40 00 08 70 43 00 68 00 6F 00 72 00 64 00 54 00 52 00 82 65 1F F5 9E 80 80 00 00 00 00 00 3F FF C0 00 00 00 20 00 00 00 20 27 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FC CC 10 00 03 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 00 00 00 01 00 7E C8 00 C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 C0 00 42 C5 46 86 C7 00 00 02 A0 00 00 12 60 78 70 65 5F 77 61 72 70 5F 67 61 74 65 5F 75 73 61 67 65 92 78 70 65 5F 69 6E 73 74 61 6E 74 5F 61 63 74 69 6F 6E 92 78 70 65 5F 73 61 6E 63 74 75 61 72 79 5F 68 65 6C 70 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 32 8E 78 70 65 5F 66 6F 72 6D 5F 73 71 75 61 64 8E 78 70 65 5F 74 68 5F 6E 6F 6E 73 61 6E 63 8B 78 70 65 5F 74 68 5F 61 6D 6D 6F 90 78 70 65 5F 74 68 5F 66 69 72 65 6D 6F 64 65 73 8F 75 73 65 64 5F 63 68 61 69 6E 62 6C 61 64 65 9A 76 69 73 69 74 65 64 5F 62 72 6F 61 64 63 61 73 74 5F 77 61 72 70 67 61 74 65 8E 76 69 73 69 74 65 64 5F 6C 6F 63 6B 65 72 8D 75 73 65 64 5F 70 75 6E 69 73 68 65 72 88 75 73 65 64 5F 72 65 6B 8D 75 73 65 64 5F 72 65 70 65 61 74 65 72 9F 76 69 73 69 74 65 64 5F 64 65 63 6F 6E 73 74 72 75 63 74 69 6F 6E 5F 74 65 72 6D 69 6E 61 6C 8F 75 73 65 64 5F 73 75 70 70 72 65 73 73 6F 72 96 76 69 73 69 74 65 64 5F 6F 72 64 65 72 5F 74 65 72 6D 69 6E 61 6C 85 6D 61 70 31 35 85 6D 61 70 31 34 85 6D 61 70 31 32 85 6D 61 70 30 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 0A 36 13 88 04 00 40 00 00 10 00 04 00 00 4D 6E 40 10 41 00 00 00 40 00 18 08 38 1C C0 20 32 00 00 07 80 15 E1 D0 02 10 20 00 00 08 00 03 01 07 13 A8 04 06 40 00 00 10 03 20 BB 00 42 E4 00 00 01 00 0E 07 70 08 6C 80 00 06 40 01 C0 F0 01 13 90 00 00 C8 00 38 1E 40 23 32 00 00 19 00 07 03 D0 05 0E 40 00 03 20 00 E8 7B 00 A4 C8 00 00 64 00 DA 4F 80 14 E1 00 00 00 40 00 18 08 38 1F 40 20 32 00 00 0A 00 08 " //fake data?
val packet2 = hex"18 F8 00 00 00 BC 8C 10 90 3B 45 C6 FA 94 00 9F F0 00 00 40 00 08 C0 44 00 69 00 66 00 66 00 45" //fake data
//val packet2Rest = packet2.bits.drop(8 + 32 + 1 + 11 + 16)
var string_inventoryItem = hex"46 04 C0 08 08 80 00 00 20 00 0C 04 10 29 A0 10 19 00 00 04 00 00"
val string_9mm = hex"18 7C000000 2580 0E0 0005 A1 C8000064000"
val string_gauss = hex"18 DC000000 2580 2C9 B905 82 480000020000C04 1C00C0B0190000078000"
@ -174,6 +176,20 @@ class GamePacketTest extends Specification {
}
}
"decode (2)" in {
//an invalid bit representation will fail to turn into an object
PacketCoding.DecodePacket(packet2).require match {
case obj @ ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 248 //60 + 188
cls mustEqual 121
guid mustEqual PlanetSideGUID(2497)
parent mustEqual None
data.isDefined mustEqual false
case default =>
ko
}
}
"decode (char)" in {
PacketCoding.DecodePacket(string_testchar).require match {
case obj @ ObjectCreateMessage(len, cls, guid, parent, data) =>
@ -236,15 +252,15 @@ class GamePacketTest extends Specification {
"decode (9mm)" in {
PacketCoding.DecodePacket(string_9mm).require match {
case obj @ ObjectCreateMessage(len, cls, guid, parent, mold) =>
case obj @ ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 124
cls mustEqual 28
guid mustEqual PlanetSideGUID(1280)
parent.isDefined mustEqual true
parent.get.guid mustEqual PlanetSideGUID(75)
parent.get.slot mustEqual 33
mold.isDefined mustEqual true
val obj = mold.get.asInstanceOf[AmmoBoxData]
data.isDefined mustEqual true
val obj = data.get.asInstanceOf[AmmoBoxData]
obj.magazine mustEqual 50
case default =>
ko
@ -253,15 +269,15 @@ class GamePacketTest extends Specification {
"decode (gauss)" in {
PacketCoding.DecodePacket(string_gauss).require match {
case obj @ ObjectCreateMessage(len, cls, guid, parent, mold) =>
case obj @ ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 220
cls mustEqual 345
guid mustEqual PlanetSideGUID(1465)
parent.isDefined mustEqual true
parent.get.guid mustEqual PlanetSideGUID(75)
parent.get.slot mustEqual 2
mold.isDefined mustEqual true
val obj_wep = mold.get.asInstanceOf[WeaponData]
data.isDefined mustEqual true
val obj_wep = data.get.asInstanceOf[WeaponData]
obj_wep.unk mustEqual 4
val obj_ammo = obj_wep.ammo//.asInstanceOf[InternalSlot]
obj_ammo.objectClass mustEqual 28
@ -275,6 +291,12 @@ class GamePacketTest extends Specification {
}
}
"encode (2)" in {
//the lack of an object will fail to turn into a bad bitstream
val msg = ObjectCreateMessage(0, 121, PlanetSideGUID(2497), None, None)
PacketCoding.EncodePacket(msg).isFailure mustEqual true
}
"encode (9mm)" in {
val obj : ConstructorData = AmmoBoxData(50).asInstanceOf[ConstructorData]
val msg = ObjectCreateMessage(0, 28, PlanetSideGUID(1280), Some(ObjectCreateMessageParent(PlanetSideGUID(75), 33)), Some(obj))