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 }