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.game.objectcreate.{ConstructorData, ObjectClass}
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} 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 scodec.codecs._
import shapeless.{::, HNil} 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. * (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> * It's implementation and scope are undefined.)<br>
* <br> * <br>
* Knowing the object's class is essential for parsing the specific information passed by the `data` parameter.<br> * Knowing the object's class is essential for parsing the specific information passed by the `data` parameter.
* <br>
* Exploration:<br>
* Can we build a `case class` "foo" that can accept the `objectClass` and the `data` and construct any valid object automatically?
* @param streamLength the total length of the data that composes this packet in bits, excluding the opcode and end padding * @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 objectClass the code for the type of object being constructed
* @param guid the GUID this object will be assigned * @param guid the GUID this object will be assigned
* @param parentInfo if defined, the relationship between this object and another object (its parent) * @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, case class ObjectCreateMessage(streamLength : Long,
objectClass : Int, objectClass : Int,
@ -61,52 +59,133 @@ case class ObjectCreateMessage(streamLength : Long,
} }
object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] { 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 type outPattern = Long :: Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: Option[ConstructorData] :: HNil
/** /**
* Codec for formatting around the lack of parent data in the stream. * Codec for formatting around the lack of parent data in the stream.
*/ */
val noParent : Codec[Pattern] = ( private val noParent : Codec[Pattern] = (
("objectClass" | uintL(0xb)) >>:~ { cls => //11u ("objectClass" | uintL(0xb)) :: //11u
("guid" | PlanetSideGUID.codec) :: //16u ("guid" | PlanetSideGUID.codec) //16u
("data" | ObjectClass.selectDataCodec(cls)) ).xmap[Pattern](
}
).xmap[Pattern] (
{ {
case cls :: guid :: data :: HNil => case cls :: guid :: HNil =>
cls :: guid :: None :: data :: HNil cls :: guid :: None :: HNil
}, }, {
{ case cls :: guid :: None :: HNil =>
case cls :: guid :: None :: data :: HNil => cls :: guid :: HNil
cls :: guid :: data :: HNil
} }
) )
/** /**
* Codec for reading and formatting parent data from the stream. * Codec for reading and formatting parent data from the stream.
*/ */
val parent : Codec[Pattern] = ( private val parent : Codec[Pattern] = (
("parentGuid" | PlanetSideGUID.codec) :: //16u ("parentGuid" | PlanetSideGUID.codec) :: //16u
(("objectClass" | uintL(0xb)) >>:~ { cls => //11u ("objectClass" | uintL(0xb)) :: //11u
("guid" | PlanetSideGUID.codec) :: //16u ("guid" | PlanetSideGUID.codec) :: //16u
("parentSlotIndex" | PacketHelpers.encodedStringSize) :: //8u or 16u ("parentSlotIndex" | PacketHelpers.encodedStringSize) //8u or 16u
("data" | ObjectClass.selectDataCodec(cls)) ).xmap[Pattern](
})
).xmap[Pattern] (
{ {
case pguid :: cls :: guid :: slot :: data :: HNil => case pguid :: cls :: guid :: slot :: HNil =>
cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: data :: HNil cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil
}, }, {
{ case cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil =>
case cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: data :: HNil => pguid :: cls :: guid :: slot :: HNil
pguid :: cls :: guid :: slot :: data :: 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> * <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: * That includes:
* the length of the stream length field (32u), * the length of the stream length field (32u),
* the object's class (11u), * 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. * and the bit to determine if there will be parent data.
* In total, these fields form a known fixed length of 60u. * In total, these fields form a known fixed length of 60u.
* @param parentInfo if defined, the parentInfo adds either 24u or 32u * @param parentInfo if defined, the parentInfo adds either 24u or 32u
* @param data the data length is indeterminate until it is read * @return the length, including the optional parent data
* @return the total length of the stream in bits
*/ */
private def streamLen(parentInfo : Option[ObjectCreateMessageParent], data : Option[ConstructorData]) : Long = { private def commonMsgLen(parentInfo : Option[ObjectCreateMessageParent]) : Long = {
//msg length if(parentInfo.isDefined) {
val first : Long = if(parentInfo.isDefined) { //(32u + 1u + 11u + 16u) ?+ (16u + (8u | 16u)) //(32u + 1u + 11u + 16u) ?+ (16u + (8u | 16u))
if(parentInfo.get.slot > 127) 92L else 84L if(parentInfo.get.slot > 127) 92L else 84L
} }
else { else {
60L 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] = ( implicit val codec : Codec[ObjectCreateMessage] = (
("streamLength" | uint32L) :: ("streamLength" | uint32L) ::
either(bool, parent, noParent).exmap[Pattern] ( (either(bool, parent, noParent).exmap[Pattern] (
{ {
case Left(a :: b :: Some(c) :: d :: HNil) => case Left(a :: b :: Some(c) :: HNil) =>
Attempt.successful(a :: b :: Some(c) :: d :: HNil) //true, _, _, Some(c) Attempt.successful(a :: b :: Some(c) :: HNil) //true, _, _, Some(c)
case Right(a :: b :: None :: d :: HNil) => case Right(a :: b :: None :: HNil) =>
Attempt.successful(a :: b :: None :: d :: HNil) //false, _, _, None Attempt.successful(a :: b :: None :: HNil) //false, _, _, None
// failure cases // failure cases
case Left(a :: b :: None :: _ :: HNil) => case Left(a :: b :: None :: HNil) =>
Attempt.failure(Err("missing parent structure")) //true, _, _, None 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) Attempt.failure(Err("unexpected parent structure")) //false, _, _, Some(c)
}, }, {
{ case a :: b :: Some(c) :: HNil =>
case a :: b :: Some(c) :: d :: HNil => Attempt.successful(Left(a :: b :: Some(c) :: HNil))
Attempt.successful(Left(a :: b :: Some(c) :: d :: HNil)) case a :: b :: None :: HNil =>
case a :: b :: None :: d :: HNil => Attempt.successful(Right(a :: b :: None :: HNil))
Attempt.successful(Right(a :: b :: None :: d :: HNil))
} }
) ) :+
).xmap[outPattern] ( ("data" | bits)) //greed is good
).xmap[outPattern](
{ {
case len :: cls :: guid :: par :: data :: HNil => 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 => case len :: cls :: guid :: par :: obj :: HNil =>
streamLen(par, data) :: cls :: guid :: par :: data :: 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] ).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 case class AmmoBoxData(magazine : Int
) extends ConstructorData { ) extends ConstructorData {
override def bsize : Long = 39L override def bitsize : Long = 39L
} }
object AmmoBoxData extends Marshallable[AmmoBoxData] { object AmmoBoxData extends Marshallable[AmmoBoxData] {

View file

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

View file

@ -19,9 +19,9 @@ case class InternalSlot(objectClass : Int,
guid : PlanetSideGUID, guid : PlanetSideGUID,
parentSlot : Int, parentSlot : Int,
obj : Option[ConstructorData]) { obj : Option[ConstructorData]) {
def bsize : Long = { def bitsize : Long = {
val first : Long = if(parentSlot > 127) 44L else 36L 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 first + second
} }
} }

View file

@ -3,14 +3,13 @@ package net.psforever.packet.game.objectcreate
import net.psforever.packet.{Marshallable, PacketHelpers} import net.psforever.packet.{Marshallable, PacketHelpers}
import scodec.Codec import scodec.Codec
import scodec.bits.BitVector
import scodec.codecs._ import scodec.codecs._
case class InventoryData(unk1 : Boolean, case class InventoryData(unk1 : Boolean,
size : Int, size : Int,
unk2 : Boolean){//, unk2 : Boolean){//,
//inv : List[InventoryItem]) { //inv : List[InventoryItem]) {
def bsize : Long = { def bitsize : Long = {
10L 10L
} }
} }
@ -18,7 +17,7 @@ case class InventoryData(unk1 : Boolean,
object InventoryData extends Marshallable[InventoryData] { object InventoryData extends Marshallable[InventoryData] {
implicit val codec : Codec[InventoryData] = ( implicit val codec : Codec[InventoryData] = (
("unk1" | bool) :: ("unk1" | bool) ::
(("size" | uint8L) >>:~ { len => (("len" | uint8L) >>:~ { len =>
("unk2" | bool).hlist// :: ("unk2" | bool).hlist// ::
//("inv" | PacketHelpers.listOfNSized(len, InventoryItem.codec)) //("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 // Copyright (c) 2016 PSForever.net to present
package net.psforever.packet.game.objectcreate package net.psforever.packet.game.objectcreate
import scodec.Codec import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import scala.annotation.switch import scala.annotation.switch
@ -36,18 +37,17 @@ object ObjectClass {
case ObjectClass.SUPPRESSOR => WeaponData.genericCodec case ObjectClass.SUPPRESSOR => WeaponData.genericCodec
case ObjectClass.REK => REKData.genericCodec case ObjectClass.REK => REKData.genericCodec
case ObjectClass.SLOT_BLOCKER => AmmoBoxData.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 case class REKData(unk : Int
) extends ConstructorData { ) extends ConstructorData {
override def bsize : Long = 72L override def bitsize : Long = 72L
} }
object REKData extends Marshallable[REKData] { 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 ...) case class RibbonBars(upper : Long = 0xFFFFFFFFL, //0xFFFFFFFF means no merit (for all ...)
middle : Long = 0xFFFFFFFFL, middle : Long = 0xFFFFFFFFL,
lower : Long = 0xFFFFFFFFL, lower : Long = 0xFFFFFFFFL,
tos : Long = 0xFFFFFFFFL) tos : Long = 0xFFFFFFFFL) {
def bitsize : Long = 128L
}
object RibbonBars extends Marshallable[RibbonBars] { object RibbonBars extends Marshallable[RibbonBars] {
implicit val codec : Codec[RibbonBars] = ( implicit val codec : Codec[RibbonBars] = (

View file

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

View file

@ -148,6 +148,8 @@ class GamePacketTest extends Specification {
"ObjectCreateMessage" should { "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 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" 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_9mm = hex"18 7C000000 2580 0E0 0005 A1 C8000064000"
val string_gauss = hex"18 DC000000 2580 2C9 B905 82 480000020000C04 1C00C0B0190000078000" 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 { "decode (char)" in {
PacketCoding.DecodePacket(string_testchar).require match { PacketCoding.DecodePacket(string_testchar).require match {
case obj @ ObjectCreateMessage(len, cls, guid, parent, data) => case obj @ ObjectCreateMessage(len, cls, guid, parent, data) =>
@ -236,15 +252,15 @@ class GamePacketTest extends Specification {
"decode (9mm)" in { "decode (9mm)" in {
PacketCoding.DecodePacket(string_9mm).require match { 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 len mustEqual 124
cls mustEqual 28 cls mustEqual 28
guid mustEqual PlanetSideGUID(1280) guid mustEqual PlanetSideGUID(1280)
parent.isDefined mustEqual true parent.isDefined mustEqual true
parent.get.guid mustEqual PlanetSideGUID(75) parent.get.guid mustEqual PlanetSideGUID(75)
parent.get.slot mustEqual 33 parent.get.slot mustEqual 33
mold.isDefined mustEqual true data.isDefined mustEqual true
val obj = mold.get.asInstanceOf[AmmoBoxData] val obj = data.get.asInstanceOf[AmmoBoxData]
obj.magazine mustEqual 50 obj.magazine mustEqual 50
case default => case default =>
ko ko
@ -253,15 +269,15 @@ class GamePacketTest extends Specification {
"decode (gauss)" in { "decode (gauss)" in {
PacketCoding.DecodePacket(string_gauss).require match { 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 len mustEqual 220
cls mustEqual 345 cls mustEqual 345
guid mustEqual PlanetSideGUID(1465) guid mustEqual PlanetSideGUID(1465)
parent.isDefined mustEqual true parent.isDefined mustEqual true
parent.get.guid mustEqual PlanetSideGUID(75) parent.get.guid mustEqual PlanetSideGUID(75)
parent.get.slot mustEqual 2 parent.get.slot mustEqual 2
mold.isDefined mustEqual true data.isDefined mustEqual true
val obj_wep = mold.get.asInstanceOf[WeaponData] val obj_wep = data.get.asInstanceOf[WeaponData]
obj_wep.unk mustEqual 4 obj_wep.unk mustEqual 4
val obj_ammo = obj_wep.ammo//.asInstanceOf[InternalSlot] val obj_ammo = obj_wep.ammo//.asInstanceOf[InternalSlot]
obj_ammo.objectClass mustEqual 28 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 { "encode (9mm)" in {
val obj : ConstructorData = AmmoBoxData(50).asInstanceOf[ConstructorData] val obj : ConstructorData = AmmoBoxData(50).asInstanceOf[ConstructorData]
val msg = ObjectCreateMessage(0, 28, PlanetSideGUID(1280), Some(ObjectCreateMessageParent(PlanetSideGUID(75), 33)), Some(obj)) val msg = ObjectCreateMessage(0, 28, PlanetSideGUID(1280), Some(ObjectCreateMessageParent(PlanetSideGUID(75), 33)), Some(obj))