Create advance session management and reaping

This commit is contained in:
Chord 2016-07-30 21:08:40 -04:00
parent c1257cb1ec
commit ae6687c38f
10 changed files with 301 additions and 84 deletions

View file

@ -15,7 +15,8 @@ lazy val commonSettings = Seq(
"ch.qos.logback" % "logback-classic" % "1.1.7", "ch.qos.logback" % "logback-classic" % "1.1.7",
"org.log4s" %% "log4s" % "1.3.0", "org.log4s" %% "log4s" % "1.3.0",
"org.fusesource.jansi" % "jansi" % "1.12", "org.fusesource.jansi" % "jansi" % "1.12",
"org.scoverage" %% "scalac-scoverage-plugin" % "1.1.1" "org.scoverage" %% "scalac-scoverage-plugin" % "1.1.1",
"com.github.nscala-time" %% "nscala-time" % "2.12.0"
) )
) )

View file

@ -17,7 +17,7 @@ object ControlPacketOpcode extends Enumeration {
ServerStart, // second packet sent in response to ClientStart ServerStart, // second packet sent in response to ClientStart
MultiPacket, // used to send multiple packets with one UDP message (subpackets limited to <= 255) MultiPacket, // used to send multiple packets with one UDP message (subpackets limited to <= 255)
Unknown4, Unknown4,
Unknown5, TeardownConnection,
Unknown6, Unknown6,
ControlSync, // sent to the server from the client ControlSync, // sent to the server from the client
ControlSyncResp, // the response generated by the server ControlSyncResp, // the response generated by the server
@ -61,7 +61,7 @@ object ControlPacketOpcode extends Enumeration {
case ClientStart => control.ClientStart.decode case ClientStart => control.ClientStart.decode
case MultiPacket => control.MultiPacket.decode case MultiPacket => control.MultiPacket.decode
case Unknown4 => noDecoder(opcode) case Unknown4 => noDecoder(opcode)
case Unknown5 => noDecoder(opcode) case TeardownConnection => control.TeardownConnection.decode
case Unknown6 => noDecoder(opcode) case Unknown6 => noDecoder(opcode)
case ControlSync => control.ControlSync.decode case ControlSync => control.ControlSync.decode
case ControlSyncResp => control.ControlSyncResp.decode case ControlSyncResp => control.ControlSyncResp.decode

View file

@ -0,0 +1,19 @@
// Copyright (c) 2016 PSForever.net to present
package net.psforever.packet.control
import net.psforever.packet.{ControlPacketOpcode, Marshallable, PacketHelpers, PlanetSideControlPacket}
import scodec.Codec
import scodec.codecs._
final case class TeardownConnection(targetNonce : Long) extends PlanetSideControlPacket {
type Packet = TeardownConnection
def opcode = ControlPacketOpcode.TeardownConnection
def encode = TeardownConnection.encode(this)
}
object TeardownConnection extends Marshallable[TeardownConnection] {
implicit val codec : Codec[TeardownConnection] = (
("nonce" | uint32L) ::
("unk" | uint16).unit(6)
).as[TeardownConnection]
}

View file

@ -167,5 +167,24 @@ class ControlPacketTest extends Specification {
} }
} }
} }
"TeardownConnection" should {
val string = hex"00 05 02 4F 57 17 00 06"
"decode" in {
PacketCoding.DecodePacket(string).require match {
case TeardownConnection(nonce) =>
nonce mustEqual 391597826
case default =>
ko
}
}
"encode" in {
val encoded = PacketCoding.EncodePacket(TeardownConnection(391597826)).require
encoded.toByteVector mustEqual string
}
}
} }
} }

View file

