From adb77382688d3980fc7ce249504e722dff8df6e9 Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 12 Mar 2018 19:18:09 -0400 Subject: [PATCH] incoming control container packet unwinding has been shifted from LSA and WSA to PCA --- .../src/main/scala/LoginSessionActor.scala | 82 ++++---------- .../src/main/scala/PacketCodingActor.scala | 102 +++++++++++++++--- .../src/main/scala/WorldSessionActor.scala | 59 +--------- 3 files changed, 109 insertions(+), 134 deletions(-) diff --git a/pslogin/src/main/scala/LoginSessionActor.scala b/pslogin/src/main/scala/LoginSessionActor.scala index 9010e0437..6260563c7 100644 --- a/pslogin/src/main/scala/LoginSessionActor.scala +++ b/pslogin/src/main/scala/LoginSessionActor.scala @@ -10,6 +10,7 @@ import scodec.bits._ import MDCContextAware.Implicits._ import com.github.mauricio.async.db.{Connection, QueryResult, RowData} import com.github.mauricio.async.db.mysql.exceptions.MySQLException +import net.psforever.objects.DefaultCancellable import net.psforever.types.PlanetSideEmpire import scala.concurrent.{Await, Future} @@ -25,7 +26,7 @@ class LoginSessionActor extends Actor with MDCContextAware { var leftRef : ActorRef = ActorRef.noSender var rightRef : ActorRef = ActorRef.noSender - var updateServerListTask : Cancellable = LoginSessionActor.DefaultCancellable + var updateServerListTask : Cancellable = DefaultCancellable.obj override def postStop() = { if(updateServerListTask != null) @@ -54,59 +55,22 @@ class LoginSessionActor extends Actor with MDCContextAware { def Started : Receive = { case UpdateServerList() => updateServerList() - case ctrl @ ControlPacket(_, _) => - 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 => + case ControlPacket(_, ctrl) => handleControlPkt(ctrl) - case game : PlanetSideGamePacket => + case GamePacket(_, _, game) => handleGamePkt(game) 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) = { 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 /// 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") - val serverTick = Math.abs(System.nanoTime().toInt) // limit the size to prevent encoding error - sendResponse(PacketCoding.CreateControlPacket(ControlSyncResp(diff, serverTick, - fa, fb, fb, fa))) - case MultiPacket(packets) => + sendResponse(PacketCoding.CreateControlPacket(ControlSyncResp(diff, serverTick, fa, fb, fb, fa))) - /// 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 => log.error(s"Unhandled ControlPacket $default") } @@ -119,7 +83,6 @@ class LoginSessionActor extends Actor with MDCContextAware { // TESTING CODE FOR ACCOUNT LOOKUP def accountLookup(username : String, password : String) : Boolean = { val connection: Connection = DatabaseConnector.getAccountsConnection - Await.result(connection.connect, 5 seconds) // create account @@ -135,13 +98,13 @@ class LoginSessionActor extends Actor with MDCContextAware { case Some(resultSet) => val row : RowData = resultSet.head row(0) - case None => -1 - } - ) + case None => + -1 + }) try { // XXX: remove awaits - val result = Await.result( mapResult, 5 seconds ) + Await.result( mapResult, 5 seconds ) return true } catch { case e : MySQLException => @@ -151,7 +114,6 @@ class LoginSessionActor extends Actor with MDCContextAware { } finally { connection.disconnect } - false } @@ -190,22 +152,25 @@ class LoginSessionActor extends Actor with MDCContextAware { log.info(s"Failed login to account $username") sendResponse(PacketCoding.CreateGamePacket(0, response)) } + case ConnectToWorldRequestMessage(name, _, _, _, _, _, _) => log.info(s"Connect to world request for '$name'") - val response = ConnectToWorldMessage(serverName, serverAddress.getHostString, serverAddress.getPort) sendResponse(PacketCoding.CreateGamePacket(0, response)) sendResponse(DropSession(sessionId, "user transferring to world")) - case default => log.debug(s"Unhandled GamePacket $pkt") + + case _ => + log.debug(s"Unhandled GamePacket $pkt") } def updateServerList() = { val msg = VNLWorldStatusMessage("Welcome to PlanetSide! ", Vector( - WorldInformation(serverName, WorldStatus.Up, ServerType.Beta, - Vector(WorldConnectionInfo(serverAddress)), PlanetSideEmpire.VS) - )) - + WorldInformation( + serverName, WorldStatus.Up, ServerType.Beta, Vector(WorldConnectionInfo(serverAddress)), PlanetSideEmpire.VS + ) + ) + ) sendResponse(PacketCoding.CreateGamePacket(0, msg)) } @@ -216,22 +181,13 @@ class LoginSessionActor extends Actor with MDCContextAware { def sendResponse(cont : Any) = { log.trace("LOGIN SEND: " + cont) - MDC("sessionId") = sessionId.toString rightRef !> cont } def sendRawResponse(pkt : ByteVector) = { log.trace("LOGIN SEND RAW: " + pkt) - MDC("sessionId") = sessionId.toString rightRef !> RawPacket(pkt) } } - -object LoginSessionActor { - final val DefaultCancellable = new Cancellable() { - def isCancelled : Boolean = true - def cancel : Boolean = true - } -} diff --git a/pslogin/src/main/scala/PacketCodingActor.scala b/pslogin/src/main/scala/PacketCodingActor.scala index 9d597ba67..59a8ab284 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.{HandleGamePacket, SlottedMetaPacket} +import net.psforever.packet.control._ /** * In between the network side and the higher functioning side of the simulation: @@ -30,13 +30,14 @@ import net.psforever.packet.control.{HandleGamePacket, SlottedMetaPacket} */ class PacketCodingActor extends Actor with MDCContextAware { 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 rightRef : ActorRef = ActorRef.noSender private[this] val log = org.log4s.getLogger override def postStop() = { - subslot = 0 //in case this `Actor` restarts + subslotOutbound = 0 //in case this `Actor` restarts super.postStop() } @@ -68,12 +69,13 @@ class PacketCodingActor extends Actor with MDCContextAware { mtuLimit(msg) } else {//from network, to LSA, WSA, etc. - decode - PacketCoding.unmarshalPayload(0, msg) match { //TODO is it safe for this to always be 0? - case Successful(packet) => - sendResponseRight(packet) - case Failure(ex) => - log.info(s"Failed to marshal a packet: $ex") - } + 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) => @@ -88,7 +90,7 @@ class PacketCodingActor extends Actor with MDCContextAware { else { //deprecated; ControlPackets should not be coming from this direction log.warn(s"DEPRECATED CONTROL PACKET SEND: $ctrl") MDC("sessionId") = sessionId.toString - sendResponseRight(ctrl) + handlePacketContainer(ctrl) //sendResponseRight } //known elevated packet type case game @ GamePacket(_, _, packet) => @@ -123,15 +125,15 @@ class PacketCodingActor extends Actor with MDCContextAware { /** * Retrieve the current subslot number. * 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 = { - if(subslot == 65536) { //TODO what is the actual wrap number? - subslot = 0 - subslot + if(subslotOutbound == 65536) { //TODO what is the actual wrap number? + subslotOutbound = 0 + subslotOutbound } else { - val curr = subslot - subslot += 1 + val curr = subslotOutbound + subslotOutbound += 1 curr } } @@ -177,7 +179,7 @@ class PacketCodingActor extends Actor with MDCContextAware { PacketCoding.EncodePacket(pkt.packet) match { case Successful(bdata) => sendResponseLeft(bdata.toByteVector) - case f @ Failure(_) => + case f : Failure => log.error(s"$f") } }) @@ -193,6 +195,72 @@ class PacketCodingActor extends Actor with MDCContextAware { 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.
+ *
+ * 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(SlottedMetaAck(slot, subslot)), rightRef) //will go towards 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 RelatedA0(subslot) => + log.error(s"bad subslot data - $subslot; potential disarray") + + case RelatedB0(subslot) => + log.trace(s"good control packet received - subslot data $subslot") + + case _ => + sendResponseRight(container) + } + } + /** * Decoded packet going towards the simulation. * @param cont the packet diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index a7c9d8f3d..b19360b8f 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -137,10 +137,10 @@ class WorldSessionActor extends Actor with MDCContextAware { galaxy = endpoint log.info("ID: " + sessionId + " Got galaxy service " + endpoint) - case ctrl @ ControlPacket(_, _) => - handlePktContainer(ctrl) - case game @ GamePacket(_, _, _) => - handlePktContainer(game) + case ControlPacket(_, ctrl) => + handleControlPkt(ctrl) + case GamePacket(_, _, pkt) => + handleGamePkt(pkt) // temporary hack to keep the client from disconnecting case PokeClient() => sendResponse(KeepAliveMessage()) @@ -1091,61 +1091,12 @@ class WorldSessionActor extends Actor with MDCContextAware { 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) = { pkt match { - case SlottedMetaPacket(slot, subslot, innerPacket) => - 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) => + case sync @ ControlSync(diff, _, _, _, _, _, fa, fb) => log.debug(s"SYNC: $sync") val serverTick = Math.abs(System.nanoTime().toInt) // limit the size to prevent encoding error 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(_) => log.info("Good bye")