diff --git a/common/src/main/scala/net/psforever/packet/PSPacket.scala b/common/src/main/scala/net/psforever/packet/PSPacket.scala index a5a5c229..2fe9a563 100644 --- a/common/src/main/scala/net/psforever/packet/PSPacket.scala +++ b/common/src/main/scala/net/psforever/packet/PSPacket.scala @@ -9,36 +9,45 @@ import scodec.codecs._ import scodec._ import shapeless._ -// Base packets +/** The base of all packets */ sealed trait PlanetSidePacket extends Serializable { def encode : Attempt[BitVector] def opcode : Enumeration#Value } -// Used by companion objects to create encoders and decoders +/** Used by companion objects to create encoders and decoders */ trait Marshallable[T] { implicit val codec : Codec[T] def encode(a : T) : Attempt[BitVector] = codec.encode(a) - // assert that when decoding a marshallable type, that no bits are left over def decode(a : BitVector) : Attempt[DecodeResult[T]] = codec.decode(a) } +/** PlanetSide game packets: net.psforever.packet.game._ */ trait PlanetSideGamePacket extends PlanetSidePacket { def opcode : GamePacketOpcode.Type } +/** PlanetSide control packets: net.psforever.packet.control._ */ trait PlanetSideControlPacket extends PlanetSidePacket { def opcode : ControlPacketOpcode.Type } +/** PlanetSide crypto packets: net.psforever.packet.crypto._ */ trait PlanetSideCryptoPacket extends PlanetSidePacket { def opcode : CryptoPacketOpcode.Type } -// Packet typing -final case class PlanetSidePacketFlags(packetType : PacketType.Value, secured : Boolean) -// Enumeration starts at 1 +/** PlanetSide packet type. Used in more complicated packet headers + * + * ResetSequence - Not sure what this is used for or if the name matches what it actually does + * but I saw some code that appeared to reset the packet sequence number when + * this was set + * Unknown2 - Your guess is as good as mine + * Crypto - Used for early crypto packets that are NOT encrypted + * Normal - Used for all non-crypto packets. May or may not be encrypted. + * + * Enumeration starts at 1. That's what I see in IDA */ object PacketType extends Enumeration(1) { type Type = Value val ResetSequence, Unknown2, Crypto, Normal = Value @@ -46,9 +55,13 @@ object PacketType extends Enumeration(1) { implicit val codec: Codec[this.Value] = PacketHelpers.createEnumerationCodec(this, uint4L) } +/** PlanetSide packet flags (beginning of most packets) */ +final case class PlanetSidePacketFlags(packetType : PacketType.Value, secured : Boolean) + +/** Codec for [[PlanetSidePacketFlags]] */ object PlanetSidePacketFlags extends Marshallable[PlanetSidePacketFlags] { implicit val codec : Codec[PlanetSidePacketFlags] = ( - ("packet_type" | PacketType.codec) :: + ("packet_type" | PacketType.codec) :: // first 4-bits ("unused" | constant(bin"0")) :: ("secured" | bool) :: ("advanced" | constant(bin"1")) :: // we only support "advanced packets" @@ -58,30 +71,23 @@ object PlanetSidePacketFlags extends Marshallable[PlanetSidePacketFlags] { ////////////////////////////////////////////////// -/*class MarshallableEnum[+T] extends Enumeration { - type StorageType = Codec[Int] - - implicit val storageType : StorageType = uint8 - - assert(maxId <= Math.pow(storageType.sizeBound.exact.get, 2), - this.getClass.getCanonicalName + ": maxId exceeds primitive type") - - implicit val codec: Codec[T] = PacketHelpers.createEnumerationCodec(this, storageType) -}*/ - object PacketHelpers { + /** Used in certain instances where Codec defintions are stubbed out */ def emptyCodec[T](instance : T) = { def to(pkt: T) = HNil def from(a: HNil) = instance Codec[HNil].xmap[T](from, to) } - // NOTE: enumerations in scala can't be represented by more than an Int anyways, so this conversion shouldnt matter. - // This is only to overload createEnumerationCodec to work with uint32[L] codecs (which are Long) - def createLongEnumerationCodec[E <: Enumeration](enum : E, storageCodec : Codec[Long]) : Codec[E#Value] = { - createEnumerationCodec(enum, storageCodec.xmap[Int](_.toInt, _.toLong)) - } + + /** Create a Codec for an enumeration type that can correctly represent its value + * + * @param enum the enumeration type to create a codec for + * @param storageCodec the Codec used for actually representing the value + * @tparam E The inferred type + * @return Generated codec + */ def createEnumerationCodec[E <: Enumeration](enum : E, storageCodec : Codec[Int]) : Codec[E#Value] = { type Struct = Int :: HNil val struct: Codec[Struct] = storageCodec.hlist @@ -110,9 +116,21 @@ object PacketHelpers { struct.narrow[E#Value](from, to) } - // when the first bit of the byte is set, the size can be between [0, 127]. - // otherwise, it is between [128, 32767] and two bytes are used for encoding - // The magic in this is next level + /** Same as [[createEnumerationCodec]] but with a Codec type of Long + * + * NOTE: enumerations in scala can't be represented by more than an Int anyways, so this conversion shouldnt matter. + * This is only to overload createEnumerationCodec to work with uint32[L] codecs (which are Long) + */ + def createLongEnumerationCodec[E <: Enumeration](enum : E, storageCodec : Codec[Long]) : Codec[E#Value] = { + createEnumerationCodec(enum, storageCodec.xmap[Int](_.toInt, _.toLong)) + } + + /** Common codec for how PlanetSide stores string sizes + * + * When the first bit of the byte is set, the size can be between [0, 127]. + * Otherwise, it is between [128, 32767] and two bytes are used for encoding. + * The magic in this is next level (read as: SCodec makes things hard to understand) + */ def encodedStringSize : Codec[Int] = either(bool, uint(15), uint(7)). xmap[Int]( (a : Either[Int, Int]) => a.fold[Int](a => a, a => a), @@ -121,6 +139,44 @@ object PacketHelpers { if(a > 0x7f) Left(a) else Right(a) ) + private def encodedStringSizeWithPad(pad : Int) : Codec[Int] = encodedStringSize <~ ignore(pad) + + /** Codec for how PlanetSide represents strings on the wire */ + def encodedString : Codec[String] = variableSizeBytes(encodedStringSize, ascii) + + + /** Same as [[encodedString]] but with a bit adjustment + * + * This comes in handy when a PlanetSide string is decoded on a non-byte boundary. The PlanetSide client + * will byte align after decoding the string lenght, but BEFORE the string itself. Scodec doesn't like this + * variability and there doesn't appear to be a way to fix this issue. + * + * @param adjustment The adjustment amount in bits + * @return Generated string decoding codec with adjustment + */ + def encodedStringAligned(adjustment : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithPad(adjustment), ascii) + + /** Variable for the charset that PlanetSide uses for unicode (2 byte unicode) */ + val utf16 = string(Charset.forName("UTF-16LE")) + + /** Common codec for PlanetSide wchar_t strings (wide strings, UTF-16) + * + * An encoded *wide* string is twice the length of the given encoded size and half of the length of the + * input string. We use xmap to transform the [[encodedString]] codec as this change is just a division and multiply + */ + def encodedWideString : Codec[String] = variableSizeBytes(encodedStringSize.xmap( + insize => insize*2, // number of symbols -> number of bytes (decode) + outSize => outSize/2 // number of bytes -> number of symbols (encode) + ), utf16) + + /** Same as [[encodedWideString]] but with a bit alignment after the decoded size + */ + def encodedWideStringAligned(adjustment : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithPad(adjustment).xmap( + insize => insize*2, + outSize => outSize/2 + ), utf16) + + // TODO: make the function below work as there are places it should be used /*private def encodedStringSizeWithLimit(limit : Int) : Codec[Int] = { either(bool, uint(15), uint(7)). exmap[Int]( @@ -143,24 +199,8 @@ object PacketHelpers { Right(a) } ) - }*/ + } - private def encodedStringSizeWithPad(pad : Int) : Codec[Int] = encodedStringSize <~ ignore(pad) - - def encodedString : Codec[String] = variableSizeBytes(encodedStringSize, ascii) - //def encodedStringWithLimit(limit : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithLimit(limit), ascii) - def encodedStringAligned(adjustment : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithPad(adjustment), ascii) - - /// Variable for the charset that planetside uses for unicode - val utf16 = string(Charset.forName("UTF-16LE")) - - /// An encoded *wide* string is twice the length of the given encoded size and half of the length of the - /// input string. We use xmap to transform the encodedString codec as this change is just a division and multiply - def encodedWideString : Codec[String] = variableSizeBytes(encodedStringSize.xmap( - insize => insize*2, - outSize => outSize/2), utf16) - - def encodedWideStringAligned(adjustment : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithPad(adjustment).xmap( - insize => insize*2, - outSize => outSize/2), utf16) + def encodedStringWithLimit(limit : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithLimit(limit), ascii) + */ }