@ -11,12 +11,15 @@ import scodec.{Attempt, Codec, Err}
import scodec.codecs.{bytes, uint16L, uint8L} import scodec.codecs.{bytes, uint16L, uint8L}
import java.security.SecureRandom import java.security.SecureRandom
import net.psforever.packet.control.{ClientStart, ServerStart} import net.psforever.packet.control.{ClientStart, ServerStart, TeardownConnection}
import net.psforever.packet.crypto._ import net.psforever.packet.crypto._
import net.psforever.packet.game.PingMsg import net.psforever.packet.game.PingMsg
import org.log4s.MDC import org.log4s.MDC
import MDCContextAware.Implicits._ import MDCContextAware.Implicits._
sealed trait CryptoSessionAPI
final case class DropCryptoSession() extends CryptoSessionAPI
/** /**
* Actor that stores crypto state for a connection, appropriately encrypts and decrypts packets, * Actor that stores crypto state for a connection, appropriately encrypts and decrypts packets,
* and passes packets along to the next hop once processed. * and passes packets along to the next hop once processed.
@ -41,6 +44,9 @@ class CryptoSessionActor extends Actor with MDCContextAware {
var clientChallenge = ByteVector.empty var clientChallenge = ByteVector.empty
var clientChallengeResult = ByteVector.empty var clientChallengeResult = ByteVector.empty
var clientNonce : Long = 0
var serverNonce : Long = 0
// Don't leak crypto object memory even on an exception // Don't leak crypto object memory even on an exception
override def postStop() = { override def postStop() = {
cleanupCrypto() cleanupCrypto()
@ -74,7 +80,9 @@ class CryptoSessionActor extends Actor with MDCContextAware {
p match { p match {
case ControlPacket(_, ClientStart(nonce)) => case ControlPacket(_, ClientStart(nonce)) =>
sendResponse(PacketCoding.CreateControlPacket(ServerStart(nonce, Math.abs(random.nextInt())))) clientNonce = nonce
serverNonce = Math.abs(random.nextInt())
sendResponse(PacketCoding.CreateControlPacket(ServerStart(nonce, serverNonce)))
context.become(CryptoExchange) context.become(CryptoExchange)
case default => case default =>
@ -252,6 +260,14 @@ class CryptoSessionActor extends Actor with MDCContextAware {
case Failure(e) => log.error("Could not decode raw packet: " + e) case Failure(e) => log.error("Could not decode raw packet: " + e)
} }
} }
case api : CryptoSessionAPI =>
api match {
case DropCryptoSession() =>
handleEstablishedPacket(
sender(),
PacketCoding.CreateControlPacket(TeardownConnection(clientNonce))
)
}
case ctrl @ ControlPacket(_, _) => case ctrl @ ControlPacket(_, _) =>
val from = sender() val from = sender()
@ -260,6 +276,8 @@ class CryptoSessionActor extends Actor with MDCContextAware {
val from = sender() val from = sender()
handleEstablishedPacket(from, game) handleEstablishedPacket(from, game)
case sessionAPI : SessionRouterAPI =>
leftRef !> sessionAPI
case default => failWithError(s"Invalid message '$default' received in state Established") case default => failWithError(s"Invalid message '$default' received in state Established")
} }

View file

@ -1,7 +1,7 @@
// Copyright (c) 2016 PSForever.net to present // Copyright (c) 2016 PSForever.net to present
import java.net.{InetAddress, InetSocketAddress} import java.net.{InetAddress, InetSocketAddress}
import akka.actor.{Actor, ActorRef, MDCContextAware} import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
import net.psforever.packet.{PlanetSideGamePacket, _} import net.psforever.packet.{PlanetSideGamePacket, _}
import net.psforever.packet.control._ import net.psforever.packet.control._
import net.psforever.packet.game._ import net.psforever.packet.game._
@ -10,17 +10,26 @@ import scodec.Attempt.{Failure, Successful}
import scodec.bits._ import scodec.bits._
import MDCContextAware.Implicits._ import MDCContextAware.Implicits._
import scala.concurrent.duration._
import scala.util.Random import scala.util.Random
class LoginSessionActor extends Actor with MDCContextAware { class LoginSessionActor extends Actor with MDCContextAware {
private[this] val log = org.log4s.getLogger private[this] val log = org.log4s.getLogger
import scala.concurrent.ExecutionContext.Implicits.global
private case class UpdateServerList() private case class UpdateServerList()
var sessionId : Long = 0 var sessionId : Long = 0
var leftRef : ActorRef = ActorRef.noSender var leftRef : ActorRef = ActorRef.noSender
var rightRef : ActorRef = ActorRef.noSender var rightRef : ActorRef = ActorRef.noSender
var updateServerListTask : Cancellable = null
override def postStop() = {
if(updateServerListTask != null)
updateServerListTask.cancel()
}
def receive = Initializing def receive = Initializing
def Initializing : Receive = { def Initializing : Receive = {
@ -112,12 +121,14 @@ class LoginSessionActor extends Actor with MDCContextAware {
0, 1, 2, 685276011, username, 0, false) 0, 1, 2, 685276011, username, 0, false)
sendResponse(PacketCoding.CreateGamePacket(0, response)) sendResponse(PacketCoding.CreateGamePacket(0, response))
updateServerList()
updateServerListTask = context.system.scheduler.schedule(0 seconds, 2 seconds, self, UpdateServerList())
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"))
case default => log.debug(s"Unhandled GamePacket ${pkt}") case default => log.debug(s"Unhandled GamePacket ${pkt}")
} }
@ -136,7 +147,7 @@ class LoginSessionActor extends Actor with MDCContextAware {
//sendResponse(PacketCoding.CreateControlPacket(ConnectionClose())) //sendResponse(PacketCoding.CreateControlPacket(ConnectionClose()))
} }
def sendResponse(cont : PlanetSidePacketContainer) = { def sendResponse(cont : Any) = {
log.trace("LOGIN SEND: " + cont) log.trace("LOGIN SEND: " + cont)
MDC("sessionId") = sessionId.toString MDC("sessionId") = sessionId.toString

View file

@ -174,9 +174,9 @@ object PsLogin {
val worldServerPort = 51001 val worldServerPort = 51001
/** Create two actors for handling the login and world server endpoints */ /** Create two actors for handling the login and world server endpoints */
val listener = system.actorOf(Props(new UdpListener(Props(new SessionRouter(loginTemplate)), "login-session-router", val listener = system.actorOf(Props(new UdpListener(Props(new SessionRouter("Login", loginTemplate)), "login-session-router",
LoginConfig.serverIpAddress, loginServerPort)), "login-udp-endpoint") LoginConfig.serverIpAddress, loginServerPort)), "login-udp-endpoint")
val worldListener = system.actorOf(Props(new UdpListener(Props(new SessionRouter(worldTemplate)), "world-session-router", val worldListener = system.actorOf(Props(new UdpListener(Props(new SessionRouter("World", worldTemplate)), "world-session-router",
LoginConfig.serverIpAddress, worldServerPort)), "world-udp-endpoint") LoginConfig.serverIpAddress, worldServerPort)), "world-udp-endpoint")
logger.info(s"NOTE: Set client.ini to point to ${LoginConfig.serverIpAddress.getHostAddress}:$loginServerPort") logger.info(s"NOTE: Set client.ini to point to ${LoginConfig.serverIpAddress.getHostAddress}:$loginServerPort")

