From aac5e0a7cedc74cba670187b8259be1c72b6710f Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 23 May 2017 20:56:47 -0400 Subject: [PATCH 1/3] testing code; doesn't work, but I like where it's going --- .../src/main/scala/WorldSessionActor.scala | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 74b9616ef..b428a76fe 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -475,4 +475,23 @@ class WorldSessionActor extends Actor with MDCContextAware { log.trace("WORLD SEND RAW: " + pkt) sendResponse(RawPacket(pkt)) } + + def experimentalSlotted(bytes : ByteVector): Unit = { + if(bytes.size > 467L) { + val packet1 : MultiPacketEx = MultiPacketEx(Vector(bytes)) + val packet2 : ByteVector = bytes + val size : Long = packet2.size + var i : Long = 0 + var subslot = 257 + leftRef !> ResponsePacket(hex"00 15 01 00") + while(i < size) { + val packet3 = packet2.slice(i, i + 463L) + val packet4 = SlottedMetaPacket(0x4, subslot, packet3) + val packet5 : ByteVector = PacketCoding.EncodePacket(packet4).require.toByteVector + leftRef !> ResponsePacket(packet5) + i += 463L + subslot += 1 + } + } + } } From c117f051654e4bf8ef7b457228d2ab484be80d00 Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 24 May 2017 09:58:15 -0400 Subject: [PATCH 2/3] tidied up the first part of PacketCoding; made certain basic client-server communication still works --- .../net/psforever/packet/PacketCoding.scala | 393 ++++++++++-------- 1 file changed, 221 insertions(+), 172 deletions(-) diff --git a/common/src/main/scala/net/psforever/packet/PacketCoding.scala b/common/src/main/scala/net/psforever/packet/PacketCoding.scala index 3a12b5618..2074cbaa1 100644 --- a/common/src/main/scala/net/psforever/packet/PacketCoding.scala +++ b/common/src/main/scala/net/psforever/packet/PacketCoding.scala @@ -2,11 +2,10 @@ package net.psforever.packet import net.psforever.crypto.CryptoInterface -import net.psforever.crypto.CryptoInterface._ import scodec.Attempt.{Successful, Failure} import scodec.bits._ -import scodec.{DecodeResult, Err, Attempt, Codec} -import scodec.codecs.{uint16L, uint8L, uint4L, bytes} +import scodec.{Err, Attempt, Codec} +import scodec.codecs.{uint16L, uint8L, bytes} /// Packet container base trait sealed trait PlanetSidePacketContainer @@ -30,144 +29,110 @@ final case class ControlPacket(opcode : ControlPacketOpcode.Value, packet : PlanetSideControlPacket) extends PlanetSidePacketContainer object PacketCoding { - /// A lower bound on the packet size - final val PLANETSIDE_MIN_PACKET_SIZE = 1 + /** + * Access to the `ControlPacket` constructor. + * @param packet a `PlanetSideControlPacket` + * @return a `ControlPacket` wrapper + */ + def CreateControlPacket(packet : PlanetSideControlPacket) = ControlPacket(packet.opcode, packet) /** - * Given a full and complete planetside packet as it would be sent on the wire, attempt to - * decode it given an optional header and required payload. This function does all of the - * hard work of making decisions along the way in order to decode a planetside packet to - * completion. - * - * @param msg the raw packet - * @param cryptoState the current state of the connection's crypto. This is only used when decoding - * crypto packets as they do not have opcodes - * @return PlanetSidePacketContainer + * Access to the `CryptoPacket` constructor. + * @param sequence na + * @param packet a `PlanetSideCryptoPacket` + * @return a `CryptoPacket` wrapper */ - def UnmarshalPacket(msg : ByteVector, cryptoState : CryptoPacketOpcode.Type) : Attempt[PlanetSidePacketContainer] = { - // check for a minimum length - if(msg.length < PLANETSIDE_MIN_PACKET_SIZE) - return Attempt.failure(Err(s"Packet does not meet the minimum length of $PLANETSIDE_MIN_PACKET_SIZE bytes")) - - val firstByte = msg{0} - - firstByte match { - // drop the first byte as control packets dont need it - case 0x00 => unmarshalControlPacket(msg.drop(1)) - case _ => unmarshalFlaggedPacket(msg, cryptoState) // returns either EncryptedPacket or CryptoPacket - } - } + def CreateCryptoPacket(sequence : Int, packet : PlanetSideCryptoPacket) = CryptoPacket(sequence, packet) /** - * Helper function to decode a packet without specifying a crypto packet state. - * Mostly used when there is no crypto state available, such as tests. - * - * @param msg packet data bytes - * @return PlanetSidePacketContainer + * Access to the `GamePacket` constructor. + * @param sequence na + * @param packet a `PlanetSideGamePacket` + * @return a `GamePacket` wrapper */ - def UnmarshalPacket(msg : ByteVector) : Attempt[PlanetSidePacketContainer] = { - UnmarshalPacket(msg, CryptoPacketOpcode.Ignore) - } + def CreateGamePacket(sequence : Int, packet : PlanetSideGamePacket) = GamePacket(packet.opcode, sequence, packet) + +/* Marshalling and Encoding. */ /** - * Similar to UnmarshalPacket, but does not process any packet header and does not support - * decoding of crypto packets. Mostly used in tests. - * - * @param msg raw, unencrypted packet - * @return PlanetSidePacket + * Transforms a packet into `BitVector` representations of its component data and then re-assemble it. + * Wraps around the encoding process for all valid packet container types. + * @param packet the packet to encode + * @return a `BitVector` translated from the packet's data */ - def DecodePacket(msg : ByteVector) : Attempt[PlanetSidePacket] = { - // check for a minimum length - if(msg.length < PLANETSIDE_MIN_PACKET_SIZE) - return Attempt.failure(Err(s"Packet does not meet the minimum length of $PLANETSIDE_MIN_PACKET_SIZE bytes")) - - val firstByte = msg{0} - - firstByte match { - // drop the first byte as control packets dont need it - case 0x00 => DecodeControlPacket(msg.drop(1)) - case _ => DecodeGamePacket(msg) - } - } - def MarshalPacket(packet : PlanetSidePacketContainer) : Attempt[BitVector] = { - var flagsEncoded : BitVector = BitVector.empty - var seqEncoded : BitVector = BitVector.empty - var paddingEncoded : BitVector = BitVector.empty - var opcodeEncoded : BitVector = BitVector.empty - var payloadEncoded : BitVector = BitVector.empty - + var flagsEncoded : BitVector = BitVector.empty //flags before everything in packet + var seqEncoded : BitVector = BitVector.empty //control packets have a sequence number + var paddingEncoded : BitVector = BitVector.empty //encrypted packets need to be aligned in a certain way + var payloadEncoded : BitVector = BitVector.empty //the packet itself as bits and bytes var controlPacket = false var sequenceNum = 0 - - // packet flags - var hasFlags = true + //packet flags var secured = false var packetType = PacketType.Crypto packet match { - case GamePacket(opcode, seq, payload) => - secured = false + case GamePacket(_, seq, payload) => packetType = PacketType.Normal sequenceNum = seq - EncodePacket(payload) match { - case f @ Failure(e) => return f + case f @ Failure(_) => return f case Successful(p) => payloadEncoded = p } - case ControlPacket(opcode, payload) => + + case ControlPacket(_, payload) => controlPacket = true - EncodePacket(payload) match { - case f @ Failure(e) => return f + case f @ Failure(_) => return f case Successful(p) => payloadEncoded = p } + case CryptoPacket(seq, payload) => - secured = false packetType = PacketType.Crypto sequenceNum = seq - EncodePacket(payload) match { - case f @ Failure(e) => return f + case f @ Failure(_) => return f case Successful(p) => payloadEncoded = p } + case EncryptedPacket(seq, payload) => secured = true packetType = PacketType.Normal sequenceNum = seq - - // encrypted packets need to be aligned to 4 bytes before encryption/decryption - // first byte are flags, second and third the sequence, and fourth is the pad + //encrypted packets need to be aligned to 4 bytes before encryption/decryption + //first byte are flags, second is the sequence, and third is the pad paddingEncoded = hex"00".bits payloadEncoded = payload.bits } - val flags = PlanetSidePacketFlags(packetType, secured = secured) - - // crypto packets DONT have flags + //crypto packets DON'T have flags if(!controlPacket) { + val flags = PlanetSidePacketFlags(packetType, secured = secured) flagsEncoded = PlanetSidePacketFlags.codec.encode(flags).require - uint16L.encode(sequenceNum) match { case Failure(e) => return Attempt.failure(Err(s"Failed to marshal sequence in packet $packet: " + e.messageWithContext)) case Successful(p) => seqEncoded = p } } - val finalPacket = flagsEncoded ++ seqEncoded ++ paddingEncoded ++ opcodeEncoded ++ payloadEncoded - Attempt.successful(finalPacket) + Attempt.successful(flagsEncoded ++ seqEncoded ++ paddingEncoded ++ payloadEncoded) } + /** + * Overloaded method for transforming a control packet into its `BitVector` representation. + * @param packet the control packet to encode + * @return a `BitVector` translated from the packet's data + */ def EncodePacket(packet : PlanetSideControlPacket) : Attempt[BitVector] = { val opcode = packet.opcode - var opcodeEncoded = BitVector.empty - var payloadEncoded = BitVector.empty + var opcodeEncoded = BitVector.empty ControlPacketOpcode.codec.encode(opcode) match { case Failure(e) => return Attempt.failure(Err(s"Failed to marshal opcode in control packet $opcode: " + e.messageWithContext)) case Successful(p) => opcodeEncoded = p } + var payloadEncoded = BitVector.empty encodePacket(packet) match { case Failure(e) => return Attempt.failure(Err(s"Failed to marshal control packet $packet: " + e.messageWithContext)) case Successful(p) => payloadEncoded = p @@ -176,23 +141,33 @@ object PacketCoding { Attempt.Successful(hex"00".bits ++ opcodeEncoded ++ payloadEncoded) } + /** + * Overloaded method for transforming a crypto packet into its `BitVector` representation. + * @param packet the crypto packet to encode + * @return a `BitVector` translated from the packet's data + */ def EncodePacket(packet : PlanetSideCryptoPacket) : Attempt[BitVector] = { encodePacket(packet) match { case Failure(e) => Attempt.failure(Err(s"Failed to marshal crypto packet $packet: " + e.messageWithContext)) - case s @ Successful(p) => s + case s @ Successful(_) => s } } + /** + * Overloaded method for transforming a game packet into its `BitVector` representation. + * @param packet the game packet to encode + * @return a `BitVector` translated from the packet's data + */ def EncodePacket(packet : PlanetSideGamePacket) : Attempt[BitVector] = { val opcode = packet.opcode - var opcodeEncoded = BitVector.empty - var payloadEncoded = BitVector.empty + var opcodeEncoded = BitVector.empty GamePacketOpcode.codec.encode(opcode) match { case Failure(e) => return Attempt.failure(Err(s"Failed to marshal opcode in game packet $opcode: " + e.messageWithContext)) case Successful(p) => opcodeEncoded = p } + var payloadEncoded = BitVector.empty encodePacket(packet) match { case Failure(e) => return Attempt.failure(Err(s"Failed to marshal game packet $packet: " + e.messageWithContext)) case Successful(p) => payloadEncoded = p @@ -201,17 +176,62 @@ object PacketCoding { Attempt.Successful(opcodeEncoded ++ payloadEncoded) } - def CreateControlPacket(packet : PlanetSideControlPacket) = ControlPacket(packet.opcode, packet) - def CreateCryptoPacket(sequence : Int, packet : PlanetSideCryptoPacket) = CryptoPacket(sequence, packet) - def CreateGamePacket(sequence : Int, packet : PlanetSideGamePacket) = GamePacket(packet.opcode, sequence, packet) - - ////////////////////////////////////////////////////////////////////////////// - + /** + * Calls the packet-specific encode function. + * Lowest encode call. + * @param packet the packet to encode + * @return a `BitVector` translated from the packet's data + */ private def encodePacket(packet : PlanetSidePacket) : Attempt[BitVector] = packet.encode - private def unmarshalFlaggedPacket(msg : ByteVector, cryptoState : CryptoPacketOpcode.Type) : Attempt[PlanetSidePacketContainer] = { - val decodedFlags = Codec.decode[PlanetSidePacketFlags](BitVector(msg)) +/* Unmarshalling and Decoding. */ + /** + * A lower bound on the packet size + */ + final val PLANETSIDE_MIN_PACKET_SIZE = 1 + + /** + * Transforms `BitVector` data into a PlanetSide packet.
+ *
+ * Given a full and complete planetside packet as it would be sent on the wire, attempt to + * decode it given an optional header and required payload. This function does all of the + * hard work of making decisions along the way in order to decode a planetside packet to + * completion. + * @param msg the raw packet + * @param cryptoState the current state of the connection's crypto. This is only used when decoding + * crypto packets as they do not have opcodes + * @return `PlanetSidePacketContainer` + */ + def UnmarshalPacket(msg : ByteVector, cryptoState : CryptoPacketOpcode.Type) : Attempt[PlanetSidePacketContainer] = { + if(msg.length < PLANETSIDE_MIN_PACKET_SIZE) + return Attempt.failure(Err(s"Packet does not meet the minimum length of $PLANETSIDE_MIN_PACKET_SIZE bytes")) + + val firstByte = msg{0} + firstByte match { + case 0x00 => unmarshalControlPacket(msg.drop(1)) //control packets dont need the first byte + case _ => unmarshalFlaggedPacket(msg, cryptoState) //either EncryptedPacket or CryptoPacket + } + } + + /** + * Helper function to decode a packet without specifying a crypto packet state. + * Mostly used when there is no crypto state available, such as tests. + * @param msg packet data bytes + * @return `PlanetSidePacketContainer` + */ + def UnmarshalPacket(msg : ByteVector) : Attempt[PlanetSidePacketContainer] = UnmarshalPacket(msg, CryptoPacketOpcode.Ignore) + + /** + * Handle decoding for a packet that is not a control packet. + * It may just be encrypted or it may be involved in the encryption process itself. + * @param msg the packet + * @param cryptoState the current state of the connection's crypto + * @return a `PlanetSidePacketContainer` + */ + private def unmarshalFlaggedPacket(msg : ByteVector, cryptoState : CryptoPacketOpcode.Type) : Attempt[PlanetSidePacketContainer] = { + //get the flags + val decodedFlags = Codec.decode[PlanetSidePacketFlags](BitVector(msg)) decodedFlags match { case Failure(e) => return Attempt.failure(Err("Failed to parse packet flags: " + e.message)) @@ -219,112 +239,76 @@ object PacketCoding { } val flags = decodedFlags.require.value - val rest = decodedFlags.require.remainder val packetType = flags.packetType - - // perform a quick reject of weird packet types packetType match { - case PacketType.Crypto => ; - case PacketType.Normal => ; - case default => + case PacketType.Normal => + if(!flags.secured) { //support normal packets only if they are encrypted + return Attempt.failure(Err("Unsupported packet type: normal packets must be encryped")) + } + + case PacketType.Crypto => + if(flags.secured) { //support crypto packets only if they are not encrypted + return Attempt.failure(Err("Unsupported packet type: crypto packets must be unencrypted")) + } + + case _ => return Attempt.failure(Err("Unsupported packet type: " + flags.packetType.toString)) } - // we only support normal packets if they are encrypted - if(packetType == PacketType.Normal && !flags.secured) - return Attempt.failure(Err("Unsupported packet type: normal packets must be encryped")) - - // we only support crypto packets if they are not encrypted - if(packetType == PacketType.Crypto && flags.secured) - return Attempt.failure(Err("Unsupported packet type: crypto packets must be unencrypted")) - - // all packets have a two byte sequence ID - // TODO: make this a codec for reuse - val decodedSeq = uint16L.decode(rest) - + //all packets have a two byte sequence ID + val decodedSeq = uint16L.decode(decodedFlags.require.remainder) //TODO: make this a codec for reuse decodedSeq match { case Failure(e) => return Attempt.failure(Err("Failed to parse packet sequence number: " + e.message)) case _ => } - val sequence = decodedSeq.require.value - var payload = decodedSeq.require.remainder.toByteVector - - // encrypted packets must be 4-byte aligned - if(flags.secured) { - payload = payload.drop(1) - } + val payload = decodedSeq.require.remainder.toByteVector packetType match { case PacketType.Crypto => unmarshalCryptoPacket(cryptoState, sequence, payload) case PacketType.Normal => - unmarshalEncryptedPacket(sequence, payload) + unmarshalEncryptedPacket(sequence, payload.drop(1)) //payload is 4-byte aligned } } + /** + * Handle decoding for a control packet. + * @param msg the packet + * @return a `ControlPacket` + */ private def unmarshalControlPacket(msg : ByteVector) : Attempt[ControlPacket] = { - val packet = DecodeControlPacket(msg) - - packet match { - // just return the failure - case f @ Failure(e) => f + DecodeControlPacket(msg) match { + case f @ Failure(_) => f case Successful(p) => Attempt.successful(CreateControlPacket(p)) } } - def DecodeControlPacket(msg : ByteVector) : Attempt[PlanetSideControlPacket] = { - val opcode = ControlPacketOpcode.codec.decode(msg.bits) - - opcode match { - case Failure(e) => - Attempt.failure(Err("Failed to decode control packet's opcode: " + e.message)) - case Successful(op) => - val packet = ControlPacketOpcode.getPacketDecoder(op.value)(op.remainder) - - packet match { - case Failure(e) => - Attempt.failure(Err(f"Failed to parse control packet ${op.value}: " + e.messageWithContext)) - case Successful(p) => - Attempt.successful(p.value) - } - } - } - + /** + * Handle decoding for a game packet. + * @param sequence na + * @param msg the packet data + * @return a `GamePacket` + */ private def unmarshalGamePacket(sequence : Int, msg : ByteVector) : Attempt[GamePacket] = { - val packet = DecodeGamePacket(msg) - - packet match { - case f @ Failure(e) => f + DecodeGamePacket(msg) match { + case f @ Failure(_) => f case Successful(p) => Attempt.successful(CreateGamePacket(sequence, p)) } } - def DecodeGamePacket(msg : ByteVector) : Attempt[PlanetSideGamePacket] = { - val opcode = GamePacketOpcode.codec.decode(msg.bits) - - opcode match { - case Failure(e) => - return Attempt.failure(Err("Failed to decode game packet's opcode: " + e.message)) - case _ => - } - - val packet = GamePacketOpcode.getPacketDecoder(opcode.require.value)(opcode.require.remainder) - - packet match { - case Failure(e) => - Attempt.failure(Err(f"Failed to parse game packet 0x${opcode.require.value.id}%02x: " + e.messageWithContext)) - case Successful(p) => Attempt.successful(p.value) - } - } - + /** + * Handle decoding for a crypto packet. + * @param state the current state of the connection's crypto + * @param sequence na + * @param payload the packet data + * @return a `CryptoPacket` + */ private def unmarshalCryptoPacket(state : CryptoPacketOpcode.Type, sequence : Int, payload : ByteVector) : Attempt[CryptoPacket] = { - val packet = CryptoPacketOpcode.getPacketDecoder(state)(payload.bits) - - packet match { + CryptoPacketOpcode.getPacketDecoder(state)(payload.bits) match { case Successful(a) => Attempt.successful(CryptoPacket(sequence, a.value)) case Failure(e) => @@ -332,13 +316,78 @@ object PacketCoding { } } + /** + * Handle decoding for an encrypted packet. + * That is, it's already encrypted. + * Just repackage the data. + * @param sequence na + * @param payload the packet data + * @return an `EncryptedPacket` + */ private def unmarshalEncryptedPacket(sequence : Int, payload : ByteVector) : Attempt[EncryptedPacket] = { Attempt.successful(EncryptedPacket(sequence, payload)) } - /////////////////////////////////////////////////////////// - // Packet Crypto - /////////////////////////////////////////////////////////// + /** + * Similar to `UnmarshalPacket`, but does not process any packet header and does not support decoding of crypto packets. + * Mostly used in tests. + * @param msg raw, unencrypted packet + * @return `PlanetSidePacket` + */ + def DecodePacket(msg : ByteVector) : Attempt[PlanetSidePacket] = { + if(msg.length < PLANETSIDE_MIN_PACKET_SIZE) + return Attempt.failure(Err(s"Packet does not meet the minimum length of $PLANETSIDE_MIN_PACKET_SIZE bytes")) + + val firstByte = msg{0} + firstByte match { + case 0x00 => DecodeControlPacket(msg.drop(1)) //control packets dont need the first byte + case _ => DecodeGamePacket(msg) + } + } + + /** + * Transform a `BitVector` into a control packet. + * @param msg the the raw data to decode + * @return a `PlanetSideControlPacket` + */ + def DecodeControlPacket(msg : ByteVector) : Attempt[PlanetSideControlPacket] = { + ControlPacketOpcode.codec.decode(msg.bits) match { + case Failure(e) => + Attempt.failure(Err("Failed to decode control packet's opcode: " + e.message)) + + case Successful(op) => + ControlPacketOpcode.getPacketDecoder(op.value)(op.remainder) match { + case Failure(e) => + Attempt.failure(Err(f"Failed to parse control packet ${op.value}: " + e.messageWithContext)) + + case Successful(p) => + Attempt.successful(p.value) + } + } + } + + /** + * Transform a `BitVector` into a game packet. + * @param msg the the raw data to decode + * @return a `PlanetSideGamePacket` + */ + def DecodeGamePacket(msg : ByteVector) : Attempt[PlanetSideGamePacket] = { + GamePacketOpcode.codec.decode(msg.bits) match { + case Failure(e) => + Attempt.failure(Err("Failed to decode game packet's opcode: " + e.message)) + + case Successful(opcode) => + GamePacketOpcode.getPacketDecoder(opcode.value)(opcode.remainder) match { + case Failure(e) => + Attempt.failure(Err(f"Failed to parse game packet 0x${opcode.value.id}%02x: " + e.messageWithContext)) + + case Successful(p) => + Attempt.successful(p.value) + } + } + } + +/* Encrypting and Decrypting. */ def encryptPacket(crypto : CryptoInterface.CryptoStateWithMAC, packet : PlanetSidePacketContainer) : Attempt[EncryptedPacket] = { // TODO XXX: this is bad. rework From ec51823bb1ce4a657d695f69891a492a0c627cd8 Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 24 May 2017 21:50:26 -0400 Subject: [PATCH 3/3] finished work in PacketCoding --- .../net/psforever/packet/PacketCoding.scala | 187 ++++++++++-------- .../src/main/scala/WorldSessionActor.scala | 19 -- 2 files changed, 109 insertions(+), 97 deletions(-) diff --git a/common/src/main/scala/net/psforever/packet/PacketCoding.scala b/common/src/main/scala/net/psforever/packet/PacketCoding.scala index 2074cbaa1..f3500b3e3 100644 --- a/common/src/main/scala/net/psforever/packet/PacketCoding.scala +++ b/common/src/main/scala/net/psforever/packet/PacketCoding.scala @@ -7,24 +7,50 @@ import scodec.bits._ import scodec.{Err, Attempt, Codec} import scodec.codecs.{uint16L, uint8L, bytes} -/// Packet container base trait +/** + * Base trait of the packet container `case class`es. + */ sealed trait PlanetSidePacketContainer -/// A sequence, encrypted opcode, encrypted payload, and implicit MD5MAC plus padding +/** + * An encrypted packet contains the following: + * a sequence; + * an encrypted opcode; + * an encrypted payload; + * and an implicit MD5MAC plus padding. + * @param sequenceNumber na + * @param payload the packet data + */ final case class EncryptedPacket(sequenceNumber : Int, payload : ByteVector) extends PlanetSidePacketContainer -/// A sequence, and payload. Crypto packets have no discernible opcodes an rely off of implicit -/// state to decode properly +/** + * A crypto packet contains the following: + * a sequence; + * and, a payload. + * These packets have no opcodes and they rely on implicit state to decode properly. + * @param sequenceNumber na + * @param packet the packet data + */ final case class CryptoPacket(sequenceNumber : Int, packet : PlanetSideCryptoPacket) extends PlanetSidePacketContainer -/// A sequenced game packet with an opcode and payload +/** + * A game packet is prefaced by a byte that determines the type of packet and how to interpret the data. + * This is important for decoding and encoding. + * @param opcode a byte that identifies the packet + * @param sequenceNumber na + * @param packet the packet data + */ final case class GamePacket(opcode : GamePacketOpcode.Value, sequenceNumber : Int, packet : PlanetSideGamePacket) extends PlanetSidePacketContainer -/// Just an opcode + payload +/** + * A control packet is prefaced with a zero'd byte (`00`) followed by a special byte opcode for the type of control packet. + * @param opcode a byte that identifies the packet + * @param packet the packet data + */ final case class ControlPacket(opcode : ControlPacketOpcode.Value, packet : PlanetSideControlPacket) extends PlanetSidePacketContainer @@ -32,7 +58,7 @@ object PacketCoding { /** * Access to the `ControlPacket` constructor. * @param packet a `PlanetSideControlPacket` - * @return a `ControlPacket` wrapper + * @return a `ControlPacket` */ def CreateControlPacket(packet : PlanetSideControlPacket) = ControlPacket(packet.opcode, packet) @@ -40,7 +66,7 @@ object PacketCoding { * Access to the `CryptoPacket` constructor. * @param sequence na * @param packet a `PlanetSideCryptoPacket` - * @return a `CryptoPacket` wrapper + * @return a `CryptoPacket` */ def CreateCryptoPacket(sequence : Int, packet : PlanetSideCryptoPacket) = CryptoPacket(sequence, packet) @@ -48,14 +74,14 @@ object PacketCoding { * Access to the `GamePacket` constructor. * @param sequence na * @param packet a `PlanetSideGamePacket` - * @return a `GamePacket` wrapper + * @return a `GamePacket` */ def CreateGamePacket(sequence : Int, packet : PlanetSideGamePacket) = GamePacket(packet.opcode, sequence, packet) /* Marshalling and Encoding. */ /** - * Transforms a packet into `BitVector` representations of its component data and then re-assemble it. + * Transforms a type of packet into the `BitVector` representations of its component data and then reconstructs those components. * Wraps around the encoding process for all valid packet container types. * @param packet the packet to encode * @return a `BitVector` translated from the packet's data @@ -125,7 +151,6 @@ object PacketCoding { */ def EncodePacket(packet : PlanetSideControlPacket) : Attempt[BitVector] = { val opcode = packet.opcode - var opcodeEncoded = BitVector.empty ControlPacketOpcode.codec.encode(opcode) match { case Failure(e) => return Attempt.failure(Err(s"Failed to marshal opcode in control packet $opcode: " + e.messageWithContext)) @@ -137,7 +162,6 @@ object PacketCoding { case Failure(e) => return Attempt.failure(Err(s"Failed to marshal control packet $packet: " + e.messageWithContext)) case Successful(p) => payloadEncoded = p } - Attempt.Successful(hex"00".bits ++ opcodeEncoded ++ payloadEncoded) } @@ -160,7 +184,6 @@ object PacketCoding { */ def EncodePacket(packet : PlanetSideGamePacket) : Attempt[BitVector] = { val opcode = packet.opcode - var opcodeEncoded = BitVector.empty GamePacketOpcode.codec.encode(opcode) match { case Failure(e) => return Attempt.failure(Err(s"Failed to marshal opcode in game packet $opcode: " + e.messageWithContext)) @@ -172,13 +195,12 @@ object PacketCoding { case Failure(e) => return Attempt.failure(Err(s"Failed to marshal game packet $packet: " + e.messageWithContext)) case Successful(p) => payloadEncoded = p } - Attempt.Successful(opcodeEncoded ++ payloadEncoded) } /** * Calls the packet-specific encode function. - * Lowest encode call. + * Lowest encode call before the packet-specific implementations. * @param packet the packet to encode * @return a `BitVector` translated from the packet's data */ @@ -223,15 +245,14 @@ object PacketCoding { def UnmarshalPacket(msg : ByteVector) : Attempt[PlanetSidePacketContainer] = UnmarshalPacket(msg, CryptoPacketOpcode.Ignore) /** - * Handle decoding for a packet that is not a control packet. + * Handle decoding for a packet that has been identified as not a control packet. * It may just be encrypted or it may be involved in the encryption process itself. * @param msg the packet * @param cryptoState the current state of the connection's crypto * @return a `PlanetSidePacketContainer` */ private def unmarshalFlaggedPacket(msg : ByteVector, cryptoState : CryptoPacketOpcode.Type) : Attempt[PlanetSidePacketContainer] = { - //get the flags - val decodedFlags = Codec.decode[PlanetSidePacketFlags](BitVector(msg)) + val decodedFlags = Codec.decode[PlanetSidePacketFlags](BitVector(msg)) //get the flags decodedFlags match { case Failure(e) => return Attempt.failure(Err("Failed to parse packet flags: " + e.message)) @@ -389,69 +410,87 @@ object PacketCoding { /* Encrypting and Decrypting. */ + /** + * Encrypt the provided packet using the provided crypto state. + * @param crypto the current state of the connection's crypto + * @param packet the unencrypted packet + * @return an `EncryptedPacket` + */ def encryptPacket(crypto : CryptoInterface.CryptoStateWithMAC, packet : PlanetSidePacketContainer) : Attempt[EncryptedPacket] = { - // TODO XXX: this is bad. rework - var sequenceNumber = 0 - - val rawPacket : BitVector = packet match { - case GamePacket(opcode, seq, payload) => - val opcodeEncoded = GamePacketOpcode.codec.encode(opcode) - sequenceNumber = seq - - opcodeEncoded match { - case Failure(e) => return Attempt.failure(Err(s"Failed to marshal opcode in packet $opcode: " + e.message)) - case _ => + makeRawPacket(packet) match { + case Successful(rawPacket) => + var sequenceNumber = 0 + packet match { //the sequence is a not default if this is a GamePacket + case GamePacket(_, seq, _) => sequenceNumber = seq + case _ => ; } + encryptPacket(crypto, sequenceNumber, rawPacket.toByteVector) - encodePacket(payload) match { - case Failure(e) => return Attempt.failure(Err(s"Failed to marshal packet $opcode: " + e.messageWithContext)) - case Successful(p) => opcodeEncoded.require ++ p - } - case ControlPacket(opcode, payload) => - val opcodeEncoded = ControlPacketOpcode.codec.encode(opcode) - - opcodeEncoded match { - case Failure(e) => return Attempt.failure(Err(s"Failed to marshal opcode in packet $opcode: " + e.messageWithContext)) - case _ => - } - - encodePacket(payload) match { - case Failure(e) => return Attempt.failure(Err(s"Failed to marshal packet $opcode: " + e.messageWithContext)) - case Successful(p) => hex"00".bits ++ opcodeEncoded.require ++ p - } - case default => throw new IllegalArgumentException("Unsupported packet container type") + case f @ Failure(_) => f; } - - encryptPacket(crypto, sequenceNumber, rawPacket.toByteVector) } + /** + * Transform either a game packet or a control packet into a `BitVector`. + * This is more thorough than the process of unmarshalling, though the results are very similar. + * @param packet a packet + * @return a `BitVector` that represents the packet + */ + def makeRawPacket(packet : PlanetSidePacketContainer) : Attempt[BitVector] = packet match { + case GamePacket(opcode, _, payload) => + val opcodeEncoded = GamePacketOpcode.codec.encode(opcode) + opcodeEncoded match { + case Failure(e) => Attempt.failure(Err(s"Failed to marshal opcode in packet $opcode: " + e.message)) + case _ => + encodePacket(payload) match { + case Failure(e) => Attempt.failure(Err(s"Failed to marshal packet $opcode: " + e.messageWithContext)) + case Successful(p) => Attempt.successful(opcodeEncoded.require ++ p) + } + } + + case ControlPacket(opcode, payload) => + val opcodeEncoded = ControlPacketOpcode.codec.encode(opcode) + opcodeEncoded match { + case Failure(e) => Attempt.failure(Err(s"Failed to marshal opcode in packet $opcode: " + e.messageWithContext)) + case _ => + encodePacket(payload) match { + case Failure(e) => Attempt.failure(Err(s"Failed to marshal packet $opcode: " + e.messageWithContext)) + case Successful(p) => Attempt.successful(hex"00".bits ++ opcodeEncoded.require ++ p) + } + } + + case _ => + throw new IllegalArgumentException("Unsupported packet container type") + } + + /** + * Perform encryption on the packet's raw data. + * @param crypto the current state of the connection's crypto + * @param sequenceNumber na + * @param rawPacket a `ByteVector` that represents the packet data + * @return + */ def encryptPacket(crypto : CryptoInterface.CryptoStateWithMAC, sequenceNumber : Int, rawPacket : ByteVector) : Attempt[EncryptedPacket] = { val packetMac = crypto.macForEncrypt(rawPacket) - - // opcode, payload, and MAC - val packetNoPadding = rawPacket ++ packetMac - + val packetNoPadding = rawPacket ++ packetMac //opcode, payload, and MAC val remainder = packetNoPadding.length % CryptoInterface.RC5_BLOCK_SIZE - - // minus 1 because of the actual byte telling of the padding, which always has to be there - val paddingNeeded = CryptoInterface.RC5_BLOCK_SIZE - remainder - 1 + val paddingNeeded = CryptoInterface.RC5_BLOCK_SIZE - remainder - 1 //minus 1 because of a mandatory padding bit val paddingEncoded = uint8L.encode(paddingNeeded.toInt).require - val packetWithPadding = packetNoPadding ++ ByteVector.fill(paddingNeeded)(0x00) ++ paddingEncoded.toByteVector - - // raw packets plus MAC must be padded to the nearest 16 byte boundary - val encryptedPayload = crypto.encrypt(packetWithPadding) - + val encryptedPayload = crypto.encrypt(packetWithPadding) //raw packets plus MAC, padded to the nearest 16 byte boundary Attempt.successful(EncryptedPacket(sequenceNumber, encryptedPayload)) } + /** + * Perform decryption on a packet's data. + * @param crypto the current state of the connection's crypto + * @param packet an encrypted packet + * @return + */ def decryptPacket(crypto : CryptoInterface.CryptoStateWithMAC, packet : EncryptedPacket) : Attempt[PlanetSidePacketContainer] = { val payloadDecrypted = crypto.decrypt(packet.payload) - - // get the last byte which is the padding length - val payloadJustLen = payloadDecrypted.takeRight(1) + val payloadJustLen = payloadDecrypted.takeRight(1) //get the last byte which is the padding length val padding = uint8L.decode(payloadJustLen.bits) - padding match { case Failure(e) => return Attempt.failure(Err("Failed to decode the encrypted padding length: " + e.message)) case _ => @@ -461,8 +500,6 @@ object PacketCoding { val macDecoder = bytes(macSize) val payloadNoPadding = payloadDecrypted.dropRight(1 + padding.require.value) val payloadMac = payloadNoPadding.takeRight(macSize) - val payloadNoMac = payloadNoPadding.dropRight(macSize) - /* println("Payload: " + packet.payload) println("DecPayload: " + payloadDecrypted) @@ -470,29 +507,23 @@ object PacketCoding { println("Padding: " + padding.require.value) println("NoPadding: " + payloadNoPadding) println("Mac: " + payloadMac) - println("NoMac: " + payloadNoMac)*/ - - + println("NoMac: " + payloadNoMac) + */ val mac = macDecoder.decode(payloadMac.bits) - mac match { case Failure(e) => return Attempt.failure(Err("Failed to extract the encrypted MAC: " + e.message)) case _ => } + val payloadNoMac = payloadNoPadding.dropRight(macSize) val computedMac = crypto.macForDecrypt(payloadNoMac) - - // verify that the MAC matches - if(!CryptoInterface.verifyMAC(computedMac, mac.require.value)) + if(!CryptoInterface.verifyMAC(computedMac, mac.require.value)) { //verify that the MAC matches throw new SecurityException("Invalid packet MAC") - + } if(payloadNoMac.length < PLANETSIDE_MIN_PACKET_SIZE) { return Attempt.failure(Err(s"Decrypted packet does not meet the minimum length of $PLANETSIDE_MIN_PACKET_SIZE bytes")) } - - val firstByte = payloadNoMac{0} - - firstByte match { + payloadNoMac{0} match { case 0x00 => unmarshalControlPacket(payloadNoMac.drop(1)) case _ => unmarshalGamePacket(packet.sequenceNumber, payloadNoMac) } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index b428a76fe..74b9616ef 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -475,23 +475,4 @@ class WorldSessionActor extends Actor with MDCContextAware { log.trace("WORLD SEND RAW: " + pkt) sendResponse(RawPacket(pkt)) } - - def experimentalSlotted(bytes : ByteVector): Unit = { - if(bytes.size > 467L) { - val packet1 : MultiPacketEx = MultiPacketEx(Vector(bytes)) - val packet2 : ByteVector = bytes - val size : Long = packet2.size - var i : Long = 0 - var subslot = 257 - leftRef !> ResponsePacket(hex"00 15 01 00") - while(i < size) { - val packet3 = packet2.slice(i, i + 463L) - val packet4 = SlottedMetaPacket(0x4, subslot, packet3) - val packet5 : ByteVector = PacketCoding.EncodePacket(packet4).require.toByteVector - leftRef !> ResponsePacket(packet5) - i += 463L - subslot += 1 - } - } - } }