PSF-BotServer/pslogin/src/main/scala/PacketCodingActor.scala
FateJH 294d5335c9 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
2017-08-25 21:20:07 -04:00

213 lines
7.4 KiB
Scala

// 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
}