mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-04-22 12:25:21 +00:00
initial RelatedA0 and RelatedB0 packets
modified HandleGamePacket so that it should encode properly; modified CSA so that it attempt to detect packets that encode into ByteVectors that are 'too big' and will attempt to split them up separated the ControlPacket tests into invdividual files and wrote tests for RelatedA0, RelatedB0, and HandleGamePacket proof of concept MTU packet split in CSA; example in WSA @ character select modified session pipeline to accept n queued Actors rather than just two; special packet decoder in progress some effort separating useful sub-operations in encryption/decryption/encoding/decoding functions; introduced PacketCodingActor , devoted to encoding and decoding packets; simplified CSA so that it is devoted just to encrypting and decrypting
This commit is contained in:
parent
3e5e8a2573
commit
294d5335c9
23 changed files with 833 additions and 370 deletions
|
|
@ -1,17 +1,13 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
import java.net.{InetAddress, InetSocketAddress}
|
||||
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, DiagnosticActorLogging, Identify, MDCContextAware}
|
||||
import net.psforever.crypto.CryptoInterface.{CryptoState, CryptoStateWithMAC}
|
||||
import akka.actor.{Actor, ActorRef, MDCContextAware}
|
||||
import net.psforever.crypto.CryptoInterface.CryptoStateWithMAC
|
||||
import net.psforever.crypto.CryptoInterface
|
||||
import net.psforever.packet._
|
||||
import scodec.Attempt.{Failure, Successful}
|
||||
import scodec.bits._
|
||||
import scodec.{Attempt, Codec, Err}
|
||||
import scodec.codecs.{bytes, uint16L, uint8L}
|
||||
import java.security.SecureRandom
|
||||
|
||||
import net.psforever.packet.control.{ClientStart, ServerStart, TeardownConnection}
|
||||
import net.psforever.packet.control._
|
||||
import net.psforever.packet.crypto._
|
||||
import net.psforever.packet.game.PingMsg
|
||||
import org.log4s.MDC
|
||||
|
|
@ -55,18 +51,19 @@ class CryptoSessionActor extends Actor with MDCContextAware {
|
|||
def receive = Initializing
|
||||
|
||||
def Initializing : Receive = {
|
||||
case HelloFriend(sessionId, right) =>
|
||||
case HelloFriend(sharedSessionId, pipe) =>
|
||||
import MDCContextAware.Implicits._
|
||||
this.sessionId = sessionId
|
||||
this.sessionId = sharedSessionId
|
||||
leftRef = sender()
|
||||
rightRef = right.asInstanceOf[ActorRef]
|
||||
|
||||
// who ever we send to has to send something back to us
|
||||
rightRef !> HelloFriend(sessionId, self)
|
||||
|
||||
if(pipe.hasNext) {
|
||||
rightRef = pipe.next // who ever we send to has to send something back to us
|
||||
rightRef !> HelloFriend(sessionId, pipe)
|
||||
} else {
|
||||
rightRef = sender()
|
||||
}
|
||||
log.trace(s"Left sender ${leftRef.path.name}")
|
||||
|
||||
context.become(NewClient)
|
||||
|
||||
case default =>
|
||||
log.error("Unknown message " + default)
|
||||
context.stop(self)
|
||||
|
|
@ -85,10 +82,10 @@ class CryptoSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(PacketCoding.CreateControlPacket(ServerStart(nonce, serverNonce)))
|
||||
|
||||
context.become(CryptoExchange)
|
||||
case default =>
|
||||
log.error(s"Unexpected packet type ${p} in state NewClient")
|
||||
case _ =>
|
||||
log.error(s"Unexpected packet type $p in state NewClient")
|
||||
}
|
||||
case Failure(e) =>
|
||||
case Failure(_) =>
|
||||
// There is a special case where no crypto is being used.
|
||||
// The only packet coming through looks like PingMsg. This is a hardcoded
|
||||
// feature of the client @ 0x005FD618
|
||||
|
|
@ -98,56 +95,53 @@ class CryptoSessionActor extends Actor with MDCContextAware {
|
|||
case ping @ PingMsg(_, _) =>
|
||||
// reflect the packet back to the sender
|
||||
sendResponse(ping)
|
||||
case default => log.error(s"Unexpected non-crypto packet type ${packet} in state NewClient")
|
||||
case _ =>
|
||||
log.error(s"Unexpected non-crypto packet type $packet in state NewClient")
|
||||
}
|
||||
case Failure(e) =>
|
||||
log.error("Could not decode packet: " + e + s" in state NewClient")
|
||||
}
|
||||
}
|
||||
case default => log.error(s"Invalid message '$default' received in state NewClient")
|
||||
case default =>
|
||||
log.error(s"Invalid message '$default' received in state NewClient")
|
||||
}
|
||||
|
||||
def CryptoExchange : Receive = {
|
||||
case RawPacket(msg) =>
|
||||
PacketCoding.UnmarshalPacket(msg, CryptoPacketOpcode.ClientChallengeXchg) match {
|
||||
case Failure(e) => log.error("Could not decode packet in state CryptoExchange: " + e)
|
||||
case Successful(p) =>
|
||||
log.trace("NewClient -> CryptoExchange")
|
||||
case Failure(e) =>
|
||||
log.error("Could not decode packet in state CryptoExchange: " + e)
|
||||
|
||||
p match {
|
||||
case Successful(pkt) =>
|
||||
log.trace("NewClient -> CryptoExchange")
|
||||
pkt match {
|
||||
case CryptoPacket(seq, ClientChallengeXchg(time, challenge, p, g)) =>
|
||||
cryptoDHState = Some(new CryptoInterface.CryptoDHState())
|
||||
|
||||
val dh = cryptoDHState.get
|
||||
|
||||
// initialize our crypto state from the client's P and G
|
||||
dh.start(p, g)
|
||||
|
||||
// save the client challenge
|
||||
clientChallenge = ServerChallengeXchg.getCompleteChallenge(time, challenge)
|
||||
|
||||
// save the packet we got for a MAC check later. drop the first 3 bytes
|
||||
serverMACBuffer ++= msg.drop(3)
|
||||
|
||||
val serverTime = System.currentTimeMillis() / 1000L
|
||||
val randomChallenge = getRandBytes(0xc)
|
||||
|
||||
// store the complete server challenge for later
|
||||
serverChallenge = ServerChallengeXchg.getCompleteChallenge(serverTime, randomChallenge)
|
||||
|
||||
val packet = PacketCoding.CreateCryptoPacket(seq,
|
||||
ServerChallengeXchg(serverTime, randomChallenge, dh.getPublicKey))
|
||||
|
||||
ServerChallengeXchg(serverTime, randomChallenge, dh.getPublicKey)
|
||||
)
|
||||
val sentPacket = sendResponse(packet)
|
||||
|
||||
// save the sent packet a MAC check
|
||||
serverMACBuffer ++= sentPacket.drop(3)
|
||||
|
||||
context.become(CryptoSetupFinishing)
|
||||
case default => log.error(s"Unexpected packet type $p in state CryptoExchange")
|
||||
|
||||
case _ =>
|
||||
log.error(s"Unexpected packet type $pkt in state CryptoExchange")
|
||||
}
|
||||
}
|
||||
case default => log.error(s"Invalid message '$default' received in state CryptoExchange")
|
||||
case default =>
|
||||
log.error(s"Invalid message '$default' received in state CryptoExchange")
|
||||
}
|
||||
|
||||
def CryptoSetupFinishing : Receive = {
|
||||
|
|
@ -188,11 +182,10 @@ class CryptoSessionActor extends Actor with MDCContextAware {
|
|||
ByteVector("server finished".getBytes) ++ serverMACBuffer ++ hex"01",
|
||||
0xc)
|
||||
|
||||
val clientChallengeResultCheck = CryptoInterface.MD5MAC(masterSecret,
|
||||
ByteVector("client finished".getBytes) ++ serverMACBuffer ++ hex"01" ++ clientChallengeResult ++ hex"01",
|
||||
0xc)
|
||||
|
||||
//println("Check result: " + CryptoInterface.verifyMAC(clientChallenge, clientChallengeResult))
|
||||
// val clientChallengeResultCheck = CryptoInterface.MD5MAC(masterSecret,
|
||||
// ByteVector("client finished".getBytes) ++ serverMACBuffer ++ hex"01" ++ clientChallengeResult ++ hex"01",
|
||||
// 0xc)
|
||||
// println("Check result: " + CryptoInterface.verifyMAC(clientChallenge, clientChallengeResult))
|
||||
|
||||
val decExpansion = ByteVector("client expansion".getBytes) ++ hex"0000" ++ serverChallenge ++
|
||||
hex"00000000" ++ clientChallenge ++ hex"00000000"
|
||||
|
|
@ -239,27 +232,32 @@ class CryptoSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
|
||||
def Established : Receive = {
|
||||
//same as having received ad hoc hexadecimal
|
||||
case RawPacket(msg) =>
|
||||
if(sender() == rightRef) {
|
||||
val packet = PacketCoding.encryptPacket(cryptoState.get, 0, msg).require
|
||||
sendResponse(packet)
|
||||
} else {
|
||||
} else { //from network-side
|
||||
PacketCoding.UnmarshalPacket(msg) match {
|
||||
case Successful(p) =>
|
||||
p match {
|
||||
case encPacket @ EncryptedPacket(seq, _) =>
|
||||
PacketCoding.decryptPacket(cryptoState.get, encPacket) match {
|
||||
case encPacket @ EncryptedPacket(_/*seq*/, _) =>
|
||||
PacketCoding.decryptPacketData(cryptoState.get, encPacket) match {
|
||||
case Successful(packet) =>
|
||||
self !> packet
|
||||
MDC("sessionId") = sessionId.toString
|
||||
rightRef !> RawPacket(packet)
|
||||
case Failure(e) =>
|
||||
log.error("Failed to decode encrypted packet: " + e)
|
||||
}
|
||||
case default => failWithError(s"Unexpected packet type $default in state Established")
|
||||
case default =>
|
||||
failWithError(s"Unexpected packet type $default in state Established")
|
||||
|
||||
}
|
||||
case Failure(e) => log.error("Could not decode raw packet: " + e)
|
||||
case Failure(e) =>
|
||||
log.error("Could not decode raw packet: " + e)
|
||||
}
|
||||
}
|
||||
//message to self?
|
||||
case api : CryptoSessionAPI =>
|
||||
api match {
|
||||
case DropCryptoSession() =>
|
||||
|
|
@ -268,17 +266,12 @@ class CryptoSessionActor extends Actor with MDCContextAware {
|
|||
PacketCoding.CreateControlPacket(TeardownConnection(clientNonce))
|
||||
)
|
||||
}
|
||||
case ctrl @ ControlPacket(_, _) =>
|
||||
val from = sender()
|
||||
|
||||
handleEstablishedPacket(from, ctrl)
|
||||
case game @ GamePacket(_, _, _) =>
|
||||
val from = sender()
|
||||
|
||||
handleEstablishedPacket(from, game)
|
||||
//echo the session router? isn't that normally the leftRef?
|
||||
case sessionAPI : SessionRouterAPI =>
|
||||
leftRef !> sessionAPI
|
||||
case default => failWithError(s"Invalid message '$default' received in state Established")
|
||||
//error
|
||||
case default =>
|
||||
failWithError(s"Invalid message '$default' received in state Established")
|
||||
}
|
||||
|
||||
def failWithError(error : String) = {
|
||||
|
|
@ -311,32 +304,35 @@ class CryptoSessionActor extends Actor with MDCContextAware {
|
|||
clientChallengeResult = ByteVector.empty
|
||||
}
|
||||
|
||||
def handleEstablishedPacket(from : ActorRef, cont : PlanetSidePacketContainer) = {
|
||||
// we are processing a packet we decrypted
|
||||
if(from == self) {
|
||||
def handleEstablishedPacket(from : ActorRef, cont : PlanetSidePacketContainer) : Unit = {
|
||||
//we are processing a packet that we decrypted
|
||||
if(from == self) { //to WSA, LSA, etc.
|
||||
rightRef !> cont
|
||||
} else if(from == rightRef) { // processing a completed packet from the right. encrypt
|
||||
val packet = PacketCoding.encryptPacket(cryptoState.get, cont).require
|
||||
sendResponse(packet)
|
||||
} else if(from == rightRef) { //processing a completed packet from the right; to network-side
|
||||
PacketCoding.getPacketDataForEncryption(cont) match {
|
||||
case Successful((seq, data)) =>
|
||||
val packet = PacketCoding.encryptPacket(cryptoState.get, seq, data).require
|
||||
sendResponse(packet)
|
||||
case Failure(ex) =>
|
||||
log.error(s"$ex")
|
||||
}
|
||||
} else {
|
||||
log.error(s"Invalid sender when handling a message in Established ${from}")
|
||||
log.error(s"Invalid sender when handling a message in Established $from")
|
||||
}
|
||||
}
|
||||
|
||||
def sendResponse(cont : PlanetSidePacketContainer) : ByteVector = {
|
||||
log.trace("CRYPTO SEND: " + cont)
|
||||
val pkt = PacketCoding.MarshalPacket(cont)
|
||||
|
||||
pkt match {
|
||||
case Failure(e) =>
|
||||
case Failure(_) =>
|
||||
log.error(s"Failed to marshal packet ${cont.getClass.getName} when sending response")
|
||||
ByteVector.empty
|
||||
|
||||
case Successful(v) =>
|
||||
val bytes = v.toByteVector
|
||||
|
||||
MDC("sessionId") = sessionId.toString
|
||||
leftRef !> ResponsePacket(bytes)
|
||||
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
|
@ -344,17 +340,15 @@ class CryptoSessionActor extends Actor with MDCContextAware {
|
|||
def sendResponse(pkt : PlanetSideGamePacket) : ByteVector = {
|
||||
log.trace("CRYPTO SEND GAME: " + pkt)
|
||||
val pktEncoded = PacketCoding.EncodePacket(pkt)
|
||||
|
||||
pktEncoded match {
|
||||
case Failure(e) =>
|
||||
case Failure(_) =>
|
||||
log.error(s"Failed to encode packet ${pkt.getClass.getName} when sending response")
|
||||
ByteVector.empty
|
||||
|
||||
case Successful(v) =>
|
||||
val bytes = v.toByteVector
|
||||
|
||||
MDC("sessionId") = sessionId.toString
|
||||
leftRef !> ResponsePacket(bytes)
|
||||
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,19 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
import java.net.{InetAddress, InetSocketAddress}
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
|
||||
import net.psforever.packet.{PlanetSideGamePacket, _}
|
||||
import net.psforever.packet.control._
|
||||
import net.psforever.packet.game._
|
||||
import org.log4s.MDC
|
||||
import scodec.Attempt.{Failure, Successful}
|
||||
import scodec.bits._
|
||||
import MDCContextAware.Implicits._
|
||||
import com.github.mauricio.async.db.{Connection, QueryResult, RowData}
|
||||
import com.github.mauricio.async.db.mysql.MySQLConnection
|
||||
import com.github.mauricio.async.db.mysql.exceptions.MySQLException
|
||||
import com.github.mauricio.async.db.mysql.util.URLParser
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
|
||||
import scala.concurrent.{Await, Future}
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Random
|
||||
|
||||
class LoginSessionActor extends Actor with MDCContextAware {
|
||||
private[this] val log = org.log4s.getLogger
|
||||
|
|
@ -29,7 +25,7 @@ class LoginSessionActor extends Actor with MDCContextAware {
|
|||
var leftRef : ActorRef = ActorRef.noSender
|
||||
var rightRef : ActorRef = ActorRef.noSender
|
||||
|
||||
var updateServerListTask : Cancellable = null
|
||||
var updateServerListTask : Cancellable = LoginSessionActor.DefaultCancellable
|
||||
|
||||
override def postStop() = {
|
||||
if(updateServerListTask != null)
|
||||
|
|
@ -39,12 +35,17 @@ class LoginSessionActor extends Actor with MDCContextAware {
|
|||
def receive = Initializing
|
||||
|
||||
def Initializing : Receive = {
|
||||
case HelloFriend(sessionId, right) =>
|
||||
this.sessionId = sessionId
|
||||
case HelloFriend(aSessionId, pipe) =>
|
||||
this.sessionId = aSessionId
|
||||
leftRef = sender()
|
||||
rightRef = right.asInstanceOf[ActorRef]
|
||||
|
||||
if(pipe.hasNext) {
|
||||
rightRef = pipe.next
|
||||
rightRef !> HelloFriend(aSessionId, pipe)
|
||||
} else {
|
||||
rightRef = sender()
|
||||
}
|
||||
context.become(Started)
|
||||
|
||||
case _ =>
|
||||
log.error("Unknown message")
|
||||
context.stop(self)
|
||||
|
|
@ -91,7 +92,7 @@ class LoginSessionActor extends Actor with MDCContextAware {
|
|||
/// 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) =>
|
||||
log.trace(s"SYNC: ${sync}")
|
||||
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,
|
||||
|
|
@ -131,10 +132,9 @@ class LoginSessionActor extends Actor with MDCContextAware {
|
|||
val future: Future[QueryResult] = connection.sendPreparedStatement("SELECT * FROM accounts where username=?", Array(username))
|
||||
|
||||
val mapResult: Future[Any] = future.map(queryResult => queryResult.rows match {
|
||||
case Some(resultSet) => {
|
||||
case Some(resultSet) =>
|
||||
val row : RowData = resultSet.head
|
||||
row(0)
|
||||
}
|
||||
case None => -1
|
||||
}
|
||||
)
|
||||
|
|
@ -161,12 +161,12 @@ class LoginSessionActor extends Actor with MDCContextAware {
|
|||
// TODO: prevent multiple LoginMessages from being processed in a row!! We need a state machine
|
||||
import game.LoginRespMessage._
|
||||
|
||||
val clientVersion = s"Client Version: ${majorVersion}.${minorVersion}.${revision}, ${buildDate}"
|
||||
val clientVersion = s"Client Version: $majorVersion.$minorVersion.$revision, $buildDate"
|
||||
|
||||
if(token.isDefined)
|
||||
log.info(s"New login UN:$username Token:${token.get}. ${clientVersion}")
|
||||
log.info(s"New login UN:$username Token:${token.get}. $clientVersion")
|
||||
else
|
||||
log.info(s"New login UN:$username PW:$password. ${clientVersion}")
|
||||
log.info(s"New login UN:$username PW:$password. $clientVersion")
|
||||
|
||||
// This is temporary until a schema has been developed
|
||||
//val loginSucceeded = accountLookup(username, password.getOrElse(token.get))
|
||||
|
|
@ -187,16 +187,16 @@ class LoginSessionActor extends Actor with MDCContextAware {
|
|||
val response = LoginRespMessage(newToken, LoginError.BadUsernameOrPassword, StationError.AccountActive,
|
||||
StationSubscriptionStatus.Active, 685276011, username, 10001)
|
||||
|
||||
log.info(s"Failed login to account ${username}")
|
||||
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}'")
|
||||
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 default => log.debug(s"Unhandled GamePacket $pkt")
|
||||
}
|
||||
|
||||
def updateServerList() = {
|
||||
|
|
@ -228,3 +228,10 @@ class LoginSessionActor extends Actor with MDCContextAware {
|
|||
rightRef !> RawPacket(pkt)
|
||||
}
|
||||
}
|
||||
|
||||
object LoginSessionActor {
|
||||
final val DefaultCancellable = new Cancellable() {
|
||||
def isCancelled : Boolean = true
|
||||
def cancel : Boolean = true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
213
pslogin/src/main/scala/PacketCodingActor.scala
Normal file
213
pslogin/src/main/scala/PacketCodingActor.scala
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
import akka.actor.{Actor, ActorRef, MDCContextAware}
|
||||
import net.psforever.packet._
|
||||
import scodec.Attempt.{Failure, Successful}
|
||||
import scodec.bits._
|
||||
import org.log4s.MDC
|
||||
import MDCContextAware.Implicits._
|
||||
import net.psforever.packet.control.{HandleGamePacket, SlottedMetaPacket}
|
||||
|
||||
/**
|
||||
* In between the network side and the higher functioning side of the simulation:
|
||||
* accept packets and transform them into a sequence of data (encoding), and
|
||||
* accept a sequence of data and transform it into s packet (decoding).<br>
|
||||
* <br>
|
||||
* Following the standardization of the `SessionRouter` pipeline, the throughput of this `Actor` has directionality.
|
||||
* The "network," where the encoded data comes and goes, is assumed to be `leftRef`.
|
||||
* The "simulation", where the decoded packets come and go, is assumed to be `rightRef`.
|
||||
* `rightRef` can accept a sequence that looks like encoded data but it will merely pass out the same sequence.
|
||||
* Likewise, `leftRef` accepts decoded packets but merely ejects the same packets without doing any work on them.
|
||||
* The former functionality is anticipated.
|
||||
* The latter functionality is deprecated.<br>
|
||||
* <br>
|
||||
* Encoded data leaving the `Actor` (`leftRef`) is limited by an upper bound capacity.
|
||||
* Sequences can not be larger than that bound or else they will be dropped.
|
||||
* This maximum transmission unit (MTU) is used to divide the encoded sequence into chunks of encoded data,
|
||||
* re-packaged into nested `ControlPacket` units, and each unit encoded.
|
||||
* The outer packaging is numerically consistent with a `subslot` that starts counting once the simulation starts.
|
||||
* The client is very specific about the `subslot` number and will reject out-of-order packets.
|
||||
* It resets to 0 each time this `Actor` starts up and the client reflects this functionality.
|
||||
*/
|
||||
class PacketCodingActor extends Actor with MDCContextAware {
|
||||
private var sessionId : Long = 0
|
||||
private var subslot : 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
|
||||
super.postStop()
|
||||
}
|
||||
|
||||
def receive = Initializing
|
||||
|
||||
def Initializing : Receive = {
|
||||
case HelloFriend(sharedSessionId, pipe) =>
|
||||
import MDCContextAware.Implicits._
|
||||
this.sessionId = sharedSessionId
|
||||
leftRef = sender()
|
||||
if(pipe.hasNext) {
|
||||
rightRef = pipe.next
|
||||
rightRef !> HelloFriend(sessionId, pipe)
|
||||
}
|
||||
else {
|
||||
rightRef = sender()
|
||||
}
|
||||
log.trace(s"Left sender ${leftRef.path.name}")
|
||||
context.become(Established)
|
||||
|
||||
case default =>
|
||||
log.error("Unknown message " + default)
|
||||
context.stop(self)
|
||||
}
|
||||
|
||||
def Established : Receive = {
|
||||
case RawPacket(msg) =>
|
||||
if(sender == rightRef) { //from LSA, WSA, etc., to network - encode
|
||||
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")
|
||||
}
|
||||
}
|
||||
//known elevated packet type
|
||||
case ctrl @ ControlPacket(_, packet) =>
|
||||
if(sender == rightRef) { //from LSA, WSA, to network - encode
|
||||
PacketCoding.EncodePacket(packet) match {
|
||||
case Successful(data) =>
|
||||
mtuLimit(data.toByteVector)
|
||||
case Failure(ex) =>
|
||||
log.error(s"Failed to encode a ControlPacket: $ex")
|
||||
}
|
||||
}
|
||||
else { //deprecated; ControlPackets should not be coming from this direction
|
||||
log.warn(s"DEPRECATED CONTROL PACKET SEND: $ctrl")
|
||||
MDC("sessionId") = sessionId.toString
|
||||
sendResponseRight(ctrl)
|
||||
}
|
||||
//known elevated packet type
|
||||
case game @ GamePacket(_, _, packet) =>
|
||||
if(sender == rightRef) { //from LSA, WSA, etc., to network - encode
|
||||
PacketCoding.EncodePacket(packet) match {
|
||||
case Successful(data) =>
|
||||
mtuLimit(data.toByteVector)
|
||||
case Failure(ex) =>
|
||||
log.error(s"Failed to encode a GamePacket: $ex")
|
||||
}
|
||||
}
|
||||
else { //deprecated; GamePackets should not be coming from this direction
|
||||
log.warn(s"DEPRECATED GAME PACKET SEND: $game")
|
||||
MDC("sessionId") = sessionId.toString
|
||||
sendResponseRight(game)
|
||||
}
|
||||
//etc
|
||||
case msg =>
|
||||
log.trace(s"PACKET SEND, LEFT: $msg")
|
||||
if(sender == rightRef) {
|
||||
MDC("sessionId") = sessionId.toString
|
||||
leftRef !> msg
|
||||
}
|
||||
else {
|
||||
MDC("sessionId") = sessionId.toString
|
||||
rightRef !> msg
|
||||
}
|
||||
// case default =>
|
||||
// failWithError(s"Invalid message '$default' received in state Established")
|
||||
}
|
||||
|
||||
def resetState() : Unit = {
|
||||
context.become(receive)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current subslot number.
|
||||
* Increment the `subslot` for the next time it is needed.
|
||||
* @return a 16u number starting at 0
|
||||
*/
|
||||
def Subslot : Int = {
|
||||
if(subslot == 65536) { //TODO what is the actual wrap number?
|
||||
subslot = 0
|
||||
subslot
|
||||
} else {
|
||||
val curr = subslot
|
||||
subslot += 1
|
||||
curr
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that an outbound packet is not too big to get stuck by the MTU.
|
||||
* If it is larger than the MTU, divide it up and re-package the sections.
|
||||
* Otherwise, send the data out like normal.
|
||||
* @param msg the encoded packet data
|
||||
*/
|
||||
def mtuLimit(msg : ByteVector) : Unit = {
|
||||
if(msg.length > PacketCodingActor.MTU_LIMIT_BYTES) {
|
||||
handleSplitPacket(PacketCoding.CreateControlPacket(HandleGamePacket(msg)))
|
||||
}
|
||||
else {
|
||||
sendResponseLeft(msg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a `ControlPacket` into `ByteVector` data for splitting.
|
||||
* @param cont the original `ControlPacket`
|
||||
*/
|
||||
def handleSplitPacket(cont : ControlPacket) : Unit = {
|
||||
PacketCoding.getPacketDataForEncryption(cont) match {
|
||||
case Successful((_, data)) =>
|
||||
handleSplitPacket(data)
|
||||
case Failure(ex) =>
|
||||
log.error(s"$ex")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept `ByteVector` data, representing a `ControlPacket`, and split it into chunks.
|
||||
* The chunks should not be blocked by the MTU.
|
||||
* Send each chunk (towards the network) as it is converted.
|
||||
* @param data `ByteVector` data to be split
|
||||
*/
|
||||
def handleSplitPacket(data : ByteVector) : Unit = {
|
||||
val lim = PacketCodingActor.MTU_LIMIT_BYTES - 4 //4 bytes is the base size of SlottedMetaPacket
|
||||
data.grouped(lim).foreach(bvec => {
|
||||
val pkt = PacketCoding.CreateControlPacket(SlottedMetaPacket(4, Subslot, bvec))
|
||||
PacketCoding.EncodePacket(pkt.packet) match {
|
||||
case Successful(bdata) =>
|
||||
sendResponseLeft(bdata.toByteVector)
|
||||
case f @ Failure(_) =>
|
||||
log.error(s"$f")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Encoded sequence of data going towards the network.
|
||||
* @param cont the data
|
||||
*/
|
||||
def sendResponseLeft(cont : ByteVector) : Unit = {
|
||||
log.trace("PACKET SEND, LEFT: " + cont)
|
||||
MDC("sessionId") = sessionId.toString
|
||||
leftRef !> RawPacket(cont)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoded packet going towards the simulation.
|
||||
* @param cont the packet
|
||||
*/
|
||||
def sendResponseRight(cont : PlanetSidePacketContainer) : Unit = {
|
||||
log.trace("PACKET SEND, RIGHT: " + cont)
|
||||
MDC("sessionId") = sessionId.toString
|
||||
rightRef !> cont
|
||||
}
|
||||
}
|
||||
|
||||
object PacketCodingActor {
|
||||
final val MTU_LIMIT_BYTES : Int = 467
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ import java.net.InetAddress
|
|||
import java.io.File
|
||||
import java.util.Locale
|
||||
|
||||
import akka.actor.{ActorSystem, Props}
|
||||
import akka.actor.{ActorRef, ActorSystem, Props}
|
||||
import akka.routing.RandomPool
|
||||
import ch.qos.logback.classic.LoggerContext
|
||||
import ch.qos.logback.classic.joran.JoranConfigurator
|
||||
|
|
@ -24,17 +24,16 @@ import scala.collection.JavaConverters._
|
|||
import scala.concurrent.Await
|
||||
import scala.concurrent.duration._
|
||||
|
||||
|
||||
object PsLogin {
|
||||
private val logger = org.log4s.getLogger
|
||||
|
||||
var args : Array[String] = Array()
|
||||
var config : java.util.Map[String,Object] = null
|
||||
implicit var system : akka.actor.ActorSystem = null
|
||||
var loginRouter : akka.actor.Props = null
|
||||
var worldRouter : akka.actor.Props = null
|
||||
var loginListener : akka.actor.ActorRef = null
|
||||
var worldListener : akka.actor.ActorRef = null
|
||||
implicit var system : ActorSystem = null
|
||||
var loginRouter : Props = Props.empty
|
||||
var worldRouter : Props = Props.empty
|
||||
var loginListener : ActorRef = ActorRef.noSender
|
||||
var worldListener : ActorRef = ActorRef.noSender
|
||||
|
||||
def banner() : Unit = {
|
||||
println(ansi().fgBright(BLUE).a(""" ___ ________"""))
|
||||
|
|
@ -178,10 +177,12 @@ object PsLogin {
|
|||
*/
|
||||
val loginTemplate = List(
|
||||
SessionPipeline("crypto-session-", Props[CryptoSessionActor]),
|
||||
SessionPipeline("packet-session-", Props[PacketCodingActor]),
|
||||
SessionPipeline("login-session-", Props[LoginSessionActor])
|
||||
)
|
||||
val worldTemplate = List(
|
||||
SessionPipeline("crypto-session-", Props[CryptoSessionActor]),
|
||||
SessionPipeline("packet-session-", Props[PacketCodingActor]),
|
||||
SessionPipeline("world-session-", Props[WorldSessionActor])
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,10 @@ class Session(val sessionId : Long,
|
|||
a
|
||||
}
|
||||
|
||||
pipeline.head ! HelloFriend(sessionId, pipeline.tail.head)
|
||||
val pipelineIter = pipeline.iterator
|
||||
if(pipelineIter.hasNext) {
|
||||
pipelineIter.next ! HelloFriend(sessionId, pipelineIter)
|
||||
}
|
||||
|
||||
// statistics
|
||||
var bytesSent : Long = 0
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import akka.util.ByteString
|
|||
final case class ReceivedPacket(msg : ByteVector, from : InetSocketAddress)
|
||||
final case class SendPacket(msg : ByteVector, to : InetSocketAddress)
|
||||
final case class Hello()
|
||||
final case class HelloFriend(sessionId : Long, next: ActorRef)
|
||||
final case class HelloFriend(sessionId : Long, next: Iterator[ActorRef])
|
||||
|
||||
class UdpListener(nextActorProps : Props,
|
||||
nextActorName : String,
|
||||
|
|
|
|||
|
|
@ -60,16 +60,20 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
def receive = Initializing
|
||||
|
||||
def Initializing : Receive = {
|
||||
case HelloFriend(inSessionId, right) =>
|
||||
case HelloFriend(inSessionId, pipe) =>
|
||||
this.sessionId = inSessionId
|
||||
leftRef = sender()
|
||||
rightRef = right.asInstanceOf[ActorRef]
|
||||
|
||||
if(pipe.hasNext) {
|
||||
rightRef = pipe.next
|
||||
rightRef !> HelloFriend(sessionId, pipe)
|
||||
} else {
|
||||
rightRef = sender()
|
||||
}
|
||||
context.become(Started)
|
||||
ServiceManager.serviceManager ! Lookup("avatar")
|
||||
ServiceManager.serviceManager ! Lookup("accessor1")
|
||||
ServiceManager.serviceManager ! Lookup("taskResolver")
|
||||
|
||||
context.become(Started)
|
||||
case _ =>
|
||||
log.error("Unknown message")
|
||||
context.stop(self)
|
||||
|
|
@ -537,8 +541,18 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
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")
|
||||
|
||||
case default =>
|
||||
log.debug(s"Unhandled ControlPacket $default")
|
||||
log.warn(s"Unhandled ControlPacket $default")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue