mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-07-13 07:24:53 +00:00
commit
fc3837268c
15 changed files with 1156 additions and 268 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.packet
|
package net.psforever.packet
|
||||||
|
|
||||||
import net.psforever.packet.control.SlottedMetaPacket
|
import net.psforever.packet.control.{RelatedA, RelatedB, SlottedMetaPacket}
|
||||||
import scodec.bits.BitVector
|
import scodec.bits.BitVector
|
||||||
import scodec.{Attempt, Codec, DecodeResult, Err}
|
import scodec.{Attempt, Codec, DecodeResult, Err}
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
|
|
@ -74,15 +74,15 @@ object ControlPacketOpcode extends Enumeration {
|
||||||
|
|
||||||
// OPCODES 0x10-1e
|
// OPCODES 0x10-1e
|
||||||
case 0x10 => SlottedMetaPacket.decodeWithOpcode(SlottedMetaPacket7)
|
case 0x10 => SlottedMetaPacket.decodeWithOpcode(SlottedMetaPacket7)
|
||||||
case 0x11 => control.RelatedA0.decode
|
case 0x11 => RelatedA.decodeWithOpcode(RelatedA0)
|
||||||
case 0x12 => noDecoder(RelatedA1)
|
case 0x12 => RelatedA.decodeWithOpcode(RelatedA1)
|
||||||
case 0x13 => noDecoder(RelatedA2)
|
case 0x13 => RelatedA.decodeWithOpcode(RelatedA2)
|
||||||
case 0x14 => noDecoder(RelatedA3)
|
case 0x14 => RelatedA.decodeWithOpcode(RelatedA3)
|
||||||
case 0x15 => control.RelatedB0.decode
|
case 0x15 => RelatedB.decodeWithOpcode(RelatedB0)
|
||||||
case 0x16 => noDecoder(RelatedB1)
|
case 0x16 => RelatedB.decodeWithOpcode(RelatedB1)
|
||||||
case 0x17 => noDecoder(RelatedB2)
|
case 0x17 => RelatedB.decodeWithOpcode(RelatedB2)
|
||||||
// 0x18
|
// 0x18
|
||||||
case 0x18 => noDecoder(RelatedB3)
|
case 0x18 => RelatedB.decodeWithOpcode(RelatedB3)
|
||||||
case 0x19 => control.MultiPacketEx.decode
|
case 0x19 => control.MultiPacketEx.decode
|
||||||
case 0x1a => noDecoder(Unknown26)
|
case 0x1a => noDecoder(Unknown26)
|
||||||
case 0x1b => noDecoder(Unknown27)
|
case 0x1b => noDecoder(Unknown27)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
// Copyright (c) 2017 PSForever
|
||||||
|
package net.psforever.packet.control
|
||||||
|
|
||||||
|
import net.psforever.packet.PlanetSidePacket
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message for holding a series of packets being moved through the system (server),
|
||||||
|
* eventually be bundled into a `MultiPacketEx` and dispatched to the client.
|
||||||
|
* Invalid packets are eliminated at the time of creation.
|
||||||
|
* At least one packet is necessary.
|
||||||
|
* @param packets a series of packets to be bundled together;
|
||||||
|
* this list is effectively immutable;
|
||||||
|
* the only way to access these packets is through pattern matching
|
||||||
|
*/
|
||||||
|
final case class MultiPacketBundle(private var packets : List[PlanetSidePacket]) {
|
||||||
|
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 _ =>
|
||||||
|
MultiPacketBundle(packets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object MultiPacketBundle {
|
||||||
|
/**
|
||||||
|
* Accept a series of packets of a specific supertype (`PlanetSidePacket`)
|
||||||
|
* and filter out subtypes that should be excluded.
|
||||||
|
* Show a generic disclaimer if any packets were filtered.
|
||||||
|
* Two of the four subclasses of `PlanetSidePacket` are accepted - `PlanetSideGamePacket` and `PlanetSideControlPacket`.
|
||||||
|
* @param packets a series of packets
|
||||||
|
* @return the accepted packets from the original group
|
||||||
|
*/
|
||||||
|
def collectValidPackets(packets : List[PlanetSidePacket]) : List[PlanetSidePacket] = {
|
||||||
|
import net.psforever.packet.{PlanetSideGamePacket, PlanetSideControlPacket}
|
||||||
|
val (good, bad) = packets.partition( {
|
||||||
|
case _ : PlanetSideGamePacket => true
|
||||||
|
case _ : PlanetSideControlPacket => true
|
||||||
|
case _ => false
|
||||||
|
})
|
||||||
|
if(bad.nonEmpty) {
|
||||||
|
org.log4s.getLogger("MultiPacketBundle")
|
||||||
|
.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.
|
||||||
|
*/
|
||||||
|
class MultiPacketCollector() {
|
||||||
|
private var bundle : List[PlanetSidePacket] = List.empty
|
||||||
|
|
||||||
|
def Add(t : PlanetSidePacket) : Unit = Add(List(t))
|
||||||
|
|
||||||
|
def Add(t : MultiPacketBundle) : Unit = t match {
|
||||||
|
case MultiPacketBundle(list) =>
|
||||||
|
Add(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
def Add(t : List[PlanetSidePacket]) : Unit = {
|
||||||
|
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 = {
|
||||||
|
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.
|
||||||
|
* @see `Bundle`
|
||||||
|
* @return a loaded `MultiPacketBundle` object, or `None`
|
||||||
|
*/
|
||||||
|
def BundleOption : Option[MultiPacketBundle] = {
|
||||||
|
try {
|
||||||
|
Some(Bundle)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
case _ : Exception =>
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object MultiPacketCollector {
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
def apply(packets : List[PlanetSidePacket]) : MultiPacketCollector = {
|
||||||
|
val obj = new MultiPacketCollector()
|
||||||
|
obj.Add(packets)
|
||||||
|
obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright (c) 2017 PSForever
|
||||||
|
package net.psforever.packet.control
|
||||||
|
|
||||||
|
import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideControlPacket}
|
||||||
|
import scodec.Codec
|
||||||
|
import scodec.bits.BitVector
|
||||||
|
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 slot the type of `ResultA` packet;
|
||||||
|
* valid types are integers 0-3
|
||||||
|
* @param subslot identification of a control packet
|
||||||
|
*/
|
||||||
|
final case class RelatedA(slot : Int, subslot : Int) extends PlanetSideControlPacket {
|
||||||
|
type Packet = RelatedA
|
||||||
|
if(slot < 0 || slot > 3) {
|
||||||
|
throw new IllegalArgumentException(s"slot number is out of range - $slot")
|
||||||
|
}
|
||||||
|
|
||||||
|
def opcode = {
|
||||||
|
val base = ControlPacketOpcode.RelatedA0.id
|
||||||
|
ControlPacketOpcode(base + slot)
|
||||||
|
}
|
||||||
|
def encode = RelatedA.encode(this).map(vect => vect.drop(8))
|
||||||
|
}
|
||||||
|
|
||||||
|
object RelatedA extends Marshallable[RelatedA] {
|
||||||
|
implicit val codec : Codec[RelatedA] = (
|
||||||
|
("slot" | uint8L.xmap[Int](a => a - ControlPacketOpcode.RelatedA0.id, a=>a) ) ::
|
||||||
|
("subslot" | uint16) // the slot is big endian. see 0x00A42F76
|
||||||
|
).as[RelatedA]
|
||||||
|
|
||||||
|
def decodeWithOpcode(slot : ControlPacketOpcode.Value)(bits : BitVector) = {
|
||||||
|
decode(ControlPacketOpcode.codec.encode(slot).require ++ bits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
// 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]
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright (c) 2017 PSForever
|
||||||
|
package net.psforever.packet.control
|
||||||
|
|
||||||
|
import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideControlPacket}
|
||||||
|
import scodec.Codec
|
||||||
|
import scodec.bits.BitVector
|
||||||
|
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 slot the type of `ResultB` packet;
|
||||||
|
* valid types are integers 0-3
|
||||||
|
* @param subslot identification of a control packet
|
||||||
|
*/
|
||||||
|
final case class RelatedB(slot : Int, subslot : Int) extends PlanetSideControlPacket {
|
||||||
|
type Packet = RelatedB
|
||||||
|
if(slot < 0 || slot > 3) {
|
||||||
|
throw new IllegalArgumentException(s"slot number is out of range - $slot")
|
||||||
|
}
|
||||||
|
|
||||||
|
def opcode = {
|
||||||
|
val base = ControlPacketOpcode.RelatedB0.id
|
||||||
|
ControlPacketOpcode(base + slot)
|
||||||
|
}
|
||||||
|
def encode = RelatedB.encode(this).map(vect => vect.drop(8))
|
||||||
|
}
|
||||||
|
|
||||||
|
object RelatedB extends Marshallable[RelatedB] {
|
||||||
|
implicit val codec : Codec[RelatedB] = (
|
||||||
|
("slot" | uint8L.xmap[Int](a => a - ControlPacketOpcode.RelatedB0.id, a=>a) ) ::
|
||||||
|
("subslot" | uint16) // the slot is big endian. see 0x00A42F76
|
||||||
|
).as[RelatedB]
|
||||||
|
|
||||||
|
def decodeWithOpcode(slot : ControlPacketOpcode.Value)(bits : BitVector) = {
|
||||||
|
decode(ControlPacketOpcode.codec.encode(slot).require ++ bits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
// 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]
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
package net.psforever.packet.control
|
|
||||||
|
|
||||||
import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideControlPacket}
|
|
||||||
import scodec.Codec
|
|
||||||
import scodec.bits.BitVector
|
|
||||||
import scodec.codecs._
|
|
||||||
|
|
||||||
final case class SlottedMetaAck(slot : Int, subslot : Int)
|
|
||||||
extends PlanetSideControlPacket {
|
|
||||||
type Packet = SlottedMetaAck
|
|
||||||
|
|
||||||
assert(slot >= 0 && slot <= 7, s"Slot number ($slot) is out of range") //TODO 7 types of SlottedMeta, 4 types of ResultB?
|
|
||||||
|
|
||||||
def opcode = {
|
|
||||||
val base = ControlPacketOpcode.RelatedB0.id
|
|
||||||
ControlPacketOpcode(base + slot % 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX: a nasty hack to ignore the "slot" field
|
|
||||||
// There is so much wrong with this it's not even funny. Why scodec, whyyyy...
|
|
||||||
// I've never had a library make me feel so stupid and smart at the same time
|
|
||||||
def encode = SlottedMetaAck.encode(this).map(vect => vect.drop(8))
|
|
||||||
}
|
|
||||||
|
|
||||||
object SlottedMetaAck extends Marshallable[SlottedMetaAck] {
|
|
||||||
implicit val codec : Codec[SlottedMetaAck] = (
|
|
||||||
("slot" | uint8L.xmap[Int](a => a - ControlPacketOpcode.RelatedB0.id, a=>a) ) ::
|
|
||||||
("subslot" | uint16)
|
|
||||||
).as[SlottedMetaAck]
|
|
||||||
|
|
||||||
def decodeWithOpcode(slot : ControlPacketOpcode.Value)(bits : BitVector) = {
|
|
||||||
decode(ControlPacketOpcode.codec.encode(slot).require ++ bits)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
183
common/src/test/scala/control/MultiPacketCollectorTest.scala
Normal file
183
common/src/test/scala/control/MultiPacketCollectorTest.scala
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,24 +3,80 @@ package control
|
||||||
|
|
||||||
import org.specs2.mutable._
|
import org.specs2.mutable._
|
||||||
import net.psforever.packet._
|
import net.psforever.packet._
|
||||||
import net.psforever.packet.control._
|
import net.psforever.packet.control.RelatedA
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
|
|
||||||
class RelatedATest extends Specification {
|
class RelatedATest extends Specification {
|
||||||
val string0 = hex"00 11 01 04"
|
val string0 = hex"00 11 01 04"
|
||||||
|
val string1 = hex"00 12 01 04"
|
||||||
|
val string2 = hex"00 13 01 04"
|
||||||
|
val string3 = hex"00 14 01 04"
|
||||||
|
|
||||||
"decode (0)" in {
|
"decode (0)" in {
|
||||||
PacketCoding.DecodePacket(string0).require match {
|
PacketCoding.DecodePacket(string0).require match {
|
||||||
case RelatedA0(slot) =>
|
case RelatedA(slot, subslot) =>
|
||||||
slot mustEqual 260
|
slot mustEqual 0
|
||||||
|
subslot mustEqual 260
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"decode (1)" in {
|
||||||
|
PacketCoding.DecodePacket(string1).require match {
|
||||||
|
case RelatedA(slot, subslot) =>
|
||||||
|
slot mustEqual 1
|
||||||
|
subslot mustEqual 260
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"decode (2)" in {
|
||||||
|
PacketCoding.DecodePacket(string2).require match {
|
||||||
|
case RelatedA(slot, subslot) =>
|
||||||
|
slot mustEqual 2
|
||||||
|
subslot mustEqual 260
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"decode (3)" in {
|
||||||
|
PacketCoding.DecodePacket(string3).require match {
|
||||||
|
case RelatedA(slot, subslot) =>
|
||||||
|
slot mustEqual 3
|
||||||
|
subslot mustEqual 260
|
||||||
case _ =>
|
case _ =>
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"encode (0)" in {
|
"encode (0)" in {
|
||||||
val pkt = RelatedA0(260)
|
val pkt = RelatedA(0, 260)
|
||||||
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||||
msg mustEqual string0
|
msg mustEqual string0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"encode (1)" in {
|
||||||
|
val pkt = RelatedA(1, 260)
|
||||||
|
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||||
|
msg mustEqual string1
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode (2)" in {
|
||||||
|
val pkt = RelatedA(2, 260)
|
||||||
|
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||||
|
msg mustEqual string2
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode (3)" in {
|
||||||
|
val pkt = RelatedA(3, 260)
|
||||||
|
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||||
|
msg mustEqual string3
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode (n)" in {
|
||||||
|
RelatedA(4, 260) must throwA[IllegalArgumentException]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,80 @@ package control
|
||||||
|
|
||||||
import org.specs2.mutable._
|
import org.specs2.mutable._
|
||||||
import net.psforever.packet._
|
import net.psforever.packet._
|
||||||
import net.psforever.packet.control._
|
import net.psforever.packet.control.RelatedB
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
|
|
||||||
class RelatedBTest extends Specification {
|
class RelatedBTest extends Specification {
|
||||||
val string0 = hex"00 15 01 04"
|
val string0 = hex"00 15 01 04"
|
||||||
|
val string1 = hex"00 16 01 04"
|
||||||
|
val string2 = hex"00 17 01 04"
|
||||||
|
val string3 = hex"00 18 01 04"
|
||||||
|
|
||||||
"decode (0)" in {
|
"decode (0)" in {
|
||||||
PacketCoding.DecodePacket(string0).require match {
|
PacketCoding.DecodePacket(string0).require match {
|
||||||
case RelatedB0(slot) =>
|
case RelatedB(slot, subslot) =>
|
||||||
slot mustEqual 260
|
slot mustEqual 0
|
||||||
|
subslot mustEqual 260
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"decode (1)" in {
|
||||||
|
PacketCoding.DecodePacket(string1).require match {
|
||||||
|
case RelatedB(slot, subslot) =>
|
||||||
|
slot mustEqual 1
|
||||||
|
subslot mustEqual 260
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"decode (2)" in {
|
||||||
|
PacketCoding.DecodePacket(string2).require match {
|
||||||
|
case RelatedB(slot, subslot) =>
|
||||||
|
slot mustEqual 2
|
||||||
|
subslot mustEqual 260
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"decode (3)" in {
|
||||||
|
PacketCoding.DecodePacket(string3).require match {
|
||||||
|
case RelatedB(slot, subslot) =>
|
||||||
|
slot mustEqual 3
|
||||||
|
subslot mustEqual 260
|
||||||
case _ =>
|
case _ =>
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"encode (0)" in {
|
"encode (0)" in {
|
||||||
val pkt = RelatedB0(260)
|
val pkt = RelatedB(0, 260)
|
||||||
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||||
msg mustEqual string0
|
msg mustEqual string0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"encode (1)" in {
|
||||||
|
val pkt = RelatedB(1, 260)
|
||||||
|
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||||
|
msg mustEqual string1
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode (2)" in {
|
||||||
|
val pkt = RelatedB(2, 260)
|
||||||
|
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||||
|
msg mustEqual string2
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode (3)" in {
|
||||||
|
val pkt = RelatedB(3, 260)
|
||||||
|
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||||
|
msg mustEqual string3
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode (n)" in {
|
||||||
|
RelatedB(4, 260) must throwA[IllegalArgumentException]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
package control
|
|
||||||
|
|
||||||
import org.specs2.mutable._
|
|
||||||
import net.psforever.packet._
|
|
||||||
import net.psforever.packet.control._
|
|
||||||
import scodec.bits._
|
|
||||||
|
|
||||||
class SlottedMetaAckTest extends Specification {
|
|
||||||
val string = hex"00150da4"
|
|
||||||
|
|
||||||
"decode" in {
|
|
||||||
PacketCoding.DecodePacket(string).require match {
|
|
||||||
case SlottedMetaAck(_, _) =>
|
|
||||||
ko
|
|
||||||
case RelatedB0(subslot) => //important!
|
|
||||||
subslot mustEqual 3492
|
|
||||||
case _ =>
|
|
||||||
ko
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"encode" in {
|
|
||||||
val pkt = SlottedMetaAck(0, 3492)
|
|
||||||
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
|
||||||
|
|
||||||
msg mustEqual string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10,6 +10,7 @@ import scodec.bits._
|
||||||
import MDCContextAware.Implicits._
|
import MDCContextAware.Implicits._
|
||||||
import com.github.mauricio.async.db.{Connection, QueryResult, RowData}
|
import com.github.mauricio.async.db.{Connection, QueryResult, RowData}
|
||||||
import com.github.mauricio.async.db.mysql.exceptions.MySQLException
|
import com.github.mauricio.async.db.mysql.exceptions.MySQLException
|
||||||
|
import net.psforever.objects.DefaultCancellable
|
||||||
import net.psforever.types.PlanetSideEmpire
|
import net.psforever.types.PlanetSideEmpire
|
||||||
|
|
||||||
import scala.concurrent.{Await, Future}
|
import scala.concurrent.{Await, Future}
|
||||||
|
|
@ -25,7 +26,7 @@ class LoginSessionActor extends Actor with MDCContextAware {
|
||||||
var leftRef : ActorRef = ActorRef.noSender
|
var leftRef : ActorRef = ActorRef.noSender
|
||||||
var rightRef : ActorRef = ActorRef.noSender
|
var rightRef : ActorRef = ActorRef.noSender
|
||||||
|
|
||||||
var updateServerListTask : Cancellable = LoginSessionActor.DefaultCancellable
|
var updateServerListTask : Cancellable = DefaultCancellable.obj
|
||||||
|
|
||||||
override def postStop() = {
|
override def postStop() = {
|
||||||
if(updateServerListTask != null)
|
if(updateServerListTask != null)
|
||||||
|
|
@ -54,59 +55,22 @@ class LoginSessionActor extends Actor with MDCContextAware {
|
||||||
def Started : Receive = {
|
def Started : Receive = {
|
||||||
case UpdateServerList() =>
|
case UpdateServerList() =>
|
||||||
updateServerList()
|
updateServerList()
|
||||||
case ctrl @ ControlPacket(_, _) =>
|
case ControlPacket(_, ctrl) =>
|
||||||
handlePktContainer(ctrl)
|
|
||||||
case game @ GamePacket(_, _, _) =>
|
|
||||||
handlePktContainer(game)
|
|
||||||
case default => failWithError(s"Invalid packet class received: $default")
|
|
||||||
}
|
|
||||||
|
|
||||||
def handlePkt(pkt : PlanetSidePacket) : Unit = pkt match {
|
|
||||||
case ctrl : PlanetSideControlPacket =>
|
|
||||||
handleControlPkt(ctrl)
|
handleControlPkt(ctrl)
|
||||||
case game : PlanetSideGamePacket =>
|
case GamePacket(_, _, game) =>
|
||||||
handleGamePkt(game)
|
handleGamePkt(game)
|
||||||
case default => failWithError(s"Invalid packet class received: $default")
|
case default => failWithError(s"Invalid packet class received: $default")
|
||||||
}
|
}
|
||||||
|
|
||||||
def handlePktContainer(pkt : PlanetSidePacketContainer) : Unit = pkt match {
|
|
||||||
case ctrl @ ControlPacket(opcode, ctrlPkt) =>
|
|
||||||
handleControlPkt(ctrlPkt)
|
|
||||||
case game @ GamePacket(opcode, seq, gamePkt) =>
|
|
||||||
handleGamePkt(gamePkt)
|
|
||||||
case default => failWithError(s"Invalid packet container class received: $default")
|
|
||||||
}
|
|
||||||
|
|
||||||
def handleControlPkt(pkt : PlanetSideControlPacket) = {
|
def handleControlPkt(pkt : PlanetSideControlPacket) = {
|
||||||
pkt match {
|
pkt match {
|
||||||
case SlottedMetaPacket(slot, subslot, innerPacket) =>
|
|
||||||
// Meta packets are like TCP packets - then need to be ACKed to the client
|
|
||||||
sendResponse(PacketCoding.CreateControlPacket(SlottedMetaAck(slot, subslot)))
|
|
||||||
|
|
||||||
// Decode the inner packet and handle it or error
|
|
||||||
PacketCoding.DecodePacket(innerPacket).fold({
|
|
||||||
error => log.error(s"Failed to decode inner packet of SlottedMetaPacket: $error")
|
|
||||||
}, {
|
|
||||||
handlePkt(_)
|
|
||||||
})
|
|
||||||
/// TODO: figure out what this is what what it does for the PS client
|
/// 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
|
/// I believe it has something to do with reliable packet transmission and resending
|
||||||
case sync @ ControlSync(diff, unk, f1, f2, f3, f4, fa, fb) =>
|
case sync @ ControlSync(diff, _, _, _, _, _, 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
|
val serverTick = Math.abs(System.nanoTime().toInt) // limit the size to prevent encoding error
|
||||||
sendResponse(PacketCoding.CreateControlPacket(ControlSyncResp(diff, serverTick,
|
sendResponse(PacketCoding.CreateControlPacket(ControlSyncResp(diff, serverTick, fa, fb, fb, fa)))
|
||||||
fa, fb, fb, fa)))
|
|
||||||
case MultiPacket(packets) =>
|
|
||||||
|
|
||||||
/// Extract out each of the subpackets in the MultiPacket and handle them or raise a packet error
|
|
||||||
packets.foreach { pkt =>
|
|
||||||
PacketCoding.DecodePacket(pkt).fold({ error =>
|
|
||||||
log.error(s"Failed to decode inner packet of MultiPacket: $error")
|
|
||||||
}, {
|
|
||||||
handlePkt(_)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
case default =>
|
case default =>
|
||||||
log.error(s"Unhandled ControlPacket $default")
|
log.error(s"Unhandled ControlPacket $default")
|
||||||
}
|
}
|
||||||
|
|
@ -119,7 +83,6 @@ class LoginSessionActor extends Actor with MDCContextAware {
|
||||||
// TESTING CODE FOR ACCOUNT LOOKUP
|
// TESTING CODE FOR ACCOUNT LOOKUP
|
||||||
def accountLookup(username : String, password : String) : Boolean = {
|
def accountLookup(username : String, password : String) : Boolean = {
|
||||||
val connection: Connection = DatabaseConnector.getAccountsConnection
|
val connection: Connection = DatabaseConnector.getAccountsConnection
|
||||||
|
|
||||||
Await.result(connection.connect, 5 seconds)
|
Await.result(connection.connect, 5 seconds)
|
||||||
|
|
||||||
// create account
|
// create account
|
||||||
|
|
@ -135,13 +98,13 @@ class LoginSessionActor extends Actor with MDCContextAware {
|
||||||
case Some(resultSet) =>
|
case Some(resultSet) =>
|
||||||
val row : RowData = resultSet.head
|
val row : RowData = resultSet.head
|
||||||
row(0)
|
row(0)
|
||||||
case None => -1
|
case None =>
|
||||||
}
|
-1
|
||||||
)
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// XXX: remove awaits
|
// XXX: remove awaits
|
||||||
val result = Await.result( mapResult, 5 seconds )
|
Await.result( mapResult, 5 seconds )
|
||||||
return true
|
return true
|
||||||
} catch {
|
} catch {
|
||||||
case e : MySQLException =>
|
case e : MySQLException =>
|
||||||
|
|
@ -151,7 +114,6 @@ class LoginSessionActor extends Actor with MDCContextAware {
|
||||||
} finally {
|
} finally {
|
||||||
connection.disconnect
|
connection.disconnect
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -190,22 +152,25 @@ class LoginSessionActor extends Actor with MDCContextAware {
|
||||||
log.info(s"Failed login to account $username")
|
log.info(s"Failed login to account $username")
|
||||||
sendResponse(PacketCoding.CreateGamePacket(0, response))
|
sendResponse(PacketCoding.CreateGamePacket(0, response))
|
||||||
}
|
}
|
||||||
|
|
||||||
case ConnectToWorldRequestMessage(name, _, _, _, _, _, _) =>
|
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)
|
val response = ConnectToWorldMessage(serverName, serverAddress.getHostString, serverAddress.getPort)
|
||||||
sendResponse(PacketCoding.CreateGamePacket(0, response))
|
sendResponse(PacketCoding.CreateGamePacket(0, response))
|
||||||
sendResponse(DropSession(sessionId, "user transferring to world"))
|
sendResponse(DropSession(sessionId, "user transferring to world"))
|
||||||
case default => log.debug(s"Unhandled GamePacket $pkt")
|
|
||||||
|
case _ =>
|
||||||
|
log.debug(s"Unhandled GamePacket $pkt")
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateServerList() = {
|
def updateServerList() = {
|
||||||
val msg = VNLWorldStatusMessage("Welcome to PlanetSide! ",
|
val msg = VNLWorldStatusMessage("Welcome to PlanetSide! ",
|
||||||
Vector(
|
Vector(
|
||||||
WorldInformation(serverName, WorldStatus.Up, ServerType.Beta,
|
WorldInformation(
|
||||||
Vector(WorldConnectionInfo(serverAddress)), PlanetSideEmpire.VS)
|
serverName, WorldStatus.Up, ServerType.Beta, Vector(WorldConnectionInfo(serverAddress)), PlanetSideEmpire.VS
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
sendResponse(PacketCoding.CreateGamePacket(0, msg))
|
sendResponse(PacketCoding.CreateGamePacket(0, msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,22 +181,13 @@ class LoginSessionActor extends Actor with MDCContextAware {
|
||||||
|
|
||||||
def sendResponse(cont : Any) = {
|
def sendResponse(cont : Any) = {
|
||||||
log.trace("LOGIN SEND: " + cont)
|
log.trace("LOGIN SEND: " + cont)
|
||||||
|
|
||||||
MDC("sessionId") = sessionId.toString
|
MDC("sessionId") = sessionId.toString
|
||||||
rightRef !> cont
|
rightRef !> cont
|
||||||
}
|
}
|
||||||
|
|
||||||
def sendRawResponse(pkt : ByteVector) = {
|
def sendRawResponse(pkt : ByteVector) = {
|
||||||
log.trace("LOGIN SEND RAW: " + pkt)
|
log.trace("LOGIN SEND RAW: " + pkt)
|
||||||
|
|
||||||
MDC("sessionId") = sessionId.toString
|
MDC("sessionId") = sessionId.toString
|
||||||
rightRef !> RawPacket(pkt)
|
rightRef !> RawPacket(pkt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object LoginSessionActor {
|
|
||||||
final val DefaultCancellable = new Cancellable() {
|
|
||||||
def isCancelled : Boolean = true
|
|
||||||
def cancel : Boolean = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@ import scodec.Attempt.{Failure, Successful}
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
import org.log4s.MDC
|
import org.log4s.MDC
|
||||||
import MDCContextAware.Implicits._
|
import MDCContextAware.Implicits._
|
||||||
import net.psforever.packet.control.{HandleGamePacket, SlottedMetaPacket}
|
import net.psforever.packet.control.{HandleGamePacket, _}
|
||||||
|
|
||||||
|
import scala.annotation.tailrec
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In between the network side and the higher functioning side of the simulation:
|
* In between the network side and the higher functioning side of the simulation:
|
||||||
|
|
@ -30,13 +33,14 @@ import net.psforever.packet.control.{HandleGamePacket, SlottedMetaPacket}
|
||||||
*/
|
*/
|
||||||
class PacketCodingActor extends Actor with MDCContextAware {
|
class PacketCodingActor extends Actor with MDCContextAware {
|
||||||
private var sessionId : Long = 0
|
private var sessionId : Long = 0
|
||||||
private var subslot : Int = 0
|
private var subslotOutbound : Int = 0
|
||||||
|
private var subslotInbound : Int = 0
|
||||||
private var leftRef : ActorRef = ActorRef.noSender
|
private var leftRef : ActorRef = ActorRef.noSender
|
||||||
private var rightRef : ActorRef = ActorRef.noSender
|
private var rightRef : ActorRef = ActorRef.noSender
|
||||||
private[this] val log = org.log4s.getLogger
|
private[this] val log = org.log4s.getLogger
|
||||||
|
|
||||||
override def postStop() = {
|
override def postStop() = {
|
||||||
subslot = 0 //in case this `Actor` restarts
|
subslotOutbound = 0 //in case this `Actor` restarts
|
||||||
super.postStop()
|
super.postStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,12 +72,7 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
||||||
mtuLimit(msg)
|
mtuLimit(msg)
|
||||||
}
|
}
|
||||||
else {//from network, to LSA, WSA, etc. - decode
|
else {//from network, to LSA, WSA, etc. - decode
|
||||||
PacketCoding.unmarshalPayload(0, msg) match { //TODO is it safe for this to always be 0?
|
UnmarshalInnerPacket(msg, "a packet")
|
||||||
case Successful(packet) =>
|
|
||||||
sendResponseRight(packet)
|
|
||||||
case Failure(ex) =>
|
|
||||||
log.info(s"Failed to marshal a packet: $ex")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//known elevated packet type
|
//known elevated packet type
|
||||||
case ctrl @ ControlPacket(_, packet) =>
|
case ctrl @ ControlPacket(_, packet) =>
|
||||||
|
|
@ -88,7 +87,7 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
||||||
else { //deprecated; ControlPackets should not be coming from this direction
|
else { //deprecated; ControlPackets should not be coming from this direction
|
||||||
log.warn(s"DEPRECATED CONTROL PACKET SEND: $ctrl")
|
log.warn(s"DEPRECATED CONTROL PACKET SEND: $ctrl")
|
||||||
MDC("sessionId") = sessionId.toString
|
MDC("sessionId") = sessionId.toString
|
||||||
sendResponseRight(ctrl)
|
handlePacketContainer(ctrl) //sendResponseRight
|
||||||
}
|
}
|
||||||
//known elevated packet type
|
//known elevated packet type
|
||||||
case game @ GamePacket(_, _, packet) =>
|
case game @ GamePacket(_, _, packet) =>
|
||||||
|
|
@ -105,33 +104,36 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
||||||
MDC("sessionId") = sessionId.toString
|
MDC("sessionId") = sessionId.toString
|
||||||
sendResponseRight(game)
|
sendResponseRight(game)
|
||||||
}
|
}
|
||||||
|
//bundling packets into a SlottedMetaPacket0/MultiPacketEx
|
||||||
|
case msg @ MultiPacketBundle(list) =>
|
||||||
|
log.trace(s"BUNDLE PACKET REQUEST SEND, LEFT (always): $msg")
|
||||||
|
handleBundlePacket(list)
|
||||||
//etc
|
//etc
|
||||||
case msg =>
|
case msg =>
|
||||||
log.trace(s"PACKET SEND, LEFT: $msg")
|
|
||||||
if(sender == rightRef) {
|
if(sender == rightRef) {
|
||||||
|
log.trace(s"BASE CASE PACKET SEND, LEFT: $msg")
|
||||||
MDC("sessionId") = sessionId.toString
|
MDC("sessionId") = sessionId.toString
|
||||||
leftRef !> msg
|
leftRef !> msg
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
log.trace(s"BASE CASE PACKET SEND, RIGHT: $msg")
|
||||||
MDC("sessionId") = sessionId.toString
|
MDC("sessionId") = sessionId.toString
|
||||||
rightRef !> msg
|
rightRef !> msg
|
||||||
}
|
}
|
||||||
// case default =>
|
|
||||||
// failWithError(s"Invalid message '$default' received in state Established")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the current subslot number.
|
* Retrieve the current subslot number.
|
||||||
* Increment the `subslot` for the next time it is needed.
|
* Increment the `subslot` for the next time it is needed.
|
||||||
* @return a 16u number starting at 0
|
* @return a `16u` number starting at 0
|
||||||
*/
|
*/
|
||||||
def Subslot : Int = {
|
def Subslot : Int = {
|
||||||
if(subslot == 65536) { //TODO what is the actual wrap number?
|
if(subslotOutbound == 65536) { //TODO what is the actual wrap number?
|
||||||
subslot = 0
|
subslotOutbound = 0
|
||||||
subslot
|
subslotOutbound
|
||||||
} else {
|
} else {
|
||||||
val curr = subslot
|
val curr = subslotOutbound
|
||||||
subslot += 1
|
subslotOutbound += 1
|
||||||
curr
|
curr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -173,16 +175,74 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
||||||
def handleSplitPacket(data : ByteVector) : Unit = {
|
def handleSplitPacket(data : ByteVector) : Unit = {
|
||||||
val lim = PacketCodingActor.MTU_LIMIT_BYTES - 4 //4 bytes is the base size of SlottedMetaPacket
|
val lim = PacketCodingActor.MTU_LIMIT_BYTES - 4 //4 bytes is the base size of SlottedMetaPacket
|
||||||
data.grouped(lim).foreach(bvec => {
|
data.grouped(lim).foreach(bvec => {
|
||||||
val pkt = PacketCoding.CreateControlPacket(SlottedMetaPacket(4, Subslot, bvec))
|
PacketCoding.EncodePacket(SlottedMetaPacket(4, Subslot, bvec)) match {
|
||||||
PacketCoding.EncodePacket(pkt.packet) match {
|
|
||||||
case Successful(bdata) =>
|
case Successful(bdata) =>
|
||||||
sendResponseLeft(bdata.toByteVector)
|
sendResponseLeft(bdata.toByteVector)
|
||||||
case f @ Failure(_) =>
|
case f : Failure =>
|
||||||
log.error(s"$f")
|
log.error(s"$f")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept a `List` of packets and sequentially re-package the elements from the list into multiple container packets.<br>
|
||||||
|
* <br>
|
||||||
|
* 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.
|
||||||
|
* 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] = recursiveEncode(bundle.iterator)
|
||||||
|
recursiveFillPacketBuckets(packets.iterator, PacketCodingActor.MTU_LIMIT_BYTES - 6)
|
||||||
|
.foreach( list => {
|
||||||
|
handleBundlePacket(list.toVector)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept `ByteVector` data and package it into a `SlottedMetaPacket`.
|
||||||
|
* Send it (towards the network) upon successful encoding.
|
||||||
|
* @param data an encoded packet
|
||||||
|
*/
|
||||||
|
def handleBundlePacket(data : ByteVector) : Unit = {
|
||||||
|
PacketCoding.EncodePacket(SlottedMetaPacket(0, Subslot, data)) match {
|
||||||
|
case Successful(bdata) =>
|
||||||
|
sendResponseLeft(bdata.toByteVector)
|
||||||
|
case Failure(e) =>
|
||||||
|
log.warn(s"bundling failed on SlottedMetaPacket creation: - $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encoded sequence of data going towards the network.
|
* Encoded sequence of data going towards the network.
|
||||||
* @param cont the data
|
* @param cont the data
|
||||||
|
|
@ -193,6 +253,72 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
||||||
leftRef !> RawPacket(cont)
|
leftRef !> RawPacket(cont)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform data into a container packet and re-submit that container to the process that handles the packet.
|
||||||
|
* @param data the packet data
|
||||||
|
* @param description an explanation of the input `data`
|
||||||
|
*/
|
||||||
|
def UnmarshalInnerPacket(data : ByteVector, description : String) : Unit = {
|
||||||
|
PacketCoding.unmarshalPayload(0, data) match { //TODO is it safe for this to always be 0?
|
||||||
|
case Successful(packet) =>
|
||||||
|
handlePacketContainer(packet)
|
||||||
|
case Failure(ex) =>
|
||||||
|
log.info(s"Failed to unmarshal $description: $ex")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort and redirect a container packet bound for the server by type of contents.
|
||||||
|
* `GamePacket` objects can just onwards without issue.
|
||||||
|
* `ControlPacket` objects may need to be dequeued.
|
||||||
|
* All other container types are invalid.
|
||||||
|
* @param container the container packet
|
||||||
|
*/
|
||||||
|
def handlePacketContainer(container : PlanetSidePacketContainer) : Unit = {
|
||||||
|
container match {
|
||||||
|
case _ : GamePacket =>
|
||||||
|
sendResponseRight(container)
|
||||||
|
case ControlPacket(_, ctrlPkt) =>
|
||||||
|
handleControlPacket(container, ctrlPkt)
|
||||||
|
case default =>
|
||||||
|
log.warn(s"Invalid packet container class received: ${default.getClass.getName}") //do not spill contents in log
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a control packet or determine that it does not need to be processed at this level.
|
||||||
|
* Primarily, if the packet is of a type that contains another packet that needs be be unmarshalled,
|
||||||
|
* that/those packet must be unwound.<br>
|
||||||
|
* <br>
|
||||||
|
* The subslot information is used to identify these nested packets after arriving at their destination,
|
||||||
|
* to establish order for sequential packets and relation between divided packets.
|
||||||
|
* @param container the original container packet
|
||||||
|
* @param packet the packet that was extracted from the container
|
||||||
|
*/
|
||||||
|
def handleControlPacket(container : PlanetSidePacketContainer, packet : PlanetSideControlPacket) = {
|
||||||
|
packet match {
|
||||||
|
case SlottedMetaPacket(slot, subslot, innerPacket) =>
|
||||||
|
subslotInbound = subslot
|
||||||
|
self.tell(PacketCoding.CreateControlPacket(RelatedB(slot, subslot)), rightRef) //will go to the network
|
||||||
|
UnmarshalInnerPacket(innerPacket, "the inner packet of a SlottedMetaPacket")
|
||||||
|
|
||||||
|
case MultiPacket(packets) =>
|
||||||
|
packets.foreach { UnmarshalInnerPacket(_, "the inner packet of a MultiPacket") }
|
||||||
|
|
||||||
|
case MultiPacketEx(packets) =>
|
||||||
|
packets.foreach { UnmarshalInnerPacket(_, "the inner packet of a MultiPacketEx") }
|
||||||
|
|
||||||
|
case RelatedA(slot, subslot) =>
|
||||||
|
log.error(s"result $slot: subslot $subslot was in error")
|
||||||
|
|
||||||
|
case RelatedB(slot, subslot) =>
|
||||||
|
log.trace(s"result $slot: subslot $subslot accepted")
|
||||||
|
|
||||||
|
case _ =>
|
||||||
|
sendResponseRight(container)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decoded packet going towards the simulation.
|
* Decoded packet going towards the simulation.
|
||||||
* @param cont the packet
|
* @param cont the packet
|
||||||
|
|
@ -202,6 +328,71 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
||||||
MDC("sessionId") = sessionId.toString
|
MDC("sessionId") = sessionId.toString
|
||||||
rightRef !> cont
|
rightRef !> cont
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
import net.psforever.packet.{PlanetSideControlPacket, PlanetSideGamePacket}
|
||||||
|
iter.next match {
|
||||||
|
case msg : PlanetSideGamePacket =>
|
||||||
|
PacketCoding.EncodePacket(msg) match {
|
||||||
|
case Successful(bytecode) =>
|
||||||
|
recursiveEncode(iter, out :+ bytecode.toByteVector)
|
||||||
|
case Failure(e) =>
|
||||||
|
log.warn(s"game packet $msg, part of a bundle, did not encode - $e")
|
||||||
|
recursiveEncode(iter, out)
|
||||||
|
}
|
||||||
|
case msg : PlanetSideControlPacket =>
|
||||||
|
PacketCoding.EncodePacket(msg) match {
|
||||||
|
case Successful(bytecode) =>
|
||||||
|
recursiveEncode(iter, out :+ bytecode.toByteVector)
|
||||||
|
case Failure(e) =>
|
||||||
|
log.warn(s"control packet $msg, part of a bundle, did not encode - $e")
|
||||||
|
recursiveEncode(iter, out)
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
|
recursiveEncode(iter, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = 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, curr + len, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object PacketCodingActor {
|
object PacketCodingActor {
|
||||||
|
|
|
||||||
|
|
@ -140,10 +140,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
galaxy = endpoint
|
galaxy = endpoint
|
||||||
log.info("ID: " + sessionId + " Got galaxy service " + endpoint)
|
log.info("ID: " + sessionId + " Got galaxy service " + endpoint)
|
||||||
|
|
||||||
case ctrl @ ControlPacket(_, _) =>
|
case ControlPacket(_, ctrl) =>
|
||||||
handlePktContainer(ctrl)
|
handleControlPkt(ctrl)
|
||||||
case game @ GamePacket(_, _, _) =>
|
case GamePacket(_, _, pkt) =>
|
||||||
handlePktContainer(game)
|
handleGamePkt(pkt)
|
||||||
// temporary hack to keep the client from disconnecting
|
// temporary hack to keep the client from disconnecting
|
||||||
case PokeClient() =>
|
case PokeClient() =>
|
||||||
sendResponse(KeepAliveMessage())
|
sendResponse(KeepAliveMessage())
|
||||||
|
|
@ -1110,61 +1110,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
log.warn(s"Invalid packet class received: $default")
|
log.warn(s"Invalid packet class received: $default")
|
||||||
}
|
}
|
||||||
|
|
||||||
def handlePkt(pkt : PlanetSidePacket) : Unit = pkt match {
|
|
||||||
case ctrl : PlanetSideControlPacket =>
|
|
||||||
handleControlPkt(ctrl)
|
|
||||||
case game : PlanetSideGamePacket =>
|
|
||||||
handleGamePkt(game)
|
|
||||||
case default => log.error(s"Invalid packet class received: $default")
|
|
||||||
}
|
|
||||||
|
|
||||||
def handlePktContainer(pkt : PlanetSidePacketContainer) : Unit = pkt match {
|
|
||||||
case ctrl @ ControlPacket(opcode, ctrlPkt) =>
|
|
||||||
handleControlPkt(ctrlPkt)
|
|
||||||
case game @ GamePacket(opcode, seq, gamePkt) =>
|
|
||||||
handleGamePkt(gamePkt)
|
|
||||||
case default => log.warn(s"Invalid packet container class received: $default")
|
|
||||||
}
|
|
||||||
|
|
||||||
def handleControlPkt(pkt : PlanetSideControlPacket) = {
|
def handleControlPkt(pkt : PlanetSideControlPacket) = {
|
||||||
pkt match {
|
pkt match {
|
||||||
case SlottedMetaPacket(slot, subslot, innerPacket) =>
|
case sync @ ControlSync(diff, _, _, _, _, _, fa, fb) =>
|
||||||
sendResponse(SlottedMetaAck(slot, subslot))
|
|
||||||
|
|
||||||
PacketCoding.DecodePacket(innerPacket) match {
|
|
||||||
case Failure(e) =>
|
|
||||||
log.error(s"Failed to decode inner packet of SlottedMetaPacket: $e")
|
|
||||||
case Successful(v) =>
|
|
||||||
handlePkt(v)
|
|
||||||
}
|
|
||||||
case sync @ ControlSync(diff, unk, f1, f2, f3, f4, fa, fb) =>
|
|
||||||
log.debug(s"SYNC: $sync")
|
log.debug(s"SYNC: $sync")
|
||||||
val serverTick = Math.abs(System.nanoTime().toInt) // limit the size to prevent encoding error
|
val serverTick = Math.abs(System.nanoTime().toInt) // limit the size to prevent encoding error
|
||||||
sendResponse(ControlSyncResp(diff, serverTick, fa, fb, fb, fa))
|
sendResponse(ControlSyncResp(diff, serverTick, fa, fb, fb, fa))
|
||||||
case MultiPacket(packets) =>
|
|
||||||
packets.foreach { pkt =>
|
|
||||||
PacketCoding.DecodePacket(pkt) match {
|
|
||||||
case Failure(e) =>
|
|
||||||
log.error(s"Failed to decode inner packet of MultiPacket: $e")
|
|
||||||
case Successful(v) =>
|
|
||||||
handlePkt(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case MultiPacketEx(packets) =>
|
|
||||||
packets.foreach { pkt =>
|
|
||||||
PacketCoding.DecodePacket(pkt) match {
|
|
||||||
case Failure(e) =>
|
|
||||||
log.error(s"Failed to decode inner packet of MultiPacketEx: $e")
|
|
||||||
case Successful(v) =>
|
|
||||||
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(_) =>
|
case TeardownConnection(_) =>
|
||||||
log.info("Good bye")
|
log.info("Good bye")
|
||||||
|
|
@ -3190,6 +3141,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
sendResponse(cont.asInstanceOf[Any])
|
sendResponse(cont.asInstanceOf[Any])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def sendResponse(cont : MultiPacketBundle) : Unit = {
|
||||||
|
log.trace("WORLD SEND: " + cont)
|
||||||
|
sendResponse(cont.asInstanceOf[Any])
|
||||||
|
}
|
||||||
|
|
||||||
def sendResponse(msg : Any) : Unit = {
|
def sendResponse(msg : Any) : Unit = {
|
||||||
MDC("sessionId") = sessionId.toString
|
MDC("sessionId") = sessionId.toString
|
||||||
rightRef !> msg
|
rightRef !> msg
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
import akka.actor.{ActorRef, Props}
|
import akka.actor.{ActorRef, Props}
|
||||||
import akka.testkit.TestProbe
|
import akka.testkit.TestProbe
|
||||||
import net.psforever.packet.control.ControlSync
|
import net.psforever.packet.control.{ControlSync, MultiPacketBundle, SlottedMetaPacket}
|
||||||
import net.psforever.packet.game.objectcreate.ObjectClass
|
import net.psforever.packet.{ControlPacket, GamePacket, GamePacketOpcode, PacketCoding}
|
||||||
import net.psforever.packet.{ControlPacket, GamePacket, PacketCoding}
|
|
||||||
import net.psforever.packet.game._
|
import net.psforever.packet.game._
|
||||||
|
import net.psforever.packet.game.objectcreate.ObjectClass
|
||||||
|
import net.psforever.types._
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
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 {
|
object PacketCodingActorTest {
|
||||||
//decoy
|
//decoy
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue