diff --git a/common/src/main/scala/net/psforever/packet/control/MultiPacketCollector.scala b/common/src/main/scala/net/psforever/packet/control/MultiPacketCollector.scala
index c02bf15a..16afb22c 100644
--- a/common/src/main/scala/net/psforever/packet/control/MultiPacketCollector.scala
+++ b/common/src/main/scala/net/psforever/packet/control/MultiPacketCollector.scala
@@ -13,11 +13,18 @@ import net.psforever.packet.PlanetSidePacket
* the only way to access these packets is through pattern matching
*/
final case class MultiPacketBundle(private var packets : List[PlanetSidePacket]) {
- packets match {
+ MultiPacketBundle.collectValidPackets(packets) match {
case Nil =>
throw new IllegalArgumentException("can not create with zero packets")
+ case list =>
+ packets = list
+ }
+
+ def +(t : MultiPacketBundle) : MultiPacketBundle = t match {
+ case MultiPacketBundle(list) =>
+ MultiPacketBundle(packets ++ list)
case _ =>
- packets = MultiPacketBundle.collectValidPackets(packets)
+ MultiPacketBundle(packets)
}
}
@@ -39,19 +46,14 @@ object MultiPacketBundle {
})
if(bad.nonEmpty) {
org.log4s.getLogger("MultiPacketBundle")
- .warn(s"attempted to include packet types that are on the whitelist; ${bad.size} items have been excluded")
+ .warn(s"attempted to include packet types that are not in the whitelist; ${bad.size} items have been excluded")
}
good
}
}
/**
- * Accumulator for packets that will eventually be bundled and submitted for composing a `MultiPacketEx` packet.
- *
- * The accumulator is intended to be a disposable convenience class to incrementally construct a `MultiPacketBundle`.
- * Packets can only be added on top of the existing internal collection and can not be removed.
- * (Overloaded methods for adding packets from various sources also exist.)
- * Additionally, retrieving a copy of the collection via a `MultiPacketBundle` does not empty the collection.
+ * Accumulator for packets that will eventually be bundled and submitted for composing a `MultiPacketEx` packet.
*/
class MultiPacketCollector() {
private var bundle : List[PlanetSidePacket] = List.empty
@@ -64,14 +66,27 @@ class MultiPacketCollector() {
}
def Add(t : List[PlanetSidePacket]) : Unit = {
- bundle = bundle ++ t
+ if(t.nonEmpty) {
+ bundle = bundle ++ t
+ }
}
/**
* Retrieve the internal collection of packets.
+ * Reset the internal list of packets by clearing it.
* @return a loaded `MultiPacketBundle` object
*/
- def Bundle : MultiPacketBundle = MultiPacketBundle(bundle)
+ def Bundle : MultiPacketBundle = {
+ try {
+ val out = MultiPacketBundle(bundle)
+ bundle = List.empty
+ out
+ }
+ catch {
+ case _ : Exception => //catch and rethrow the exception
+ throw new RuntimeException("no packets")
+ }
+ }
/**
* A safer `Bundle` that consumes any` Exceptions` that might be thrown in the process of producing output.
@@ -91,7 +106,18 @@ class MultiPacketCollector() {
object MultiPacketCollector {
/**
- * Overload constructor that accepts a initial packets.
+ * Overload constructor that accepts initial packets.
+ * @param bundle previously bundled packets
+ * @return a `MultiPacketCollector` object
+ */
+ def apply(bundle : MultiPacketBundle) : MultiPacketCollector = {
+ val obj = new MultiPacketCollector()
+ obj.Add(bundle)
+ obj
+ }
+
+ /**
+ * Overload constructor that accepts initial packets.
* @param packets a series of packets
* @return a `MultiPacketCollector` object
*/
diff --git a/common/src/test/scala/control/MultiPacketCollectorTest.scala b/common/src/test/scala/control/MultiPacketCollectorTest.scala
new file mode 100644
index 00000000..38d7e9f9
--- /dev/null
+++ b/common/src/test/scala/control/MultiPacketCollectorTest.scala
@@ -0,0 +1,183 @@
+// Copyright (c) 2017 PSForever
+package control
+
+import org.specs2.mutable._
+import net.psforever.packet.control.{ControlSync, MultiPacketBundle, MultiPacketCollector}
+import net.psforever.packet.crypto.{ClientFinished, ServerFinished}
+import net.psforever.packet.game.{ObjectDeleteMessage, PlanetSideGUID}
+
+class MultiPacketCollectorTest extends Specification {
+ val packet1 = ObjectDeleteMessage(PlanetSideGUID(1103), 2)
+
+ "MultiPacketBundle" should {
+ import scodec.bits._
+ val packet2 = ControlSync(21096, 0x4d, 0x52, 0x4d, 0x7c, 0x4d, 0x276, 0x275)
+
+ "construct" in {
+ MultiPacketBundle(List(packet1))
+ ok
+ }
+
+ "fail to construct if not initialized with PlanetSidePackets" in {
+ MultiPacketBundle(Nil) must throwA[IllegalArgumentException]
+ }
+
+ "concatenate bundles into a new bundle" in {
+ val obj1 = MultiPacketBundle(List(packet1))
+ val obj2 = MultiPacketBundle(List(packet2))
+ val obj3 = obj1 + obj2
+ obj3 match {
+ case MultiPacketBundle(list) =>
+ list.size mustEqual 2
+ list.head mustEqual packet1
+ list(1) mustEqual packet2
+ case _ =>
+ ko
+ }
+ }
+
+ "accept PlanetSideGamePackets and PlanetSideControlPackets" in {
+ MultiPacketBundle(List(packet2, packet1)) match {
+ case MultiPacketBundle(list) =>
+ list.size mustEqual 2
+ list.head mustEqual packet2
+ list(1) mustEqual packet1
+ case _ =>
+ ko
+ }
+ }
+
+ "ignore other types of PlanetSideContainerPackets" in {
+ val param = List(packet2, ClientFinished(hex"", hex""), packet1, ServerFinished(hex""))
+ MultiPacketBundle(param) match { //warning message will display in log
+ case MultiPacketBundle(list) =>
+ list.size mustEqual 2
+ list.head mustEqual param.head
+ list(1) mustEqual param(2)
+ case _ =>
+ ko
+ }
+ }
+ }
+
+ "MultiPacketCollector" should {
+ val packet2 = ObjectDeleteMessage(PlanetSideGUID(1105), 2)
+ val packet3 = ObjectDeleteMessage(PlanetSideGUID(1107), 2)
+
+ "construct" in {
+ new MultiPacketCollector()
+ ok
+ }
+
+ "construct with initial packets" in {
+ MultiPacketCollector(List(packet1, packet2))
+ ok
+ }
+
+ "can retrieve a bundle packets" in {
+ val obj = MultiPacketCollector(List(packet1, packet2))
+ obj.Bundle match {
+ case MultiPacketBundle(list) =>
+ list.size mustEqual 2
+ list.head mustEqual packet1
+ list(1) mustEqual packet2
+ case _ =>
+ ko
+ }
+ }
+
+ "can not retrieve a bundle of non-existent packets" in {
+ val obj = new MultiPacketCollector()
+ obj.Bundle must throwA[RuntimeException]
+ }
+
+ "can safely retrieve a bundle of potential packets" in {
+ val obj1 = new MultiPacketCollector()
+ obj1.BundleOption match {
+ case Some(_) =>
+ ko
+ case _ => ;
+ }
+
+ val obj2 = MultiPacketCollector(List(packet1, packet2))
+ obj2.BundleOption match {
+ case None =>
+ ko
+ case Some(MultiPacketBundle(list)) =>
+ list.size mustEqual 2
+ list.head mustEqual packet1
+ list(1) mustEqual packet2
+ }
+ }
+
+ "clear packets after being asked to bundle" in {
+ val list = List(packet1, packet2)
+ val obj = MultiPacketCollector(list)
+ obj.Bundle mustEqual MultiPacketBundle(list)
+ obj.Bundle must throwA[RuntimeException]
+ }
+
+ "add a packet" in {
+ val obj = new MultiPacketCollector()
+ obj.Add(packet1)
+ obj.Bundle match {
+ case MultiPacketBundle(list) =>
+ list.size mustEqual 1
+ list.head mustEqual packet1
+ case _ =>
+ ko
+ }
+ }
+
+ "add packets" in {
+ val obj = new MultiPacketCollector()
+ obj.Add(List(packet1, packet2))
+ obj.Bundle match {
+ case MultiPacketBundle(list) =>
+ list.size mustEqual 2
+ list.head mustEqual packet1
+ list(1) mustEqual packet2
+ case _ =>
+ ko
+ }
+ }
+
+ "concatenate bundles (1)" in {
+
+ val obj1 = new MultiPacketCollector()
+ obj1.Add(List(packet1, packet2))
+ val bundle1 = obj1.Bundle
+
+ val obj2 = MultiPacketCollector(bundle1)
+ obj2.Add(packet3)
+ obj2.Bundle match {
+ case MultiPacketBundle(list) =>
+ list.size mustEqual 3
+ list.head mustEqual packet1
+ list(1) mustEqual packet2
+ list(2) mustEqual packet3
+ case _ =>
+ ko
+ }
+ }
+
+ "concatenate bundles (2)" in {
+ val obj1 = new MultiPacketCollector()
+ obj1.Add(List(packet1, packet2))
+ val bundle1 = obj1.Bundle
+
+ val obj2 = new MultiPacketCollector()
+ obj2.Add(packet3)
+ obj2.Add(bundle1)
+ obj2.Bundle match {
+ case MultiPacketBundle(list) =>
+ list.size mustEqual 3
+ list.head mustEqual packet3
+ list(1) mustEqual packet1
+ list(2) mustEqual packet2
+ case _ =>
+ ko
+ }
+ }
+ }
+}
diff --git a/pslogin/src/main/scala/PacketCodingActor.scala b/pslogin/src/main/scala/PacketCodingActor.scala
index 45e6ff1d..5a5b06b6 100644
--- a/pslogin/src/main/scala/PacketCodingActor.scala
+++ b/pslogin/src/main/scala/PacketCodingActor.scala
@@ -5,7 +5,7 @@ import scodec.Attempt.{Failure, Successful}
import scodec.bits._
import org.log4s.MDC
import MDCContextAware.Implicits._
-import net.psforever.packet.control._
+import net.psforever.packet.control.{HandleGamePacket, _}
import scala.annotation.tailrec
import scala.collection.mutable
@@ -73,12 +73,6 @@ class PacketCodingActor extends Actor with MDCContextAware {
}
else {//from network, to LSA, WSA, etc. - decode
UnmarshalInnerPacket(msg, "a packet")
-// PacketCoding.unmarshalPayload(0, msg) match { //TODO is it safe for this to always be 0?
-// case Successful(packet) =>
-// handlePacketContainer(packet) //sendResponseRight
-// case Failure(ex) =>
-// log.info(s"Failed to marshal a packet: $ex")
-// }
}
//known elevated packet type
case ctrl @ ControlPacket(_, packet) =>
@@ -195,17 +189,12 @@ class PacketCodingActor extends Actor with MDCContextAware {
*
* The original packets are encoded then paired with their encoding lengths plus extra space to prefix the length.
* Encodings from these pairs are drawn from the list until into buckets that fit a maximum byte stream length.
- * The size limitation on any bucket is the mtu limit
+ * The size limitation on any bucket is the MTU limit.
* less by the base sizes of `MultiPacketEx` (2) and of `SlottedMetaPacket` (4).
* @param bundle the packets to be bundled
*/
def handleBundlePacket(bundle : List[PlanetSidePacket]) : Unit = {
- val packets : List[(ByteVector, Int)] = recursiveEncode(bundle.iterator).map( pkt => {
- pkt -> {
- val len = pkt.length.toInt
- len + (if(len < 256) { 1 } else if(len < 65536) { 2 } else { 4 }) //space for the prefixed length byte(s)
- }
- })
+ val packets : List[ByteVector] = recursiveEncode(bundle.iterator)
recursiveFillPacketBuckets(packets.iterator, PacketCodingActor.MTU_LIMIT_BYTES - 6)
.foreach( list => {
handleBundlePacket(list.toVector)
@@ -213,15 +202,30 @@ class PacketCodingActor extends Actor with MDCContextAware {
}
/**
- * Accept a `Vector` of encoded packets and re-package them into a `MultiPacketEx`.
+ * Accept a `Vector` of encoded packets and re-package them.
+ * The normal order is to package the elements of the vector into a `MultiPacketEx`.
+ * If the vector only has one element, it will get packaged by itself in a `SlottedMetaPacket`.
+ * If that one element risks being too big for the MTU, however, it will be handled off to be split.
+ * Splitting should preserve `Subslot` ordering with the rest of the bundling.
* @param vec a specific number of byte streams
*/
def handleBundlePacket(vec : Vector[ByteVector]) : Unit = {
- PacketCoding.EncodePacket(MultiPacketEx(vec)) match {
- case Successful(bdata) =>
- handleBundlePacket(bdata.toByteVector)
- case Failure(e) =>
- log.warn(s"bundling failed on MultiPacketEx creation: - $e")
+ if(vec.size == 1) {
+ val elem = vec.head
+ if(elem.length > PacketCodingActor.MTU_LIMIT_BYTES - 4) {
+ handleSplitPacket(PacketCoding.CreateControlPacket(HandleGamePacket(elem)))
+ }
+ else {
+ handleBundlePacket(elem)
+ }
+ }
+ else {
+ PacketCoding.EncodePacket(MultiPacketEx(vec)) match {
+ case Successful(bdata) =>
+ handleBundlePacket(bdata.toByteVector)
+ case Failure(e) =>
+ log.warn(s"bundling failed on MultiPacketEx creation: - $e")
+ }
}
}
@@ -325,8 +329,15 @@ class PacketCodingActor extends Actor with MDCContextAware {
rightRef !> cont
}
- /** WIP */
-
+ /**
+ * Accept a series of packets and transform it into a series of packet encodings.
+ * Packets that do not encode properly are simply excluded for the product.
+ * This is not treated as an error or exception; a warning will mrely be logged.
+ * @param iter the `Iterator` for a series of packets
+ * @param out updated series of byte stream data produced through successful packet encoding;
+ * defaults to an empty list
+ * @return a series of byte stream data produced through successful packet encoding
+ */
@tailrec private def recursiveEncode(iter : Iterator[PlanetSidePacket], out : List[ByteVector] = List()) : List[ByteVector] = {
if(!iter.hasNext) {
out
@@ -356,18 +367,29 @@ class PacketCodingActor extends Actor with MDCContextAware {
}
}
- @tailrec private def recursiveFillPacketBuckets(iter : Iterator[(ByteVector, Int)], lim : Int, currLen : Int = 0, out : List[mutable.ListBuffer[ByteVector]] = List(mutable.ListBuffer())) : List[mutable.ListBuffer[ByteVector]] = {
+ /**
+ * Accept a series of byte stream data and sort into sequential size-limited buckets of the same byte streams.
+ * Note that elements that exceed `lim` by themselves are always sorted into their own buckets.
+ * @param iter an `Iterator` of a series of byte stream data
+ * @param lim the maximum stream length permitted
+ * @param curr the stream length of the current bucket
+ * @param out updated series of byte stream data stored in buckets
+ * @return a series of byte stream data stored in buckets
+ */
+ @tailrec private def recursiveFillPacketBuckets(iter : Iterator[ByteVector], lim : Int, curr : Int = 0, out : List[mutable.ListBuffer[ByteVector]] = List(mutable.ListBuffer())) : List[mutable.ListBuffer[ByteVector]] = {
if(!iter.hasNext) {
out
}
else {
- val (data, len) = iter.next
- if(currLen + len > lim) {
+ val data = iter.next
+ var len = data.length.toInt
+ len = len + (if(len < 256) { 1 } else if(len < 65536) { 2 } else { 4 }) //space for the prefixed length byte(s)
+ if(curr + len > lim && out.last.nonEmpty) { //bucket must have something in it before swapping
recursiveFillPacketBuckets(iter, lim, len, out :+ mutable.ListBuffer(data))
}
else {
out.last += data
- recursiveFillPacketBuckets(iter, lim, currLen + len, out)
+ recursiveFillPacketBuckets(iter, lim, curr + len, out)
}
}
}
diff --git a/pslogin/src/test/scala/PacketCodingActorTest.scala b/pslogin/src/test/scala/PacketCodingActorTest.scala
index 1e7325d6..5cd58b0b 100644
--- a/pslogin/src/test/scala/PacketCodingActorTest.scala
+++ b/pslogin/src/test/scala/PacketCodingActorTest.scala
@@ -2,10 +2,11 @@
import akka.actor.{ActorRef, Props}
import akka.testkit.TestProbe
-import net.psforever.packet.control.ControlSync
-import net.psforever.packet.game.objectcreate.ObjectClass
-import net.psforever.packet.{ControlPacket, GamePacket, PacketCoding}
+import net.psforever.packet.control.{ControlSync, MultiPacketBundle, SlottedMetaPacket}
+import net.psforever.packet.{ControlPacket, GamePacket, GamePacketOpcode, PacketCoding}
import net.psforever.packet.game._
+import net.psforever.packet.game.objectcreate.ObjectClass
+import net.psforever.types._
import scodec.bits._
import scala.concurrent.duration._
@@ -364,6 +365,398 @@ class PacketCodingActorDTest extends ActorTest {
}
}
+class PacketCodingActorETest extends ActorTest {
+ "PacketCodingActor" should {
+ "unwind l-originating hexadecimal data into multiple r-facing packets (MultiPacket -> 2 PlayerStateMessageUpstream)" in {
+ val string_hex = RawPacket(hex"00 03 18 BD E8 04 5C 02 60 E3 F9 19 0E C1 41 27 00 04 02 60 20 0C 58 0B 20 00 00 18 BD E8 04 86 02 62 13 F9 19 0E D8 40 4D 00 04 02 60 20 0C 78 0A 80 00 00")
+ val string_obj1 = GamePacket(GamePacketOpcode.PlayerStateMessageUpstream, 0, PlayerStateMessageUpstream(PlanetSideGUID(1256),Vector3(3076.7188f,4734.1094f,56.390625f),Some(Vector3(4.0625f,4.59375f,0.0f)),36.5625f,357.1875f,0.0f,866,0,false,false,false,false,178,0))
+ val string_obj2 = GamePacket(GamePacketOpcode.PlayerStateMessageUpstream, 0, PlayerStateMessageUpstream(PlanetSideGUID(1256),Vector3(3077.0469f,4734.258f,56.390625f),Some(Vector3(5.5f,1.1875f,0.0f)),36.5625f,357.1875f,0.0f,867,0,false,false,false,false,168,0))
+
+ val probe1 = TestProbe()
+ val probe2 = system.actorOf(Props(classOf[ActorTest.MDCTestProbe], probe1), "mdc-probe")
+ val pca : ActorRef = system.actorOf(Props[PacketCodingActor], "pca")
+ pca ! HelloFriend(135, List(probe2).iterator)
+ probe1.receiveOne(100 milli) //consume
+
+ pca ! string_hex
+ val reply = probe1.receiveN(2, 200 milli)
+ assert(reply.head == string_obj1)
+ assert(reply(1) == string_obj2)
+ probe1.expectNoMsg(100 milli)
+ }
+ }
+}
+
+class PacketCodingActorFTest extends ActorTest {
+ "PacketCodingActor" should {
+ "unwind l-originating hexadecimal data into an r-facing packet (MultiPacket -> RelatedB + GenericObjectStateMsg)" in {
+ val string_hex = RawPacket(hex"00 03 04 00 15 02 98 0B 00 09 0C 0A 1D F2 00 10 00 00 00")
+ val string_obj = GamePacket(GamePacketOpcode.GenericObjectStateMsg, 0, GenericObjectStateMsg(PlanetSideGUID(242), 16))
+
+ val probe1 = TestProbe()
+ val probe2 = system.actorOf(Props(classOf[ActorTest.MDCTestProbe], probe1), "mdc-probe")
+ val pca : ActorRef = system.actorOf(Props[PacketCodingActor], "pca")
+ pca ! HelloFriend(135, List(probe2).iterator)
+ probe1.receiveOne(100 milli) //consume
+
+ pca ! string_hex
+ val reply = probe1.receiveN(1, 200 milli)
+ assert(reply.head == string_obj)
+ //the RelatedB message - 00 15 02 98 - is consumed by pca
+ probe1.expectNoMsg(100 milli)
+ }
+ }
+}
+
+class PacketCodingActorGTest extends ActorTest {
+ "PacketCodingActor" should {
+ "unwind l-originating hexadecimal data into an r-facing packet (MultiPacketEx -> RelatedA + GenericObjectStateMsg)" in {
+ val string_hex = RawPacket(hex"00 19 04 00 11 02 98 0B 00 09 0C 0A 1D F2 00 10 00 00 00")
+ val string_obj = GamePacket(GamePacketOpcode.GenericObjectStateMsg, 0, GenericObjectStateMsg(PlanetSideGUID(242), 16))
+
+ val probe1 = TestProbe()
+ val probe2 = system.actorOf(Props(classOf[ActorTest.MDCTestProbe], probe1), "mdc-probe")
+ val pca : ActorRef = system.actorOf(Props[PacketCodingActor], "pca")
+ pca ! HelloFriend(135, List(probe2).iterator)
+ probe1.receiveOne(100 milli) //consume
+
+ pca ! string_hex
+ val reply = probe1.receiveN(1, 200 milli)
+ assert(reply.head == string_obj)
+ //the RelatedA message - 00 11 02 98 - is consumed by pca; should see error log message in console
+ probe1.expectNoMsg(100 milli)
+ }
+ }
+}
+
+class PacketCodingActorHTest extends ActorTest {
+ "PacketCodingActor" should {
+ "unwind l-originating hexadecimal data into two r-facing packets (SlottedMetaPacket/MultiPacketEx -> 2 ObjectDeleteMessage)" in {
+ val string_hex = RawPacket(hex"00 09 0A E1 00 19 04 19 4F 04 40 04 19 51 04 40")
+ val string_obj1 = GamePacket(GamePacketOpcode.ObjectDeleteMessage, 0, ObjectDeleteMessage(PlanetSideGUID(1103), 2))
+ val string_obj2 = GamePacket(GamePacketOpcode.ObjectDeleteMessage, 0, ObjectDeleteMessage(PlanetSideGUID(1105), 2))
+
+ val probe1 = TestProbe()
+ val probe2 = system.actorOf(Props(classOf[ActorTest.MDCTestProbe], probe1), "mdc-probe")
+ val pca : ActorRef = system.actorOf(Props[PacketCodingActor], "pca")
+ pca ! HelloFriend(135, List(probe2).iterator)
+ probe1.receiveOne(100 milli) //consume
+
+ pca ! string_hex
+ val reply = probe1.receiveN(2, 200 milli)
+ assert(reply.head == string_obj1)
+ assert(reply(1) == string_obj2)
+ probe1.expectNoMsg(100 milli)
+ }
+ }
+}
+
+class PacketCodingActorITest extends ActorTest {
+ "PacketCodingActor" should {
+ "bundle an r-originating packet into an l-facing SlottedMetaPacket byte stream data (SlottedMetaPacket)" in {
+ import net.psforever.packet.game.objectcreate._
+ val obj = DetailedCharacterData(
+ CharacterAppearanceData(
+ PlacementData(Vector3.Zero, Vector3.Zero),
+ BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1),
+ 3,
+ false,
+ false,
+ ExoSuitType.Standard,
+ "",
+ 0,
+ false,
+ 2.8125f, 210.9375f,
+ true,
+ GrenadeState.None,
+ false,
+ false,
+ false,
+ RibbonBars()
+ ),
+ 0,
+ 0,
+ 100, 100,
+ 50,
+ 1, 7, 7,
+ 100, 100,
+ List(CertificationType.StandardAssault,CertificationType.MediumAssault,CertificationType.ATV,CertificationType.Harasser,CertificationType.StandardExoSuit,CertificationType.AgileExoSuit,CertificationType.ReinforcedExoSuit),
+ List(),
+ List(),
+ List.empty,
+ None,
+ Some(InventoryData(Nil)),
+ DrawnSlot.None
+ )
+ val pkt = MultiPacketBundle(List(ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj)))
+ val string_hex = hex"000900001879060000bc84b000000000000000000002040000097049006c006c006c004900490049006c006c006c0049006c0049006c006c0049006c006c006c0049006c006c0049008452700000000000000000000000000000002000000fe6a703fffffffffffffffffffffffffffffffc00000000000000000000000000000000000000019001900064000001007ec800c80000000000000000000000000000000000000001c00042c54686c7000000000000000000000000000000000000000000000000000000000000000000000000200700"
+
+ val probe1 = TestProbe()
+ val probe2 = system.actorOf(Props(classOf[ActorTest.MDCTestProbe], probe1), "mdc-probe")
+ val pca : ActorRef = system.actorOf(Props[PacketCodingActor], "pca")
+ pca ! HelloFriend(135, List(probe2).iterator)
+ probe1.receiveOne(100 milli) //consume
+
+ probe2 ! pkt
+ val reply1 = receiveN(1, 200 milli) //we get a MdcMsg message back
+ probe1.receiveN(1, 200 milli) //flush contents
+ probe2 ! reply1.head //by feeding the MdcMsg into the actor, we get normal output on the probe
+ probe1.receiveOne(100 milli) match {
+ case RawPacket(data) =>
+ assert(data == string_hex)
+ PacketCoding.DecodePacket(data).require match {
+ case _ : SlottedMetaPacket =>
+ assert(true)
+ case _ =>
+ assert(false)
+ }
+ case e =>
+ assert(false)
+ }
+ }
+ }
+}
+
+class PacketCodingActorJTest extends ActorTest {
+ "PacketCodingActor" should {
+ "bundle r-originating packets into a number of MTU-acceptable l-facing byte streams (1 packets into 1)" in {
+ val pkt = MultiPacketBundle(
+ List(ObjectDeleteMessage(PlanetSideGUID(1103), 2), ObjectDeleteMessage(PlanetSideGUID(1105), 2), ObjectDeleteMessage(PlanetSideGUID(1107), 2))
+ )
+ val string_hex = hex"00090000001904194f044004195104400419530440"
+
+ val probe1 = TestProbe()
+ val probe2 = system.actorOf(Props(classOf[ActorTest.MDCTestProbe], probe1), "mdc-probe")
+ val pca : ActorRef = system.actorOf(Props[PacketCodingActor], "pca")
+ pca ! HelloFriend(135, List(probe2).iterator)
+ probe1.receiveOne(100 milli) //consume
+
+ probe2 ! pkt
+ val reply1 = receiveN(1, 200 milli) //we get a MdcMsg message back
+ probe1.receiveN(1, 200 milli) //flush contents
+ probe2 ! reply1.head //by feeding the MdcMsg into the actor, we get normal output on the probe
+ probe1.receiveOne(100 milli) match {
+ case RawPacket(data) =>
+ assert(data == string_hex)
+ case e =>
+ assert(false)
+ }
+ }
+ }
+}
+
+class PacketCodingActorKTest extends ActorTest {
+ import net.psforever.packet.game.objectcreate._
+ val obj = DetailedCharacterData(
+ CharacterAppearanceData(
+ PlacementData(Vector3.Zero, Vector3.Zero),
+ BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1),
+ 3,
+ false,
+ false,
+ ExoSuitType.Standard,
+ "",
+ 0,
+ false,
+ 2.8125f, 210.9375f,
+ true,
+ GrenadeState.None,
+ false,
+ false,
+ false,
+ RibbonBars()
+ ),
+ 0,
+ 0,
+ 100, 100,
+ 50,
+ 1, 7, 7,
+ 100, 100,
+ List(CertificationType.StandardAssault, CertificationType.MediumAssault, CertificationType.ATV, CertificationType.Harasser, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit, CertificationType.ReinforcedExoSuit),
+ List(),
+ List("xpe_sanctuary_help", "xpe_th_firemodes", "used_beamer", "map13"),
+ List.empty,
+ None,
+ Some(InventoryData(Nil)),
+ DrawnSlot.None
+ )
+ val list = List(
+ ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj),
+ ObjectDeleteMessage(PlanetSideGUID(1103), 2),
+ ObjectDeleteMessage(PlanetSideGUID(1105), 2),
+ ObjectCreateDetailedMessage(0x79, PlanetSideGUID(175), obj),
+ ObjectCreateDetailedMessage(0x79, PlanetSideGUID(275), obj),
+ ObjectDeleteMessage(PlanetSideGUID(1107), 2)
+ )
+
+ "PacketCodingActor" should {
+ "bundle r-originating packets into a number of MTU-acceptable l-facing byte streams (6 packets into 2)" in {
+ val pkt = MultiPacketBundle(list)
+
+ val probe1 = TestProbe()
+ val probe2 = system.actorOf(Props(classOf[ActorTest.MDCTestProbe], probe1), "mdc-probe")
+ val pca : ActorRef = system.actorOf(Props[PacketCodingActor], "pca")
+ pca ! HelloFriend(135, List(probe2).iterator)
+ probe1.receiveOne(100 milli) //consume
+
+ probe2 ! pkt
+ val reply1 = receiveN(2, 200 milli)
+ probe1.receiveN(1, 200 milli) //flush contents
+ probe2 ! reply1.head //by feeding the MdcMsg into the actor, we get normal output on the probe
+ val reply3 = probe1.receiveOne(100 milli).asInstanceOf[RawPacket]
+
+ pca ! reply3 //reconstruct original three packets from the first bundle
+ val reply4 = probe1.receiveN(3, 200 milli)
+ var i = 0
+ reply4.foreach{
+ case GamePacket(_, _, packet) =>
+ assert(packet == list(i))
+ i += 1
+ case _ =>
+ assert(false)
+ }
+ }
+ }
+}
+
+class PacketCodingActorLTest extends ActorTest {
+ val string_obj = PropertyOverrideMessage(
+ List(
+ GamePropertyScope(0,
+ GamePropertyTarget(GamePropertyTarget.game_properties, List(
+ "purchase_exempt_vs" -> "",
+ "purchase_exempt_tr" -> "",
+ "purchase_exempt_nc" -> ""
+ )
+ )),
+ GamePropertyScope(17,
+ GamePropertyTarget(ObjectClass.katana, "allowed" -> "false")
+ ),
+ GamePropertyScope(18,
+ GamePropertyTarget(ObjectClass.katana, "allowed" -> "false")
+ ),
+ GamePropertyScope(19,
+ GamePropertyTarget(ObjectClass.katana, "allowed" -> "false")
+ ),
+ GamePropertyScope(20,
+ GamePropertyTarget(ObjectClass.katana, "allowed" -> "false")
+ ),
+ GamePropertyScope(21,
+ GamePropertyTarget(ObjectClass.katana, "allowed" -> "false")
+ ),
+ GamePropertyScope(22,
+ GamePropertyTarget(ObjectClass.katana, "allowed" -> "false")
+ ),
+ GamePropertyScope(29, List(
+ GamePropertyTarget(ObjectClass.aphelion_flight, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.aphelion_gunner, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.aurora, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.battlewagon, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.colossus_flight, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.colossus_gunner, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.flail, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.galaxy_gunship, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.lasher, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.liberator, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.lightgunship, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.maelstrom, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.magrider, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.mini_chaingun, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.peregrine_flight, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.peregrine_gunner, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.prowler, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.r_shotgun, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.thunderer, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.vanguard, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.vulture, "allowed" -> "false")
+ )),
+ GamePropertyScope(30, List(
+ GamePropertyTarget(ObjectClass.aphelion_flight, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.aphelion_gunner, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.aurora, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.battlewagon, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.colossus_flight, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.colossus_gunner, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.flail, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.galaxy_gunship, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.lasher, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.liberator, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.lightgunship, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.maelstrom, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.magrider, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.mini_chaingun, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.peregrine_flight, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.peregrine_gunner, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.prowler, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.r_shotgun, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.thunderer, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.vanguard, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.vulture, "allowed" -> "false")
+ )),
+ GamePropertyScope(31, List(
+ GamePropertyTarget(ObjectClass.aphelion_flight, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.aphelion_gunner, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.aurora, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.battlewagon, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.colossus_flight, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.colossus_gunner, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.flail, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.galaxy_gunship, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.lasher, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.liberator, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.lightgunship, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.maelstrom, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.magrider, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.mini_chaingun, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.peregrine_flight, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.peregrine_gunner, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.prowler, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.r_shotgun, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.thunderer, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.vanguard, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.vulture, "allowed" -> "false")
+ )),
+ GamePropertyScope(32, List(
+ GamePropertyTarget(ObjectClass.aphelion_flight, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.aphelion_gunner, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.aurora, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.battlewagon, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.colossus_flight, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.colossus_gunner, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.flail, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.galaxy_gunship, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.lasher, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.liberator, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.lightgunship, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.maelstrom, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.magrider, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.mini_chaingun, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.peregrine_flight, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.peregrine_gunner, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.prowler, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.r_shotgun, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.thunderer, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.vanguard, "allowed" -> "false"),
+ GamePropertyTarget(ObjectClass.vulture, "allowed" -> "false")
+ ))
+ )
+ )
+
+ "PacketCodingActor" should {
+ "split, rather than bundle, r-originating packets into a number of MTU-acceptable l-facing byte streams" in {
+ val probe1 = TestProbe()
+ val probe2 = system.actorOf(Props(classOf[ActorTest.MDCTestProbe], probe1), "mdc-probe")
+ val pca : ActorRef = system.actorOf(Props[PacketCodingActor], "pca")
+ pca ! HelloFriend(135, List(probe2).iterator)
+ probe1.receiveOne(100 milli) //consume
+
+ val msg = MultiPacketBundle(List(string_obj))
+ pca ! msg
+ receiveN(4)
+ }
+ }
+}
+
object PacketCodingActorTest {
//decoy
}