View file

@ -0,0 +1,101 @@
// Copyright (c) 2016 PSForever.net to present
import java.net.InetSocketAddress
import akka.actor._
import org.log4s.MDC
import scodec.bits._
import akka.actor.{ActorContext, ActorRef, PoisonPill}
import com.github.nscala_time.time.Imports._
import MDCContextAware.Implicits._
sealed trait SessionState
final case class New() extends SessionState
final case class Related() extends SessionState
final case class Handshaking() extends SessionState
final case class Established() extends SessionState
final case class Closing() extends SessionState
final case class Closed() extends SessionState
class Session(val sessionId : Long,
val socketAddress : InetSocketAddress,
returnActor : ActorRef,
sessionPipeline : List[SessionPipeline])
(implicit val context: ActorContext, implicit val self : ActorRef) {
var state : SessionState = New()
val sessionCreatedTime : DateTime = DateTime.now
var sessionEndedTime : DateTime = DateTime.now
val pipeline = sessionPipeline.map { actor =>
val a = context.actorOf(actor.props, actor.nameTemplate + sessionId.toString)
context.watch(a)
a
}
pipeline.head ! HelloFriend(sessionId, pipeline.tail.head)
// statistics
var bytesSent : Long = 0
var bytesReceived : Long = 0
var inboundPackets : Long = 0
var outboundPackets : Long = 0
var lastInboundEvent : Long = System.nanoTime()
var lastOutboundEvent : Long = System.nanoTime()
var inboundPacketRate : Double = 0.0
var outboundPacketRate : Double = 0.0
var inboundBytesPerSecond : Double = 0.0
var outboundBytesPerSecond : Double = 0.0
def receive(packet : RawPacket) : Unit = {
bytesReceived += packet.data.size
inboundPackets += 1
lastInboundEvent = System.nanoTime()
pipeline.head !> packet
}
def send(packet : ByteVector) : Unit = {
bytesSent += packet.size
outboundPackets += 1
lastOutboundEvent = System.nanoTime()
returnActor ! SendPacket(packet, socketAddress)
}
def dropSession(graceful : Boolean) = {
pipeline.foreach(context.unwatch)
pipeline.foreach(_ ! PoisonPill)
sessionEndedTime = DateTime.now
setState(Closed())
}
def getState = state
def setState(newState : SessionState) : Unit = {
state = newState
}
def getPipeline : List[ActorRef] = pipeline
def getTotalBytes = {
bytesSent + bytesReceived
}
def timeSinceLastInboundEvent = {
(System.nanoTime() - lastInboundEvent)/1000000
}
def timeSinceLastOutboundEvent = {
(System.nanoTime() - lastOutboundEvent)/1000000
}
override def toString : String = {
s"Session($sessionId, $getTotalBytes)"
}
}

