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")
}
}