From 294d5335c9d2ced3994daa602c563a8660caf93e Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 21 Aug 2017 19:26:41 -0400 Subject: [PATCH] initial RelatedA0 and RelatedB0 packets modified HandleGamePacket so that it should encode properly; modified CSA so that it attempt to detect packets that encode into ByteVectors that are 'too big' and will attempt to split them up separated the ControlPacket tests into invdividual files and wrote tests for RelatedA0, RelatedB0, and HandleGamePacket proof of concept MTU packet split in CSA; example in WSA @ character select modified session pipeline to accept n queued Actors rather than just two; special packet decoder in progress some effort separating useful sub-operations in encryption/decryption/encoding/decoding functions; introduced PacketCodingActor , devoted to encoding and decoding packets; simplified CSA so that it is devoted just to encrypting and decrypting --- .../packet/ControlPacketOpcode.scala | 10 +- .../net/psforever/packet/PacketCoding.scala | 158 ++++++++----- .../packet/control/ControlSync.scala | 24 +- .../packet/control/ControlSyncResp.scala | 23 +- .../packet/control/HandleGamePacket.scala | 20 +- .../psforever/packet/control/RelatedA0.scala | 22 ++ .../psforever/packet/control/RelatedB0.scala | 23 ++ common/src/test/scala/ControlPacketTest.scala | 190 ---------------- .../scala/control/ControlSyncRespTest.scala | 32 +++ .../test/scala/control/ControlSyncTest.scala | 32 +++ .../scala/control/HandleGamePacketTest.scala | 30 +++ .../scala/control/MultiPacketExTest.scala | 43 ++++ .../src/test/scala/control/RelatedATest.scala | 26 +++ .../src/test/scala/control/RelatedBTest.scala | 26 +++ .../scala/control/SlottedMetaPacketTest.scala | 76 +++++++ .../control/TeardownConnectionTest.scala | 26 +++ .../src/main/scala/CryptoSessionActor.scala | 138 ++++++------ .../src/main/scala/LoginSessionActor.scala | 45 ++-- .../src/main/scala/PacketCodingActor.scala | 213 ++++++++++++++++++ pslogin/src/main/scala/PsLogin.scala | 15 +- pslogin/src/main/scala/Session.scala | 5 +- pslogin/src/main/scala/UdpListener.scala | 2 +- .../src/main/scala/WorldSessionActor.scala | 24 +- 23 files changed, 833 insertions(+), 370 deletions(-) create mode 100644 common/src/main/scala/net/psforever/packet/control/RelatedA0.scala create mode 100644 common/src/main/scala/net/psforever/packet/control/RelatedB0.scala delete mode 100644 common/src/test/scala/ControlPacketTest.scala create mode 100644 common/src/test/scala/control/ControlSyncRespTest.scala create mode 100644 common/src/test/scala/control/ControlSyncTest.scala create mode 100644 common/src/test/scala/control/HandleGamePacketTest.scala create mode 100644 common/src/test/scala/control/MultiPacketExTest.scala create mode 100644 common/src/test/scala/control/RelatedATest.scala create mode 100644 common/src/test/scala/control/RelatedBTest.scala create mode 100644 common/src/test/scala/control/SlottedMetaPacketTest.scala create mode 100644 common/src/test/scala/control/TeardownConnectionTest.scala create mode 100644 pslogin/src/main/scala/PacketCodingActor.scala diff --git a/common/src/main/scala/net/psforever/packet/ControlPacketOpcode.scala b/common/src/main/scala/net/psforever/packet/ControlPacketOpcode.scala index 09f806b9..7c63e573 100644 --- a/common/src/main/scala/net/psforever/packet/ControlPacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/ControlPacketOpcode.scala @@ -49,8 +49,8 @@ object ControlPacketOpcode extends Enumeration { Unknown30 = Value - private def noDecoder(opcode : ControlPacketOpcode.Type) = (a : BitVector) => - Attempt.failure(Err(s"Could not find a marshaller for control packet ${opcode}")) + private def noDecoder(opcode : ControlPacketOpcode.Type) = (_ : BitVector) => + Attempt.failure(Err(s"Could not find a marshaller for control packet $opcode")) def getPacketDecoder(opcode : ControlPacketOpcode.Type) : (BitVector) => Attempt[DecodeResult[PlanetSideControlPacket]] = (opcode.id : @switch) match { // OPCODES 0x00-0f @@ -74,11 +74,11 @@ object ControlPacketOpcode extends Enumeration { // OPCODES 0x10-1e case 0x10 => SlottedMetaPacket.decodeWithOpcode(SlottedMetaPacket7) - case 0x11 => noDecoder(RelatedA0) + case 0x11 => control.RelatedA0.decode case 0x12 => noDecoder(RelatedA1) case 0x13 => noDecoder(RelatedA2) case 0x14 => noDecoder(RelatedA3) - case 0x15 => noDecoder(RelatedB0) + case 0x15 => control.RelatedB0.decode case 0x16 => noDecoder(RelatedB1) case 0x17 => noDecoder(RelatedB2) // 0x18 @@ -89,7 +89,7 @@ object ControlPacketOpcode extends Enumeration { case 0x1c => noDecoder(Unknown28) case 0x1d => control.ConnectionClose.decode case 0x1e => noDecoder(Unknown30) - case default => noDecoder(opcode) + case _ => noDecoder(opcode) } implicit val codec: Codec[this.Value] = PacketHelpers.createEnumerationCodec(this, uint8L) diff --git a/common/src/main/scala/net/psforever/packet/PacketCoding.scala b/common/src/main/scala/net/psforever/packet/PacketCoding.scala index f3500b3e..fa2400e8 100644 --- a/common/src/main/scala/net/psforever/packet/PacketCoding.scala +++ b/common/src/main/scala/net/psforever/packet/PacketCoding.scala @@ -2,13 +2,13 @@ package net.psforever.packet import net.psforever.crypto.CryptoInterface -import scodec.Attempt.{Successful, Failure} +import scodec.Attempt.{Failure, Successful} import scodec.bits._ -import scodec.{Err, Attempt, Codec} -import scodec.codecs.{uint16L, uint8L, bytes} +import scodec.{Attempt, Codec, Err} +import scodec.codecs.{bytes, uint16L, uint8L} /** - * Base trait of the packet container `case class`es. + * Base trait of the packet container `case class`. */ sealed trait PlanetSidePacketContainer @@ -81,7 +81,7 @@ object PacketCoding { /* Marshalling and Encoding. */ /** - * Transforms a type of packet into the `BitVector` representations of its component data and then reconstructs those components. + * Transform a kind of packet into the sequence of data that represents 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 @@ -140,12 +140,11 @@ object PacketCoding { case Successful(p) => seqEncoded = p } } - Attempt.successful(flagsEncoded ++ seqEncoded ++ paddingEncoded ++ payloadEncoded) } /** - * Overloaded method for transforming a control packet into its `BitVector` representation. + * Overloaded method for transforming a `ControlPacket` into its `BitVector` representation. * @param packet the control packet to encode * @return a `BitVector` translated from the packet's data */ @@ -156,7 +155,6 @@ object PacketCoding { 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)) @@ -166,7 +164,7 @@ object PacketCoding { } /** - * Overloaded method for transforming a crypto packet into its `BitVector` representation. + * Overloaded method for transforming a `CryptoPacket` into its `BitVector` representation. * @param packet the crypto packet to encode * @return a `BitVector` translated from the packet's data */ @@ -178,7 +176,7 @@ object PacketCoding { } /** - * Overloaded method for transforming a game packet into its `BitVector` representation. + * Overloaded method for transforming a `GamePacket` into its `BitVector` representation. * @param packet the game packet to encode * @return a `BitVector` translated from the packet's data */ @@ -189,7 +187,6 @@ object PacketCoding { 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)) @@ -199,7 +196,7 @@ object PacketCoding { } /** - * Calls the packet-specific encode function. + * Calls the packet's own `encode` function. * Lowest encode call before the packet-specific implementations. * @param packet the packet to encode * @return a `BitVector` translated from the packet's data @@ -214,12 +211,9 @@ object PacketCoding { 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. + * Transforms `ByteVector` data into a PlanetSide packet. + * Attempt to decode with an optional header and required payload. + * Does not decode into a `GamePacket`. * @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 @@ -237,18 +231,18 @@ object PacketCoding { } /** - * Helper function to decode a packet without specifying a crypto packet state. - * Mostly used when there is no crypto state available, such as tests. + * Helper function to decode a packet without specifying a crypto state. + * Used when there is no crypto state available such as in tests. * @param msg packet data bytes * @return `PlanetSidePacketContainer` */ def UnmarshalPacket(msg : ByteVector) : Attempt[PlanetSidePacketContainer] = UnmarshalPacket(msg, CryptoPacketOpcode.Ignore) /** - * 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. + * Handle decoding for a packet that has been identified as not a `ControlPacket`. + * It may just be encrypted (`EncryptedPacket`) or it may be involved in the encryption process itself (`CryptoPacket`). * @param msg the packet - * @param cryptoState the current state of the connection's crypto + * @param cryptoState the current cryptographic state * @return a `PlanetSidePacketContainer` */ private def unmarshalFlaggedPacket(msg : ByteVector, cryptoState : CryptoPacketOpcode.Type) : Attempt[PlanetSidePacketContainer] = { @@ -258,7 +252,6 @@ object PacketCoding { return Attempt.failure(Err("Failed to parse packet flags: " + e.message)) case _ => } - val flags = decodedFlags.require.value val packetType = flags.packetType packetType match { @@ -275,7 +268,6 @@ object PacketCoding { case _ => return Attempt.failure(Err("Unsupported packet type: " + flags.packetType.toString)) } - //all packets have a two byte sequence ID val decodedSeq = uint16L.decode(decodedFlags.require.remainder) //TODO: make this a codec for reuse decodedSeq match { @@ -285,7 +277,6 @@ object PacketCoding { } val sequence = decodedSeq.require.value val payload = decodedSeq.require.remainder.toByteVector - packetType match { case PacketType.Crypto => unmarshalCryptoPacket(cryptoState, sequence, payload) @@ -295,35 +286,37 @@ object PacketCoding { } /** - * Handle decoding for a control packet. + * Handle decoding for a `ControlPacket`. * @param msg the packet * @return a `ControlPacket` */ private def unmarshalControlPacket(msg : ByteVector) : Attempt[ControlPacket] = { DecodeControlPacket(msg) match { - case f @ Failure(_) => f + case f @ Failure(_) => + f case Successful(p) => Attempt.successful(CreateControlPacket(p)) } } /** - * Handle decoding for a game packet. + * Handle decoding for a `GamePacket`. * @param sequence na * @param msg the packet data * @return a `GamePacket` */ private def unmarshalGamePacket(sequence : Int, msg : ByteVector) : Attempt[GamePacket] = { DecodeGamePacket(msg) match { - case f @ Failure(_) => f + case f @ Failure(_) => + f case Successful(p) => Attempt.successful(CreateGamePacket(sequence, p)) } } /** - * Handle decoding for a crypto packet. - * @param state the current state of the connection's crypto + * Handle decoding for a `CryptoPacket`. + * @param state the current cryptographic state * @param sequence na * @param payload the packet data * @return a `CryptoPacket` @@ -338,8 +331,8 @@ object PacketCoding { } /** - * Handle decoding for an encrypted packet. - * That is, it's already encrypted. + * Handle decoding for an `EncryptedPacket`. + * The payload is already encrypted. * Just repackage the data. * @param sequence na * @param payload the packet data @@ -350,15 +343,17 @@ object PacketCoding { } /** - * Similar to `UnmarshalPacket`, but does not process any packet header and does not support decoding of crypto packets. + * Transforms `ByteVector` data into a PlanetSide packet. + * Similar to the `UnmarshalPacket` but it does not process packet headers. + * It supports `GamePacket` in exchange for not supporting `CryptoPacket` (like `UnMarshalPacket`). * Mostly used in tests. * @param msg raw, unencrypted packet * @return `PlanetSidePacket` + * @see `UnMarshalPacket` */ 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 @@ -367,7 +362,7 @@ object PacketCoding { } /** - * Transform a `BitVector` into a control packet. + * Transform a `ByteVector` into a `ControlPacket`. * @param msg the the raw data to decode * @return a `PlanetSideControlPacket` */ @@ -388,7 +383,7 @@ object PacketCoding { } /** - * Transform a `BitVector` into a game packet. + * Transform a `ByteVector` into a `GamePacket`. * @param msg the the raw data to decode * @return a `PlanetSideGamePacket` */ @@ -411,28 +406,45 @@ object PacketCoding { /* Encrypting and Decrypting. */ /** - * Encrypt the provided packet using the provided crypto state. - * @param crypto the current state of the connection's crypto + * Transform the privileged `packet` into a `RawPacket` representation to get: + * the sequence number, + * and the raw `ByteVector` data. * @param packet the unencrypted packet - * @return an `EncryptedPacket` + * @return paired data based on the packet */ - def encryptPacket(crypto : CryptoInterface.CryptoStateWithMAC, packet : PlanetSidePacketContainer) : Attempt[EncryptedPacket] = { + def getPacketDataForEncryption(packet : PlanetSidePacketContainer) : Attempt[(Int, ByteVector)] = { 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 _ => ; + val sequenceNumber = packet match { //the sequence is variable if this is a GamePacket + case GamePacket(_, seq, _) => seq + case _ => 0 } - encryptPacket(crypto, sequenceNumber, rawPacket.toByteVector) + Successful((sequenceNumber, rawPacket.toByteVector)) - case f @ Failure(_) => f; + case f @ Failure(_) => + f } } /** - * 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. + * Encrypt the provided packet using the provided cryptographic state. + * Translate the packet into data to send on to the actual encryption process. + * @param crypto the current cryptographic state + * @param packet the unencrypted packet + * @return an `EncryptedPacket` + */ + def encryptPacket(crypto : CryptoInterface.CryptoStateWithMAC, packet : PlanetSidePacketContainer) : Attempt[EncryptedPacket] = { + getPacketDataForEncryption(packet) match { + case Successful((seq, data)) => + encryptPacket(crypto, seq, data) + case f @ Failure(_) => + f + } + } + + /** + * Transform either a `GamePacket` or a `ControlPacket` into a `BitVector`. + * This is not as thorough as the process of unmarshalling though the results are very similar. * @param packet a packet * @return a `BitVector` that represents the packet */ @@ -440,7 +452,8 @@ object PacketCoding { 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 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)) @@ -451,7 +464,8 @@ object PacketCoding { 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 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)) @@ -465,10 +479,10 @@ object PacketCoding { /** * Perform encryption on the packet's raw data. - * @param crypto the current state of the connection's crypto + * @param crypto the current cryptographic state * @param sequenceNumber na * @param rawPacket a `ByteVector` that represents the packet data - * @return + * @return an `EncryptedPacket` */ def encryptPacket(crypto : CryptoInterface.CryptoStateWithMAC, sequenceNumber : Int, rawPacket : ByteVector) : Attempt[EncryptedPacket] = { val packetMac = crypto.macForEncrypt(rawPacket) @@ -482,12 +496,39 @@ object PacketCoding { } /** - * Perform decryption on a packet's data. + * Perform decryption on an `EncryptedPacket`. * @param crypto the current state of the connection's crypto * @param packet an encrypted packet - * @return + * @return a general packet container type */ def decryptPacket(crypto : CryptoInterface.CryptoStateWithMAC, packet : EncryptedPacket) : Attempt[PlanetSidePacketContainer] = { + decryptPacketData(crypto, packet) match { + case Successful(payload) => unmarshalPayload(packet.sequenceNumber, payload) + case f @ Failure(_) => f + } + } + + /** + * Transform decrypted packet data into the type of packet that it represents. + * Will not compose data into an `EncryptedPacket` or into a `CryptoPacket`. + * @param sequenceNumber na + * @param payload the decrypted packet data + * @return a general packet container type + */ + def unmarshalPayload(sequenceNumber : Int, payload : ByteVector) : Attempt[PlanetSidePacketContainer] = { + payload{0} match { + case 0x00 => unmarshalControlPacket(payload.drop(1)) + case _ => unmarshalGamePacket(sequenceNumber, payload) + } + } + + /** + * Compose the decrypted payload data from a formerly encrypted packet. + * @param crypto the current state of the connection's crypto + * @param packet an encrypted packet + * @return a sequence of decrypted data + */ + def decryptPacketData(crypto : CryptoInterface.CryptoStateWithMAC, packet : EncryptedPacket) : Attempt[ByteVector] = { val payloadDecrypted = crypto.decrypt(packet.payload) val payloadJustLen = payloadDecrypted.takeRight(1) //get the last byte which is the padding length val padding = uint8L.decode(payloadJustLen.bits) @@ -523,9 +564,6 @@ object PacketCoding { 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")) } - payloadNoMac{0} match { - case 0x00 => unmarshalControlPacket(payloadNoMac.drop(1)) - case _ => unmarshalGamePacket(packet.sequenceNumber, payloadNoMac) - } + Successful(payloadNoMac) } } diff --git a/common/src/main/scala/net/psforever/packet/control/ControlSync.scala b/common/src/main/scala/net/psforever/packet/control/ControlSync.scala index 85fc7cc0..e3861796 100644 --- a/common/src/main/scala/net/psforever/packet/control/ControlSync.scala +++ b/common/src/main/scala/net/psforever/packet/control/ControlSync.scala @@ -5,9 +5,27 @@ import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideContro import scodec.Codec import scodec.codecs._ -final case class ControlSync(timeDiff : Int, unk : Long, - field1 : Long, field2 : Long, field3 : Long, field4 : Long, - field64A : Long, field64B : Long) +/** + * Dispatched by the client periodically (approximately once every ten seconds). + * @param timeDiff the exact number of milliseconds since the last `ControlSync` packet + * @param unk na + * @param field1 na + * @param field2 na + * @param field3 na + * @param field4 na + * @param field64A na; + * increments by 41 per packet + * @param field64B na; + * increments by 21 per packet + */ +final case class ControlSync(timeDiff : Int, + unk : Long, + field1 : Long, + field2 : Long, + field3 : Long, + field4 : Long, + field64A : Long, + field64B : Long) extends PlanetSideControlPacket { type Packet = ControlSync def opcode = ControlPacketOpcode.ControlSync diff --git a/common/src/main/scala/net/psforever/packet/control/ControlSyncResp.scala b/common/src/main/scala/net/psforever/packet/control/ControlSyncResp.scala index f3f0f4e8..1737629e 100644 --- a/common/src/main/scala/net/psforever/packet/control/ControlSyncResp.scala +++ b/common/src/main/scala/net/psforever/packet/control/ControlSyncResp.scala @@ -5,8 +5,27 @@ import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideContro import scodec.Codec import scodec.codecs._ -final case class ControlSyncResp(timeDiff : Int, serverTick : Long, - field1 : Long, field2 : Long, field3 : Long, field4 : Long) +/** + * The response packet dispatched by the server to a client's `ControlSync` packet. + * As noted, it echoes most of the fields originating from within its companion packet except for `serverTick`. + * @param timeDiff na; + * echoes `ControlSync.timeDiff` + * @param serverTick na + * @param field1 na; + * echoes `ControlSync.field64A` + * @param field2 na; + * echoes `ControlSync.field64B` + * @param field3 na; + * echoes `ControlSync.field64B` (+/- 1) + * @param field4 na; + * echoes `ControlSync.field64A` + */ +final case class ControlSyncResp(timeDiff : Int, + serverTick : Long, + field1 : Long, + field2 : Long, + field3 : Long, + field4 : Long) extends PlanetSideControlPacket { type Packet = ControlSyncResp def opcode = ControlPacketOpcode.ControlSyncResp diff --git a/common/src/main/scala/net/psforever/packet/control/HandleGamePacket.scala b/common/src/main/scala/net/psforever/packet/control/HandleGamePacket.scala index 062ca610..56ddd128 100644 --- a/common/src/main/scala/net/psforever/packet/control/HandleGamePacket.scala +++ b/common/src/main/scala/net/psforever/packet/control/HandleGamePacket.scala @@ -3,15 +3,25 @@ package net.psforever.packet.control import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideControlPacket} import scodec.Codec -import scodec.bits.ByteVector +import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ -final case class HandleGamePacket(packet : ByteVector) +final case class HandleGamePacket(len : Int, + stream : ByteVector, + rest : BitVector = BitVector.empty) extends PlanetSideControlPacket { def opcode = ControlPacketOpcode.HandleGamePacket - def encode = throw new Exception("This packet type should never be encoded") + def encode = HandleGamePacket.encode(this) } object HandleGamePacket extends Marshallable[HandleGamePacket] { - implicit val codec : Codec[HandleGamePacket] = bytes.as[HandleGamePacket].decodeOnly -} \ No newline at end of file + def apply(stream : ByteVector) : HandleGamePacket = { + new HandleGamePacket(stream.length.toInt, stream) + } + + implicit val codec : Codec[HandleGamePacket] = ( + ("len" | uint16) >>:~ { len => + ("stream" | bytes(len)) :: + ("rest" | bits) + }).as[HandleGamePacket] +} diff --git a/common/src/main/scala/net/psforever/packet/control/RelatedA0.scala b/common/src/main/scala/net/psforever/packet/control/RelatedA0.scala new file mode 100644 index 00000000..0bd34517 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/control/RelatedA0.scala @@ -0,0 +1,22 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.control + +import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideControlPacket} +import scodec.Codec +import scodec.codecs._ + +/** + * Dispatched from the client in regards to errors trying to process prior `ControlPackets`. + * Explains which packet was in error by sending back its `subslot` number. + * @param subslot identification of a control packet + */ +final case class RelatedA0(subslot : Int) + extends PlanetSideControlPacket { + type Packet = RelatedA0 + def opcode = ControlPacketOpcode.RelatedA0 + def encode = RelatedA0.encode(this) +} + +object RelatedA0 extends Marshallable[RelatedA0] { + implicit val codec : Codec[RelatedA0] = ("subslot" | uint16).as[RelatedA0] +} diff --git a/common/src/main/scala/net/psforever/packet/control/RelatedB0.scala b/common/src/main/scala/net/psforever/packet/control/RelatedB0.scala new file mode 100644 index 00000000..98372963 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/control/RelatedB0.scala @@ -0,0 +1,23 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.control + +import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideControlPacket} +import scodec.Codec +import scodec.codecs._ + +/** + * Dispatched to coordinate information regarding `ControlPacket` packets between the client and server. + * When dispatched by the client, it relates the current (or last received) `SlottedMetaPacket` `subslot` number back to the server. + * When dispatched by the server, it relates ??? + * @param subslot identification of a control packet + */ +final case class RelatedB0(subslot : Int) + extends PlanetSideControlPacket { + type Packet = RelatedB0 + def opcode = ControlPacketOpcode.RelatedB0 + def encode = RelatedB0.encode(this) +} + +object RelatedB0 extends Marshallable[RelatedB0] { + implicit val codec : Codec[RelatedB0] = ("subslot" | uint16).as[RelatedB0] +} diff --git a/common/src/test/scala/ControlPacketTest.scala b/common/src/test/scala/ControlPacketTest.scala deleted file mode 100644 index 2bbe8b71..00000000 --- a/common/src/test/scala/ControlPacketTest.scala +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright (c) 2017 PSForever - -import org.specs2.mutable._ -import org.specs2.specification -import net.psforever.packet._ -import net.psforever.packet.control._ -import org.specs2.specification.core.Fragment -import scodec.Attempt.Successful -import scodec.bits._ -import scodec.codecs.uint16 - -class ControlPacketTest extends Specification { - - "PlanetSide control packet" in { - "ControlSync" should { - val string = hex"0007 5268 0000004D 00000052 0000004D 0000007C 0000004D 0000000000000276 0000000000000275" - - "decode" in { - PacketCoding.DecodePacket(string).require match { - case ControlSync(a, b, c, d, e, f, g, h) => - a mustEqual 21096 - - b mustEqual 0x4d - c mustEqual 0x52 - d mustEqual 0x4d - e mustEqual 0x7c - f mustEqual 0x4d - - g mustEqual 0x276 - h mustEqual 0x275 - case default => - ko - } - } - - "encode" in { - val encoded = PacketCoding.EncodePacket(ControlSync(21096, 0x4d, 0x52, 0x4d, 0x7c, 0x4d, 0x276, 0x275)).require - - encoded.toByteVector mustEqual string - } - } - - "ControlSyncResp" should { - val string = hex"0008 5268 21392D92 0000000000000276 0000000000000275 0000000000000275 0000000000000276" - - "decode" in { - PacketCoding.DecodePacket(string).require match { - case ControlSyncResp(a, b, c, d, e, f) => - a mustEqual 21096 - - b mustEqual 0x21392D92 - c mustEqual 0x276 - d mustEqual 0x275 - e mustEqual 0x275 - f mustEqual 0x276 - case default => - ko - } - } - - "encode" in { - val encoded = PacketCoding.EncodePacket(ControlSyncResp(21096, 0x21392D92, 0x276, 0x275, 0x275, 0x276)).require - - encoded.toByteVector mustEqual string - } - } - - "SlottedMetaPacket" should { - val string = hex"00 09 00 00 00194302484C36563130433F" ++ - hex"4C6835316369774A0000000018FABE0C" ++ - hex"00000000000000000000000001000000" ++ - hex"020000006B7BD8288C6469666671756F" ++ - hex"7469656E740000000000440597570065" ++ - hex"006C0063006F006D006500200074006F" ++ - hex"00200050006C0061006E006500740053" ++ - hex"0069006400650021002000018667656D" ++ - hex"696E690100040001459E2540377540" - - def createMetaPacket(slot : Int, subslot : Int, rest : ByteVector) = hex"00" ++ - ControlPacketOpcode.codec.encode( - ControlPacketOpcode(ControlPacketOpcode.SlottedMetaPacket0.id + slot) - ).require.toByteVector ++ uint16.encode(subslot).require.toByteVector ++ rest - - "decode as the base slot and subslot" in { - PacketCoding.DecodePacket(string).require match { - case SlottedMetaPacket(slot, subslot, rest) => - slot mustEqual 0 - subslot mustEqual 0 - rest mustEqual string.drop(4) - case default => - ko - } - } - - "decode as an arbitrary slot and subslot" in { - val maxSlots = ControlPacketOpcode.SlottedMetaPacket7.id - ControlPacketOpcode.SlottedMetaPacket0.id - - // create all possible SlottedMetaPackets - Fragment.foreach(0 to maxSlots) { i => - "slot " + i ! { - val subslot = 12323 - val pkt = createMetaPacket(i, subslot, ByteVector.empty) - - PacketCoding.DecodePacket(pkt).require match { - case SlottedMetaPacket(slot, subslotDecoded, rest) => - - // XXX: there isn't a simple solution to Slot0 and Slot4 be aliases of each other structurally - // This is probably best left to higher layers - //slot mustEqual i % 4 // this is seen at 0x00A3FBFA - slot mustEqual i - subslotDecoded mustEqual subslot - rest mustEqual ByteVector.empty // empty in this case - case default => - ko - } - } - } - } - - "encode" in { - val encoded = PacketCoding.EncodePacket(SlottedMetaPacket(0, 0x1000, ByteVector.empty)).require - val encoded2 = PacketCoding.EncodePacket(SlottedMetaPacket(3, 0xffff, hex"414243")).require - val encoded3 = PacketCoding.EncodePacket(SlottedMetaPacket(7, 0, hex"00")).require - - encoded.toByteVector mustEqual createMetaPacket(0, 0x1000, ByteVector.empty) - encoded2.toByteVector mustEqual createMetaPacket(3, 0xffff, hex"414243") - encoded3.toByteVector mustEqual createMetaPacket(7, 0, hex"00") - - PacketCoding.EncodePacket(SlottedMetaPacket(8, 0, hex"00")).require must throwA[AssertionError] - PacketCoding.EncodePacket(SlottedMetaPacket(-1, 0, hex"00")).require must throwA[AssertionError] - PacketCoding.EncodePacket(SlottedMetaPacket(0, 0x10000, hex"00")).require must throwA[IllegalArgumentException] - } - } - - "MultiPacketEx" should { - val strings = Vector( - hex"00", - hex"01 41", - hex"01 41" ++ hex"02 4142", - hex"fe" ++ ByteVector.fill(0xfe)(0x41), - hex"ffff00" ++ ByteVector.fill(0xff)(0x41), - hex"ff0001" ++ ByteVector.fill(0x100)(0x41), - hex"ff ffff ffff 0000" ++ ByteVector.fill(0x0000ffff)(0x41), - hex"ff ffff 0000 0100" ++ ByteVector.fill(0x00010000)(0x41) - ) - - val packets = Vector( - MultiPacketEx(Vector(ByteVector.empty)), - MultiPacketEx(Vector(hex"41")), - MultiPacketEx(Vector(hex"41", hex"4142")), - MultiPacketEx(Vector(ByteVector.fill(0xfe)(0x41))), - MultiPacketEx(Vector(ByteVector.fill(0xff)(0x41))), - MultiPacketEx(Vector(ByteVector.fill(0x100)(0x41))), - MultiPacketEx(Vector(ByteVector.fill(0x0000ffff)(0x41))), - MultiPacketEx(Vector(ByteVector.fill(0x00010000)(0x41))) - ) - - "decode" in { - Fragment.foreach(strings.indices) { i => - "test "+i ! { MultiPacketEx.decode(strings{i}.bits).require.value mustEqual packets{i} } - } - } - - "encode" in { - Fragment.foreach(packets.indices) { i => - "test "+i ! { MultiPacketEx.encode(packets{i}).require.toByteVector mustEqual strings{i} } - } - } - } - - "TeardownConnection" should { - val string = hex"00 05 02 4F 57 17 00 06" - - "decode" in { - PacketCoding.DecodePacket(string).require match { - case TeardownConnection(nonce) => - nonce mustEqual 391597826 - case default => - ko - } - } - - "encode" in { - val encoded = PacketCoding.EncodePacket(TeardownConnection(391597826)).require - - encoded.toByteVector mustEqual string - } - } - } -} \ No newline at end of file diff --git a/common/src/test/scala/control/ControlSyncRespTest.scala b/common/src/test/scala/control/ControlSyncRespTest.scala new file mode 100644 index 00000000..dec5fa70 --- /dev/null +++ b/common/src/test/scala/control/ControlSyncRespTest.scala @@ -0,0 +1,32 @@ +// Copyright (c) 2017 PSForever +package control + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.control._ +import scodec.bits._ + +class ControlSyncRespTest extends Specification { + val string = hex"0008 5268 21392D92 0000000000000276 0000000000000275 0000000000000275 0000000000000276" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case ControlSyncResp(a, b, c, d, e, f) => + a mustEqual 21096 + + b mustEqual 0x21392D92 + c mustEqual 0x276 + d mustEqual 0x275 + e mustEqual 0x275 + f mustEqual 0x276 + case _ => + ko + } + } + + "encode" in { + val encoded = PacketCoding.EncodePacket(ControlSyncResp(21096, 0x21392D92, 0x276, 0x275, 0x275, 0x276)).require + + encoded.toByteVector mustEqual string + } +} diff --git a/common/src/test/scala/control/ControlSyncTest.scala b/common/src/test/scala/control/ControlSyncTest.scala new file mode 100644 index 00000000..208e3a78 --- /dev/null +++ b/common/src/test/scala/control/ControlSyncTest.scala @@ -0,0 +1,32 @@ +// Copyright (c) 2017 PSForever +package control + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.control._ +import scodec.bits._ + +class ControlSyncTest extends Specification { + val string = hex"0007 5268 0000004D 00000052 0000004D 0000007C 0000004D 0000000000000276 0000000000000275" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case ControlSync(a, b, c, d, e, f, g, h) => + a mustEqual 21096 + b mustEqual 0x4d + c mustEqual 0x52 + d mustEqual 0x4d + e mustEqual 0x7c + f mustEqual 0x4d + g mustEqual 0x276 + h mustEqual 0x275 + case _ => + ko + } + } + + "encode" in { + val encoded = PacketCoding.EncodePacket(ControlSync(21096, 0x4d, 0x52, 0x4d, 0x7c, 0x4d, 0x276, 0x275)).require + encoded.toByteVector mustEqual string + } +} diff --git a/common/src/test/scala/control/HandleGamePacketTest.scala b/common/src/test/scala/control/HandleGamePacketTest.scala new file mode 100644 index 00000000..840f7e3b --- /dev/null +++ b/common/src/test/scala/control/HandleGamePacketTest.scala @@ -0,0 +1,30 @@ +// Copyright (c) 2017 PSForever +package control + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.control._ +import scodec.bits._ + +class HandleGamePacketTest extends Specification { + //this is the first from a series of SlottedMetaPacket4s; the length field was modified from 12 DC to pass the test + val base = hex"18 D5 96 00 00 BC 8E 00 03 A2 16 5D A4 5F B0 80 00 04 30 40 00 08 30 46 00 4A 00 48 00 02 02 F0 62 1E 80 80 00 00 00 00 00 3F FF CC 0D 40 00 20 00 03 00 27 C3 01 C8 00 00 03 08 00 00 03 FF FF FF FC A4 04 00 00 62 00 18 02 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 C8 00 00 01 00 7E C8 00 C8 00 00 00 5D B0 81 40 00 00 00 00 00 00 00 00 00 00 00 00 02 C0 00 40 83 85 46 86 C7 07 8A 4A 80 70 0C 00 01 98 00 00 01 24 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 31 30 90 78 70 65 5F 6A 6F 69 6E 5F 70 6C 61 74 6F 6F 6E 92 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 31 34 8F 78 70 65 5F 6A 6F 69 6E 5F 6F 75 74 66 69 74 92 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 31 31 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 39 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 38 92 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 31 33 93 78 70 65 5F 77 61 72 70 5F 67 61 74 65 5F 75 73 61 67 65 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 32 92 78 70 65 5F 69 6E 73 74 61 6E 74 5F 61 63 74 69 6F 6E 8E 78 70 65 5F 66 6F 72 6D 5F 73 71 75 61 64 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 36 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 37 8E 78 70 65 5F 6A 6F 69 6E 5F 73 71 75 61 64 8C 78 70 65 5F 62 69 6E 64 5F 61 6D 73 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 35 91 78 70 65 5F 62 69 6E 64 5F 66 61 63 69 6C 69 74" + val string = hex"00 00 01 CB" ++ base + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case HandleGamePacket(len, data, extra) => + len mustEqual 459 + data mustEqual base + extra mustEqual BitVector.empty + case _ => + ko + } + } + + "encode" in { + val pkt = HandleGamePacket(base) + val msg = PacketCoding.EncodePacket(pkt).require.toByteVector + msg mustEqual string + } +} diff --git a/common/src/test/scala/control/MultiPacketExTest.scala b/common/src/test/scala/control/MultiPacketExTest.scala new file mode 100644 index 00000000..e537bf81 --- /dev/null +++ b/common/src/test/scala/control/MultiPacketExTest.scala @@ -0,0 +1,43 @@ +// Copyright (c) 2017 PSForever +package control + +import org.specs2.mutable._ +import net.psforever.packet.control._ +import org.specs2.specification.core.Fragment +import scodec.bits._ + +class MultiPacketExTest extends Specification { + val strings = Vector( + hex"00", + hex"01 41", + hex"01 41" ++ hex"02 4142", + hex"fe" ++ ByteVector.fill(0xfe)(0x41), + hex"ffff00" ++ ByteVector.fill(0xff)(0x41), + hex"ff0001" ++ ByteVector.fill(0x100)(0x41), + hex"ff ffff ffff 0000" ++ ByteVector.fill(0x0000ffff)(0x41), + hex"ff ffff 0000 0100" ++ ByteVector.fill(0x00010000)(0x41) + ) + + val packets = Vector( + MultiPacketEx(Vector(ByteVector.empty)), + MultiPacketEx(Vector(hex"41")), + MultiPacketEx(Vector(hex"41", hex"4142")), + MultiPacketEx(Vector(ByteVector.fill(0xfe)(0x41))), + MultiPacketEx(Vector(ByteVector.fill(0xff)(0x41))), + MultiPacketEx(Vector(ByteVector.fill(0x100)(0x41))), + MultiPacketEx(Vector(ByteVector.fill(0x0000ffff)(0x41))), + MultiPacketEx(Vector(ByteVector.fill(0x00010000)(0x41))) + ) + + "decode" in { + Fragment.foreach(strings.indices) { i => + "test "+i ! { MultiPacketEx.decode(strings{i}.bits).require.value mustEqual packets{i} } + } + } + + "encode" in { + Fragment.foreach(packets.indices) { i => + "test "+i ! { MultiPacketEx.encode(packets{i}).require.toByteVector mustEqual strings{i} } + } + } +} diff --git a/common/src/test/scala/control/RelatedATest.scala b/common/src/test/scala/control/RelatedATest.scala new file mode 100644 index 00000000..6e28c955 --- /dev/null +++ b/common/src/test/scala/control/RelatedATest.scala @@ -0,0 +1,26 @@ +// Copyright (c) 2017 PSForever +package control + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.control._ +import scodec.bits._ + +class RelatedATest extends Specification { + val string0 = hex"00 11 01 04" + + "decode (0)" in { + PacketCoding.DecodePacket(string0).require match { + case RelatedA0(slot) => + slot mustEqual 260 + case _ => + ko + } + } + + "encode (0)" in { + val pkt = RelatedA0(260) + val msg = PacketCoding.EncodePacket(pkt).require.toByteVector + msg mustEqual string0 + } +} diff --git a/common/src/test/scala/control/RelatedBTest.scala b/common/src/test/scala/control/RelatedBTest.scala new file mode 100644 index 00000000..f9dbe56e --- /dev/null +++ b/common/src/test/scala/control/RelatedBTest.scala @@ -0,0 +1,26 @@ +// Copyright (c) 2017 PSForever +package control + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.control._ +import scodec.bits._ + +class RelatedBTest extends Specification { + val string0 = hex"00 15 01 04" + + "decode (0)" in { + PacketCoding.DecodePacket(string0).require match { + case RelatedB0(slot) => + slot mustEqual 260 + case _ => + ko + } + } + + "encode (0)" in { + val pkt = RelatedB0(260) + val msg = PacketCoding.EncodePacket(pkt).require.toByteVector + msg mustEqual string0 + } +} diff --git a/common/src/test/scala/control/SlottedMetaPacketTest.scala b/common/src/test/scala/control/SlottedMetaPacketTest.scala new file mode 100644 index 00000000..7f5569ec --- /dev/null +++ b/common/src/test/scala/control/SlottedMetaPacketTest.scala @@ -0,0 +1,76 @@ +// Copyright (c) 2017 PSForever +package control + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.control._ +import org.specs2.specification.core.Fragment +import scodec.bits._ +import scodec.codecs.uint16 + +class SlottedMetaPacketTest extends Specification { + val string = hex"00 09 00 00 00194302484C36563130433F" ++ + hex"4C6835316369774A0000000018FABE0C" ++ + hex"00000000000000000000000001000000" ++ + hex"020000006B7BD8288C6469666671756F" ++ + hex"7469656E740000000000440597570065" ++ + hex"006C0063006F006D006500200074006F" ++ + hex"00200050006C0061006E006500740053" ++ + hex"0069006400650021002000018667656D" ++ + hex"696E690100040001459E2540377540" + + def createMetaPacket(slot : Int, subslot : Int, rest : ByteVector) = hex"00" ++ + ControlPacketOpcode.codec.encode( + ControlPacketOpcode(ControlPacketOpcode.SlottedMetaPacket0.id + slot) + ).require.toByteVector ++ uint16.encode(subslot).require.toByteVector ++ rest + + "decode as the base slot and subslot" in { + PacketCoding.DecodePacket(string).require match { + case SlottedMetaPacket(slot, subslot, rest) => + slot mustEqual 0 + subslot mustEqual 0 + rest mustEqual string.drop(4) + case _ => + ko + } + } + + "decode as an arbitrary slot and subslot" in { + val maxSlots = ControlPacketOpcode.SlottedMetaPacket7.id - ControlPacketOpcode.SlottedMetaPacket0.id + + // create all possible SlottedMetaPackets + Fragment.foreach(0 to maxSlots) { i => + "slot " + i ! { + val subslot = 12323 + val pkt = createMetaPacket(i, subslot, ByteVector.empty) + + PacketCoding.DecodePacket(pkt).require match { + case SlottedMetaPacket(slot, subslotDecoded, rest) => + + // XXX: there isn't a simple solution to Slot0 and Slot4 be aliases of each other structurally + // This is probably best left to higher layers + //slot mustEqual i % 4 // this is seen at 0x00A3FBFA + slot mustEqual i + subslotDecoded mustEqual subslot + rest mustEqual ByteVector.empty // empty in this case + case _ => + ko + } + } + } + } + + "encode" in { + val encoded = PacketCoding.EncodePacket(SlottedMetaPacket(0, 0x1000, ByteVector.empty)).require + val encoded2 = PacketCoding.EncodePacket(SlottedMetaPacket(3, 0xffff, hex"414243")).require + val encoded3 = PacketCoding.EncodePacket(SlottedMetaPacket(7, 0, hex"00")).require + + encoded.toByteVector mustEqual createMetaPacket(0, 0x1000, ByteVector.empty) + encoded2.toByteVector mustEqual createMetaPacket(3, 0xffff, hex"414243") + encoded3.toByteVector mustEqual createMetaPacket(7, 0, hex"00") + + PacketCoding.EncodePacket(SlottedMetaPacket(8, 0, hex"00")).require must throwA[AssertionError] + PacketCoding.EncodePacket(SlottedMetaPacket(-1, 0, hex"00")).require must throwA[AssertionError] + PacketCoding.EncodePacket(SlottedMetaPacket(0, 0x10000, hex"00")).require must throwA[IllegalArgumentException] + } +} diff --git a/common/src/test/scala/control/TeardownConnectionTest.scala b/common/src/test/scala/control/TeardownConnectionTest.scala new file mode 100644 index 00000000..dc62f354 --- /dev/null +++ b/common/src/test/scala/control/TeardownConnectionTest.scala @@ -0,0 +1,26 @@ +// Copyright (c) 2017 PSForever +package control + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.control._ +import scodec.bits._ + +class TeardownConnectionTest extends Specification { + val string = hex"00 05 02 4F 57 17 00 06" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case TeardownConnection(nonce) => + nonce mustEqual 391597826 + case _ => + ko + } + } + + "encode" in { + val encoded = PacketCoding.EncodePacket(TeardownConnection(391597826)).require + + encoded.toByteVector mustEqual string + } +} diff --git a/pslogin/src/main/scala/CryptoSessionActor.scala b/pslogin/src/main/scala/CryptoSessionActor.scala index 4af704b9..db3f9874 100644 --- a/pslogin/src/main/scala/CryptoSessionActor.scala +++ b/pslogin/src/main/scala/CryptoSessionActor.scala @@ -1,17 +1,13 @@ // Copyright (c) 2017 PSForever -import java.net.{InetAddress, InetSocketAddress} - -import akka.actor.{Actor, ActorLogging, ActorRef, DiagnosticActorLogging, Identify, MDCContextAware} -import net.psforever.crypto.CryptoInterface.{CryptoState, CryptoStateWithMAC} +import akka.actor.{Actor, ActorRef, MDCContextAware} +import net.psforever.crypto.CryptoInterface.CryptoStateWithMAC import net.psforever.crypto.CryptoInterface import net.psforever.packet._ import scodec.Attempt.{Failure, Successful} import scodec.bits._ -import scodec.{Attempt, Codec, Err} -import scodec.codecs.{bytes, uint16L, uint8L} import java.security.SecureRandom -import net.psforever.packet.control.{ClientStart, ServerStart, TeardownConnection} +import net.psforever.packet.control._ import net.psforever.packet.crypto._ import net.psforever.packet.game.PingMsg import org.log4s.MDC @@ -55,18 +51,19 @@ class CryptoSessionActor extends Actor with MDCContextAware { def receive = Initializing def Initializing : Receive = { - case HelloFriend(sessionId, right) => + case HelloFriend(sharedSessionId, pipe) => import MDCContextAware.Implicits._ - this.sessionId = sessionId + this.sessionId = sharedSessionId leftRef = sender() - rightRef = right.asInstanceOf[ActorRef] - - // who ever we send to has to send something back to us - rightRef !> HelloFriend(sessionId, self) - + if(pipe.hasNext) { + rightRef = pipe.next // who ever we send to has to send something back to us + rightRef !> HelloFriend(sessionId, pipe) + } else { + rightRef = sender() + } log.trace(s"Left sender ${leftRef.path.name}") - context.become(NewClient) + case default => log.error("Unknown message " + default) context.stop(self) @@ -85,10 +82,10 @@ class CryptoSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateControlPacket(ServerStart(nonce, serverNonce))) context.become(CryptoExchange) - case default => - log.error(s"Unexpected packet type ${p} in state NewClient") + case _ => + log.error(s"Unexpected packet type $p in state NewClient") } - case Failure(e) => + case Failure(_) => // There is a special case where no crypto is being used. // The only packet coming through looks like PingMsg. This is a hardcoded // feature of the client @ 0x005FD618 @@ -98,56 +95,53 @@ class CryptoSessionActor extends Actor with MDCContextAware { case ping @ PingMsg(_, _) => // reflect the packet back to the sender sendResponse(ping) - case default => log.error(s"Unexpected non-crypto packet type ${packet} in state NewClient") + case _ => + log.error(s"Unexpected non-crypto packet type $packet in state NewClient") } case Failure(e) => log.error("Could not decode packet: " + e + s" in state NewClient") } } - case default => log.error(s"Invalid message '$default' received in state NewClient") + case default => + log.error(s"Invalid message '$default' received in state NewClient") } def CryptoExchange : Receive = { case RawPacket(msg) => PacketCoding.UnmarshalPacket(msg, CryptoPacketOpcode.ClientChallengeXchg) match { - case Failure(e) => log.error("Could not decode packet in state CryptoExchange: " + e) - case Successful(p) => - log.trace("NewClient -> CryptoExchange") + case Failure(e) => + log.error("Could not decode packet in state CryptoExchange: " + e) - p match { + case Successful(pkt) => + log.trace("NewClient -> CryptoExchange") + pkt match { case CryptoPacket(seq, ClientChallengeXchg(time, challenge, p, g)) => cryptoDHState = Some(new CryptoInterface.CryptoDHState()) - val dh = cryptoDHState.get - // initialize our crypto state from the client's P and G dh.start(p, g) - // save the client challenge clientChallenge = ServerChallengeXchg.getCompleteChallenge(time, challenge) - // save the packet we got for a MAC check later. drop the first 3 bytes serverMACBuffer ++= msg.drop(3) - val serverTime = System.currentTimeMillis() / 1000L val randomChallenge = getRandBytes(0xc) - // store the complete server challenge for later serverChallenge = ServerChallengeXchg.getCompleteChallenge(serverTime, randomChallenge) - val packet = PacketCoding.CreateCryptoPacket(seq, - ServerChallengeXchg(serverTime, randomChallenge, dh.getPublicKey)) - + ServerChallengeXchg(serverTime, randomChallenge, dh.getPublicKey) + ) val sentPacket = sendResponse(packet) - // save the sent packet a MAC check serverMACBuffer ++= sentPacket.drop(3) - context.become(CryptoSetupFinishing) - case default => log.error(s"Unexpected packet type $p in state CryptoExchange") + + case _ => + log.error(s"Unexpected packet type $pkt in state CryptoExchange") } } - case default => log.error(s"Invalid message '$default' received in state CryptoExchange") + case default => + log.error(s"Invalid message '$default' received in state CryptoExchange") } def CryptoSetupFinishing : Receive = { @@ -188,11 +182,10 @@ class CryptoSessionActor extends Actor with MDCContextAware { ByteVector("server finished".getBytes) ++ serverMACBuffer ++ hex"01", 0xc) - val clientChallengeResultCheck = CryptoInterface.MD5MAC(masterSecret, - ByteVector("client finished".getBytes) ++ serverMACBuffer ++ hex"01" ++ clientChallengeResult ++ hex"01", - 0xc) - - //println("Check result: " + CryptoInterface.verifyMAC(clientChallenge, clientChallengeResult)) +// val clientChallengeResultCheck = CryptoInterface.MD5MAC(masterSecret, +// ByteVector("client finished".getBytes) ++ serverMACBuffer ++ hex"01" ++ clientChallengeResult ++ hex"01", +// 0xc) +// println("Check result: " + CryptoInterface.verifyMAC(clientChallenge, clientChallengeResult)) val decExpansion = ByteVector("client expansion".getBytes) ++ hex"0000" ++ serverChallenge ++ hex"00000000" ++ clientChallenge ++ hex"00000000" @@ -239,27 +232,32 @@ class CryptoSessionActor extends Actor with MDCContextAware { } def Established : Receive = { + //same as having received ad hoc hexadecimal case RawPacket(msg) => if(sender() == rightRef) { val packet = PacketCoding.encryptPacket(cryptoState.get, 0, msg).require sendResponse(packet) - } else { + } else { //from network-side PacketCoding.UnmarshalPacket(msg) match { case Successful(p) => p match { - case encPacket @ EncryptedPacket(seq, _) => - PacketCoding.decryptPacket(cryptoState.get, encPacket) match { + case encPacket @ EncryptedPacket(_/*seq*/, _) => + PacketCoding.decryptPacketData(cryptoState.get, encPacket) match { case Successful(packet) => - self !> packet + MDC("sessionId") = sessionId.toString + rightRef !> RawPacket(packet) case Failure(e) => log.error("Failed to decode encrypted packet: " + e) } - case default => failWithError(s"Unexpected packet type $default in state Established") + case default => + failWithError(s"Unexpected packet type $default in state Established") } - case Failure(e) => log.error("Could not decode raw packet: " + e) + case Failure(e) => + log.error("Could not decode raw packet: " + e) } } + //message to self? case api : CryptoSessionAPI => api match { case DropCryptoSession() => @@ -268,17 +266,12 @@ class CryptoSessionActor extends Actor with MDCContextAware { PacketCoding.CreateControlPacket(TeardownConnection(clientNonce)) ) } - case ctrl @ ControlPacket(_, _) => - val from = sender() - - handleEstablishedPacket(from, ctrl) - case game @ GamePacket(_, _, _) => - val from = sender() - - handleEstablishedPacket(from, game) + //echo the session router? isn't that normally the leftRef? case sessionAPI : SessionRouterAPI => leftRef !> sessionAPI - case default => failWithError(s"Invalid message '$default' received in state Established") + //error + case default => + failWithError(s"Invalid message '$default' received in state Established") } def failWithError(error : String) = { @@ -311,32 +304,35 @@ class CryptoSessionActor extends Actor with MDCContextAware { clientChallengeResult = ByteVector.empty } - def handleEstablishedPacket(from : ActorRef, cont : PlanetSidePacketContainer) = { - // we are processing a packet we decrypted - if(from == self) { + def handleEstablishedPacket(from : ActorRef, cont : PlanetSidePacketContainer) : Unit = { + //we are processing a packet that we decrypted + if(from == self) { //to WSA, LSA, etc. rightRef !> cont - } else if(from == rightRef) { // processing a completed packet from the right. encrypt - val packet = PacketCoding.encryptPacket(cryptoState.get, cont).require - sendResponse(packet) + } else if(from == rightRef) { //processing a completed packet from the right; to network-side + PacketCoding.getPacketDataForEncryption(cont) match { + case Successful((seq, data)) => + val packet = PacketCoding.encryptPacket(cryptoState.get, seq, data).require + sendResponse(packet) + case Failure(ex) => + log.error(s"$ex") + } } else { - log.error(s"Invalid sender when handling a message in Established ${from}") + log.error(s"Invalid sender when handling a message in Established $from") } } def sendResponse(cont : PlanetSidePacketContainer) : ByteVector = { log.trace("CRYPTO SEND: " + cont) val pkt = PacketCoding.MarshalPacket(cont) - pkt match { - case Failure(e) => + case Failure(_) => log.error(s"Failed to marshal packet ${cont.getClass.getName} when sending response") ByteVector.empty + case Successful(v) => val bytes = v.toByteVector - MDC("sessionId") = sessionId.toString leftRef !> ResponsePacket(bytes) - bytes } } @@ -344,17 +340,15 @@ class CryptoSessionActor extends Actor with MDCContextAware { def sendResponse(pkt : PlanetSideGamePacket) : ByteVector = { log.trace("CRYPTO SEND GAME: " + pkt) val pktEncoded = PacketCoding.EncodePacket(pkt) - pktEncoded match { - case Failure(e) => + case Failure(_) => log.error(s"Failed to encode packet ${pkt.getClass.getName} when sending response") ByteVector.empty + case Successful(v) => val bytes = v.toByteVector - MDC("sessionId") = sessionId.toString leftRef !> ResponsePacket(bytes) - bytes } } diff --git a/pslogin/src/main/scala/LoginSessionActor.scala b/pslogin/src/main/scala/LoginSessionActor.scala index 426086cf..9010e043 100644 --- a/pslogin/src/main/scala/LoginSessionActor.scala +++ b/pslogin/src/main/scala/LoginSessionActor.scala @@ -1,23 +1,19 @@ // Copyright (c) 2017 PSForever -import java.net.{InetAddress, InetSocketAddress} +import java.net.InetSocketAddress import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware} import net.psforever.packet.{PlanetSideGamePacket, _} import net.psforever.packet.control._ import net.psforever.packet.game._ import org.log4s.MDC -import scodec.Attempt.{Failure, Successful} import scodec.bits._ import MDCContextAware.Implicits._ import com.github.mauricio.async.db.{Connection, QueryResult, RowData} -import com.github.mauricio.async.db.mysql.MySQLConnection import com.github.mauricio.async.db.mysql.exceptions.MySQLException -import com.github.mauricio.async.db.mysql.util.URLParser import net.psforever.types.PlanetSideEmpire import scala.concurrent.{Await, Future} import scala.concurrent.duration._ -import scala.util.Random class LoginSessionActor extends Actor with MDCContextAware { private[this] val log = org.log4s.getLogger @@ -29,7 +25,7 @@ class LoginSessionActor extends Actor with MDCContextAware { var leftRef : ActorRef = ActorRef.noSender var rightRef : ActorRef = ActorRef.noSender - var updateServerListTask : Cancellable = null + var updateServerListTask : Cancellable = LoginSessionActor.DefaultCancellable override def postStop() = { if(updateServerListTask != null) @@ -39,12 +35,17 @@ class LoginSessionActor extends Actor with MDCContextAware { def receive = Initializing def Initializing : Receive = { - case HelloFriend(sessionId, right) => - this.sessionId = sessionId + case HelloFriend(aSessionId, pipe) => + this.sessionId = aSessionId leftRef = sender() - rightRef = right.asInstanceOf[ActorRef] - + if(pipe.hasNext) { + rightRef = pipe.next + rightRef !> HelloFriend(aSessionId, pipe) + } else { + rightRef = sender() + } context.become(Started) + case _ => log.error("Unknown message") context.stop(self) @@ -91,7 +92,7 @@ class LoginSessionActor extends Actor with MDCContextAware { /// TODO: figure out what this is what what it does for the PS client /// I believe it has something to do with reliable packet transmission and resending case sync @ ControlSync(diff, unk, f1, f2, f3, f4, fa, fb) => - log.trace(s"SYNC: ${sync}") + log.trace(s"SYNC: $sync") val serverTick = Math.abs(System.nanoTime().toInt) // limit the size to prevent encoding error sendResponse(PacketCoding.CreateControlPacket(ControlSyncResp(diff, serverTick, @@ -131,10 +132,9 @@ class LoginSessionActor extends Actor with MDCContextAware { val future: Future[QueryResult] = connection.sendPreparedStatement("SELECT * FROM accounts where username=?", Array(username)) val mapResult: Future[Any] = future.map(queryResult => queryResult.rows match { - case Some(resultSet) => { + case Some(resultSet) => val row : RowData = resultSet.head row(0) - } case None => -1 } ) @@ -161,12 +161,12 @@ class LoginSessionActor extends Actor with MDCContextAware { // TODO: prevent multiple LoginMessages from being processed in a row!! We need a state machine import game.LoginRespMessage._ - val clientVersion = s"Client Version: ${majorVersion}.${minorVersion}.${revision}, ${buildDate}" + val clientVersion = s"Client Version: $majorVersion.$minorVersion.$revision, $buildDate" if(token.isDefined) - log.info(s"New login UN:$username Token:${token.get}. ${clientVersion}") + log.info(s"New login UN:$username Token:${token.get}. $clientVersion") else - log.info(s"New login UN:$username PW:$password. ${clientVersion}") + log.info(s"New login UN:$username PW:$password. $clientVersion") // This is temporary until a schema has been developed //val loginSucceeded = accountLookup(username, password.getOrElse(token.get)) @@ -187,16 +187,16 @@ class LoginSessionActor extends Actor with MDCContextAware { val response = LoginRespMessage(newToken, LoginError.BadUsernameOrPassword, StationError.AccountActive, StationSubscriptionStatus.Active, 685276011, username, 10001) - log.info(s"Failed login to account ${username}") + log.info(s"Failed login to account $username") sendResponse(PacketCoding.CreateGamePacket(0, response)) } case ConnectToWorldRequestMessage(name, _, _, _, _, _, _) => - log.info(s"Connect to world request for '${name}'") + log.info(s"Connect to world request for '$name'") val response = ConnectToWorldMessage(serverName, serverAddress.getHostString, serverAddress.getPort) sendResponse(PacketCoding.CreateGamePacket(0, response)) sendResponse(DropSession(sessionId, "user transferring to world")) - case default => log.debug(s"Unhandled GamePacket ${pkt}") + case default => log.debug(s"Unhandled GamePacket $pkt") } def updateServerList() = { @@ -228,3 +228,10 @@ class LoginSessionActor extends Actor with MDCContextAware { rightRef !> RawPacket(pkt) } } + +object LoginSessionActor { + final val DefaultCancellable = new Cancellable() { + def isCancelled : Boolean = true + def cancel : Boolean = true + } +} diff --git a/pslogin/src/main/scala/PacketCodingActor.scala b/pslogin/src/main/scala/PacketCodingActor.scala new file mode 100644 index 00000000..7fc39476 --- /dev/null +++ b/pslogin/src/main/scala/PacketCodingActor.scala @@ -0,0 +1,213 @@ +// Copyright (c) 2017 PSForever +import akka.actor.{Actor, ActorRef, MDCContextAware} +import net.psforever.packet._ +import scodec.Attempt.{Failure, Successful} +import scodec.bits._ +import org.log4s.MDC +import MDCContextAware.Implicits._ +import net.psforever.packet.control.{HandleGamePacket, SlottedMetaPacket} + +/** + * In between the network side and the higher functioning side of the simulation: + * accept packets and transform them into a sequence of data (encoding), and + * accept a sequence of data and transform it into s packet (decoding).
+ *
+ * Following the standardization of the `SessionRouter` pipeline, the throughput of this `Actor` has directionality. + * The "network," where the encoded data comes and goes, is assumed to be `leftRef`. + * The "simulation", where the decoded packets come and go, is assumed to be `rightRef`. + * `rightRef` can accept a sequence that looks like encoded data but it will merely pass out the same sequence. + * Likewise, `leftRef` accepts decoded packets but merely ejects the same packets without doing any work on them. + * The former functionality is anticipated. + * The latter functionality is deprecated.
+ *
+ * Encoded data leaving the `Actor` (`leftRef`) is limited by an upper bound capacity. + * Sequences can not be larger than that bound or else they will be dropped. + * This maximum transmission unit (MTU) is used to divide the encoded sequence into chunks of encoded data, + * re-packaged into nested `ControlPacket` units, and each unit encoded. + * The outer packaging is numerically consistent with a `subslot` that starts counting once the simulation starts. + * The client is very specific about the `subslot` number and will reject out-of-order packets. + * It resets to 0 each time this `Actor` starts up and the client reflects this functionality. + */ +class PacketCodingActor extends Actor with MDCContextAware { + private var sessionId : Long = 0 + private var subslot : Int = 0 + private var leftRef : ActorRef = ActorRef.noSender + private var rightRef : ActorRef = ActorRef.noSender + private[this] val log = org.log4s.getLogger + + override def postStop() = { + subslot = 0 //in case this `Actor` restarts + super.postStop() + } + + def receive = Initializing + + def Initializing : Receive = { + case HelloFriend(sharedSessionId, pipe) => + import MDCContextAware.Implicits._ + this.sessionId = sharedSessionId + leftRef = sender() + if(pipe.hasNext) { + rightRef = pipe.next + rightRef !> HelloFriend(sessionId, pipe) + } + else { + rightRef = sender() + } + log.trace(s"Left sender ${leftRef.path.name}") + context.become(Established) + + case default => + log.error("Unknown message " + default) + context.stop(self) + } + + def Established : Receive = { + case RawPacket(msg) => + if(sender == rightRef) { //from LSA, WSA, etc., to network - encode + mtuLimit(msg) + } + else {//from network, to LSA, WSA, etc. - decode + PacketCoding.unmarshalPayload(0, msg) match { //TODO is it safe for this to always be 0? + case Successful(packet) => + sendResponseRight(packet) + case Failure(ex) => + log.info(s"Failed to marshal a packet: $ex") + } + } + //known elevated packet type + case ctrl @ ControlPacket(_, packet) => + if(sender == rightRef) { //from LSA, WSA, to network - encode + PacketCoding.EncodePacket(packet) match { + case Successful(data) => + mtuLimit(data.toByteVector) + case Failure(ex) => + log.error(s"Failed to encode a ControlPacket: $ex") + } + } + else { //deprecated; ControlPackets should not be coming from this direction + log.warn(s"DEPRECATED CONTROL PACKET SEND: $ctrl") + MDC("sessionId") = sessionId.toString + sendResponseRight(ctrl) + } + //known elevated packet type + case game @ GamePacket(_, _, packet) => + if(sender == rightRef) { //from LSA, WSA, etc., to network - encode + PacketCoding.EncodePacket(packet) match { + case Successful(data) => + mtuLimit(data.toByteVector) + case Failure(ex) => + log.error(s"Failed to encode a GamePacket: $ex") + } + } + else { //deprecated; GamePackets should not be coming from this direction + log.warn(s"DEPRECATED GAME PACKET SEND: $game") + MDC("sessionId") = sessionId.toString + sendResponseRight(game) + } + //etc + case msg => + log.trace(s"PACKET SEND, LEFT: $msg") + if(sender == rightRef) { + MDC("sessionId") = sessionId.toString + leftRef !> msg + } + else { + MDC("sessionId") = sessionId.toString + rightRef !> msg + } +// case default => +// failWithError(s"Invalid message '$default' received in state Established") + } + + def resetState() : Unit = { + context.become(receive) + } + + /** + * Retrieve the current subslot number. + * Increment the `subslot` for the next time it is needed. + * @return a 16u number starting at 0 + */ + def Subslot : Int = { + if(subslot == 65536) { //TODO what is the actual wrap number? + subslot = 0 + subslot + } else { + val curr = subslot + subslot += 1 + curr + } + } + + /** + * Check that an outbound packet is not too big to get stuck by the MTU. + * If it is larger than the MTU, divide it up and re-package the sections. + * Otherwise, send the data out like normal. + * @param msg the encoded packet data + */ + def mtuLimit(msg : ByteVector) : Unit = { + if(msg.length > PacketCodingActor.MTU_LIMIT_BYTES) { + handleSplitPacket(PacketCoding.CreateControlPacket(HandleGamePacket(msg))) + } + else { + sendResponseLeft(msg) + } + } + + /** + * Transform a `ControlPacket` into `ByteVector` data for splitting. + * @param cont the original `ControlPacket` + */ + def handleSplitPacket(cont : ControlPacket) : Unit = { + PacketCoding.getPacketDataForEncryption(cont) match { + case Successful((_, data)) => + handleSplitPacket(data) + case Failure(ex) => + log.error(s"$ex") + } + } + + /** + * Accept `ByteVector` data, representing a `ControlPacket`, and split it into chunks. + * The chunks should not be blocked by the MTU. + * Send each chunk (towards the network) as it is converted. + * @param data `ByteVector` data to be split + */ + def handleSplitPacket(data : ByteVector) : Unit = { + val lim = PacketCodingActor.MTU_LIMIT_BYTES - 4 //4 bytes is the base size of SlottedMetaPacket + data.grouped(lim).foreach(bvec => { + val pkt = PacketCoding.CreateControlPacket(SlottedMetaPacket(4, Subslot, bvec)) + PacketCoding.EncodePacket(pkt.packet) match { + case Successful(bdata) => + sendResponseLeft(bdata.toByteVector) + case f @ Failure(_) => + log.error(s"$f") + } + }) + } + + /** + * Encoded sequence of data going towards the network. + * @param cont the data + */ + def sendResponseLeft(cont : ByteVector) : Unit = { + log.trace("PACKET SEND, LEFT: " + cont) + MDC("sessionId") = sessionId.toString + leftRef !> RawPacket(cont) + } + + /** + * Decoded packet going towards the simulation. + * @param cont the packet + */ + def sendResponseRight(cont : PlanetSidePacketContainer) : Unit = { + log.trace("PACKET SEND, RIGHT: " + cont) + MDC("sessionId") = sessionId.toString + rightRef !> cont + } +} + +object PacketCodingActor { + final val MTU_LIMIT_BYTES : Int = 467 +} diff --git a/pslogin/src/main/scala/PsLogin.scala b/pslogin/src/main/scala/PsLogin.scala index fefc32c8..fab1d3e7 100644 --- a/pslogin/src/main/scala/PsLogin.scala +++ b/pslogin/src/main/scala/PsLogin.scala @@ -3,7 +3,7 @@ import java.net.InetAddress import java.io.File import java.util.Locale -import akka.actor.{ActorSystem, Props} +import akka.actor.{ActorRef, ActorSystem, Props} import akka.routing.RandomPool import ch.qos.logback.classic.LoggerContext import ch.qos.logback.classic.joran.JoranConfigurator @@ -24,17 +24,16 @@ import scala.collection.JavaConverters._ import scala.concurrent.Await import scala.concurrent.duration._ - object PsLogin { private val logger = org.log4s.getLogger var args : Array[String] = Array() var config : java.util.Map[String,Object] = null - implicit var system : akka.actor.ActorSystem = null - var loginRouter : akka.actor.Props = null - var worldRouter : akka.actor.Props = null - var loginListener : akka.actor.ActorRef = null - var worldListener : akka.actor.ActorRef = null + implicit var system : ActorSystem = null + var loginRouter : Props = Props.empty + var worldRouter : Props = Props.empty + var loginListener : ActorRef = ActorRef.noSender + var worldListener : ActorRef = ActorRef.noSender def banner() : Unit = { println(ansi().fgBright(BLUE).a(""" ___ ________""")) @@ -178,10 +177,12 @@ object PsLogin { */ val loginTemplate = List( SessionPipeline("crypto-session-", Props[CryptoSessionActor]), + SessionPipeline("packet-session-", Props[PacketCodingActor]), SessionPipeline("login-session-", Props[LoginSessionActor]) ) val worldTemplate = List( SessionPipeline("crypto-session-", Props[CryptoSessionActor]), + SessionPipeline("packet-session-", Props[PacketCodingActor]), SessionPipeline("world-session-", Props[WorldSessionActor]) ) diff --git a/pslogin/src/main/scala/Session.scala b/pslogin/src/main/scala/Session.scala index 3b74b33e..b0fbd643 100644 --- a/pslogin/src/main/scala/Session.scala +++ b/pslogin/src/main/scala/Session.scala @@ -35,7 +35,10 @@ class Session(val sessionId : Long, a } - pipeline.head ! HelloFriend(sessionId, pipeline.tail.head) + val pipelineIter = pipeline.iterator + if(pipelineIter.hasNext) { + pipelineIter.next ! HelloFriend(sessionId, pipelineIter) + } // statistics var bytesSent : Long = 0 diff --git a/pslogin/src/main/scala/UdpListener.scala b/pslogin/src/main/scala/UdpListener.scala index d34f14ee..dffc15f1 100644 --- a/pslogin/src/main/scala/UdpListener.scala +++ b/pslogin/src/main/scala/UdpListener.scala @@ -11,7 +11,7 @@ import akka.util.ByteString final case class ReceivedPacket(msg : ByteVector, from : InetSocketAddress) final case class SendPacket(msg : ByteVector, to : InetSocketAddress) final case class Hello() -final case class HelloFriend(sessionId : Long, next: ActorRef) +final case class HelloFriend(sessionId : Long, next: Iterator[ActorRef]) class UdpListener(nextActorProps : Props, nextActorName : String, diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 52094def..7423c32e 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -60,16 +60,20 @@ class WorldSessionActor extends Actor with MDCContextAware { def receive = Initializing def Initializing : Receive = { - case HelloFriend(inSessionId, right) => + case HelloFriend(inSessionId, pipe) => this.sessionId = inSessionId leftRef = sender() - rightRef = right.asInstanceOf[ActorRef] - + if(pipe.hasNext) { + rightRef = pipe.next + rightRef !> HelloFriend(sessionId, pipe) + } else { + rightRef = sender() + } + context.become(Started) ServiceManager.serviceManager ! Lookup("avatar") ServiceManager.serviceManager ! Lookup("accessor1") ServiceManager.serviceManager ! Lookup("taskResolver") - context.become(Started) case _ => log.error("Unknown message") context.stop(self) @@ -537,8 +541,18 @@ class WorldSessionActor extends Actor with MDCContextAware { handlePkt(v) } } + + case RelatedA0(subslot) => + log.error(s"Client not ready for last control packet with subslot $subslot; potential system disarray") + + case RelatedB0(subslot) => + log.trace(s"Good control packet received $subslot") + + case TeardownConnection(_) => + log.info("Good bye") + case default => - log.debug(s"Unhandled ControlPacket $default") + log.warn(s"Unhandled ControlPacket $default") } }