View file

@ -9,23 +9,43 @@ import scala.collection.mutable
import MDCContextAware.Implicits._ import MDCContextAware.Implicits._
import akka.actor.MDCContextAware.MdcMsg import akka.actor.MDCContextAware.MdcMsg
import akka.actor.SupervisorStrategy.Stop import akka.actor.SupervisorStrategy.Stop
import net.psforever.packet.PacketCoding
import net.psforever.packet.control.ConnectionClose
final case class RawPacket(data : ByteVector) import scala.concurrent.duration._
final case class ResponsePacket(data : ByteVector)
case class SessionState(id : Long, address : InetSocketAddress, pipeline : List[ActorRef]) { sealed trait SessionRouterAPI
def startOfPipe = pipeline.head final case class RawPacket(data : ByteVector) extends SessionRouterAPI
def nextOfStart = pipeline.tail.head final case class ResponsePacket(data : ByteVector) extends SessionRouterAPI
} final case class DropSession(id : Long, reason : String) extends SessionRouterAPI
final case class SessionReaper() extends SessionRouterAPI
case class SessionPipeline(nameTemplate : String, props : Props) case class SessionPipeline(nameTemplate : String, props : Props)
class SessionRouter(pipeline : List[SessionPipeline]) extends Actor with MDCContextAware { /**
* Login sessions are divided between two actors. The crypto session actor transparently handles all of the cryptographic
* setup of the connection. Once a correct crypto session has been established, all packets, after being decrypted
* will be passed on to the login session actor. This actor has important state that is used to maintain the login
* session.
*
* > PlanetSide Session Pipeline <
*
* read() route decrypt
* UDP Socket -----> [Session Router] -----> [Crypto Actor] -----> [Session Actor]
* ^ | ^ | ^ |
* | write() | | encrypt | | response |
* +--------------+ +-----------+ +-----------------+
**/
class SessionRouter(role : String, pipeline : List[SessionPipeline]) extends Actor with MDCContextAware {
private[this] val log = org.log4s.getLogger(self.path.name) private[this] val log = org.log4s.getLogger(self.path.name)
import scala.concurrent.ExecutionContext.Implicits.global
val sessionReaper = context.system.scheduler.schedule(10 seconds, 5 seconds, self, SessionReaper())
val idBySocket = mutable.Map[InetSocketAddress, Long]() val idBySocket = mutable.Map[InetSocketAddress, Long]()
val sessionById = mutable.Map[Long, SessionState]() val sessionById = mutable.Map[Long, Session]()
val sessionByActor = mutable.Map[ActorRef, SessionState]() val sessionByActor = mutable.Map[ActorRef, Session]()
val closePacket = PacketCoding.EncodePacket(ConnectionClose()).require.bytes
var sessionId = 0L // this is a connection session, not an actual logged in session ID var sessionId = 0L // this is a connection session, not an actual logged in session ID
var inputRef : ActorRef = ActorRef.noSender var inputRef : ActorRef = ActorRef.noSender
@ -33,24 +53,9 @@ class SessionRouter(pipeline : List[SessionPipeline]) extends Actor with MDCCont
override def supervisorStrategy = OneForOneStrategy() { case _ => Stop } override def supervisorStrategy = OneForOneStrategy() { case _ => Stop }
override def preStart = { override def preStart = {
log.info("SessionRouter started...ready for PlanetSide sessions") log.info(s"SessionRouter started...ready for ${role} sessions")
} }
/**
* Login sessions are divided between two actors. The crypto session actor transparently handles all of the cryptographic
* setup of the connection. Once a correct crypto session has been established, all packets, after being decrypted
* will be passed on to the login session actor. This actor has important state that is used to maintain the login
* session.
*
* > PlanetSide Session Pipeline <
*
* read() route decrypt
* UDP Socket -----> [Session Router] -----> [Crypto Actor] -----> [Session Actor]
* ^ | ^ | ^ |
* | write() | | encrypt | | response |
* +--------------+ +-----------+ +-----------------+
**/
def receive = initializing def receive = initializing
def initializing : Receive = { def initializing : Receive = {
@ -62,47 +67,70 @@ class SessionRouter(pipeline : List[SessionPipeline]) extends Actor with MDCCont
context.stop(self) context.stop(self)
} }
override def postStop() = {
sessionReaper.cancel()
}
def started : Receive = { def started : Receive = {
case ReceivedPacket(msg, from) => case recv @ ReceivedPacket(msg, from) =>
if(idBySocket.contains(from)) { var session : Session = null
MDC("sessionId") = idBySocket{from}.toString
log.trace(s"Handling received packet ${msg} -> ${sessionById{idBySocket{from}}.startOfPipe.path.name}") if(!idBySocket.contains(from)) {
sessionById{idBySocket{from}}.startOfPipe !> RawPacket(msg) session = createNewSession(from)
}
MDC.clear() else {
} else { val id = idBySocket{from}
val session = createNewSession(from) session = sessionById{id}
idBySocket{from} = session.id }
sessionById{session.id} = session
sessionByActor{session.startOfPipe} = session
sessionByActor{session.nextOfStart} = session
MDC("sessionId") = session.id.toString
log.info("New session from " + from.toString)
// send the initial message with MDC context (give the session ID to the lower layers)
sessionById{session.id}.startOfPipe !> HelloFriend(session.id, sessionById{session.id}.nextOfStart)
sessionById{session.id}.startOfPipe !> RawPacket(msg)
if(session.state != Closed()) {
MDC("sessionId") = session.sessionId.toString
log.trace(s"RECV: ${msg} -> ${session.getPipeline.head.path.name}")
session.receive(RawPacket(msg))
MDC.clear() MDC.clear()
} }
case ResponsePacket(msg) => case ResponsePacket(msg) =>
val session = sessionByActor.get(sender()) val session = sessionByActor.get(sender())
if(session.isDefined) {
log.trace(s"Sending response ${msg}")
inputRef ! SendPacket(msg, session.get.address) if(session.isDefined) {
if(session.get.state != Closed()) {
MDC("sessionId") = session.get.sessionId.toString
log.trace(s"SEND: ${msg} -> ${inputRef.path.name}")
session.get.send(msg)
MDC.clear()
}
} else { } else {
log.error("Dropped old response packet from session actor " + sender().path.name) log.error("Dropped old response packet from actor " + sender().path.name)
}
case DropSession(id, reason) =>
val session = sessionById.get(id)
if(session.isDefined) {
removeSessionById(id, reason, graceful = true)
} else {
log.error(s"Requested to drop non-existent session ID=$id from ${sender()}")
}
case SessionReaper() =>
sessionById.foreach { case (id, session) =>
log.debug(session.toString)
if(session.getState == Closed()) {
// clear mappings
session.getPipeline.foreach(sessionByActor remove)
sessionById.remove(id)
idBySocket.remove(session.socketAddress)
log.debug(s"Reaped session ID=$id")
} else if(session.timeSinceLastInboundEvent > 10000) {
removeSessionById(id, "session timed out (inbound)", graceful = false)
} else if(session.timeSinceLastOutboundEvent > 4000) {
removeSessionById(id, "session timed out (outbound)", graceful = true) // tell client to STFU
}
} }
case Terminated(actor) => case Terminated(actor) =>
val terminatedSession = sessionByActor.get(actor) val terminatedSession = sessionByActor.get(actor)
if(terminatedSession.isDefined) { if(terminatedSession.isDefined) {
removeSessionById(terminatedSession.get.id, s"${actor.path.name} died") removeSessionById(terminatedSession.get.sessionId, s"${actor.path.name} died",
graceful = true)
} else { } else {
log.error("Received an invalid actor Termination from " + actor.path.name) log.error("Received an invalid actor Termination from " + actor.path.name)
} }
@ -112,33 +140,38 @@ class SessionRouter(pipeline : List[SessionPipeline]) extends Actor with MDCCont
def createNewSession(address : InetSocketAddress) = { def createNewSession(address : InetSocketAddress) = {
val id = newSessionId val id = newSessionId
val session = new Session(id, address, inputRef, pipeline)
// inflate the pipeline // establish mappings for easy lookup
val actors = pipeline.map { actor => idBySocket{address} = id
val a = context.actorOf(actor.props, actor.nameTemplate + id.toString) sessionById{id} = session
context.watch(a)
a session.getPipeline.foreach { actor =>
sessionByActor{actor} = session
} }
SessionState(id, address, actors) log.info(s"New session ID=${id} from " + address.toString)
session
} }
def removeSessionById(id : Long, reason : String) : Unit = { def removeSessionById(id : Long, reason : String, graceful : Boolean) : Unit = {
val sessionOption = sessionById.get(id) val sessionOption = sessionById.get(id)
if(sessionOption.isEmpty) if(sessionOption.isEmpty)
return return
val session = sessionOption.get val session : Session = sessionOption.get
if(graceful) {
for(i <- 0 to 5) {
session.send(closePacket)
}
}
// TODO: add some sort of delay to prevent old session packets from coming through
// kill all session specific actors // kill all session specific actors
session.pipeline.foreach(_ ! PoisonPill) session.dropSession(graceful)
session.pipeline.foreach(sessionByActor remove) log.info(s"Dropping session ID=${id} (reason: $reason)")
sessionById.remove(id)
idBySocket.remove(session.address)
log.info(s"Stopping session ${id} (reason: $reason)")
} }
def newSessionId = { def newSessionId = {

View file

@ -1,7 +1,7 @@
// Copyright (c) 2016 PSForever.net to present // Copyright (c) 2016 PSForever.net to present
import java.net.{InetAddress, InetSocketAddress} import java.net.{InetAddress, InetSocketAddress}
import akka.actor.{Actor, ActorRef, MDCContextAware} import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
import net.psforever.packet.{PlanetSideGamePacket, _} import net.psforever.packet.{PlanetSideGamePacket, _}
import net.psforever.packet.control._ import net.psforever.packet.control._
import net.psforever.packet.game._ import net.psforever.packet.game._
@ -9,6 +9,7 @@ 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.types.ChatMessageType
class WorldSessionActor extends Actor with MDCContextAware { class WorldSessionActor extends Actor with MDCContextAware {
private[this] val log = org.log4s.getLogger private[this] val log = org.log4s.getLogger
@ -19,6 +20,13 @@ class WorldSessionActor 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 clientKeepAlive : Cancellable = null
override def postStop() = {
if(clientKeepAlive != null)
clientKeepAlive.cancel()
}
def receive = Initializing def receive = Initializing
def Initializing : Receive = { def Initializing : Receive = {
@ -139,7 +147,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
context.system.scheduler.schedule(0 seconds, 1000 milliseconds, self, PokeClient()) clientKeepAlive = context.system.scheduler.schedule(0 seconds, 1000 milliseconds, self, PokeClient())
} }
case default => case default =>
log.error("Unsupported " + default + " in " + msg) log.error("Unsupported " + default + " in " + msg)
@ -160,6 +168,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents) => case msg @ ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents) =>
log.info("Chat: " + msg) log.info("Chat: " + msg)
// TODO: handle this appropriately
if(messagetype == ChatMessageType.PopupQuit) {
sendResponse(DropCryptoSession())
sendResponse(DropSession(sessionId, "user quit"))
}
// TODO: Depending on messagetype, may need to prepend sender's name to contents with proper spacing // TODO: Depending on messagetype, may need to prepend sender's name to contents with proper spacing
// TODO: Just replays the packet straight back to sender; actually needs to be routed to recipients! // TODO: Just replays the packet straight back to sender; actually needs to be routed to recipients!
sendResponse(PacketCoding.CreateGamePacket(0, ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents))) sendResponse(PacketCoding.CreateGamePacket(0, ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents)))
@ -235,17 +249,18 @@ class WorldSessionActor extends Actor with MDCContextAware {
//sendResponse(PacketCoding.CreateControlPacket(ConnectionClose())) //sendResponse(PacketCoding.CreateControlPacket(ConnectionClose()))
} }
def sendResponse(cont : PlanetSidePacketContainer) = { def sendResponse(cont : PlanetSidePacketContainer) : Unit = {
log.trace("WORLD SEND: " + cont) log.trace("WORLD SEND: " + cont)
sendResponse(cont.asInstanceOf[Any])
}
def sendResponse(msg : Any) : Unit = {
MDC("sessionId") = sessionId.toString MDC("sessionId") = sessionId.toString
rightRef !> cont rightRef !> msg
} }
def sendRawResponse(pkt : ByteVector) = { def sendRawResponse(pkt : ByteVector) = {
log.trace("WORLD SEND RAW: " + pkt) log.trace("WORLD SEND RAW: " + pkt)
sendResponse(RawPacket(pkt))
MDC("sessionId") = sessionId.toString
rightRef !> RawPacket(pkt)
} }
} }