mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-07-15 00:14:43 +00:00
proper reset sequence handling, with an added small refactor to the marshalling process
This commit is contained in:
parent
575f5fd7fc
commit
aac0ecfd3e
2 changed files with 87 additions and 84 deletions
|
|
@ -6,7 +6,6 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
|
||||||
import akka.io.Udp
|
import akka.io.Udp
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.security.{SecureRandom, Security}
|
import java.security.{SecureRandom, Security}
|
||||||
|
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
|
|
||||||
|
|
@ -26,6 +25,7 @@ import net.psforever.packet.game._
|
||||||
import net.psforever.packet.crypto._
|
import net.psforever.packet.crypto._
|
||||||
import net.psforever.packet.game.{ChangeFireModeMessage, CharacterInfoMessage, KeepAliveMessage, PingMsg}
|
import net.psforever.packet.game.{ChangeFireModeMessage, CharacterInfoMessage, KeepAliveMessage, PingMsg}
|
||||||
import net.psforever.packet.PacketCoding.CryptoCoding
|
import net.psforever.packet.PacketCoding.CryptoCoding
|
||||||
|
import net.psforever.packet.reset.ResetSequence
|
||||||
import net.psforever.util.{Config, DiffieHellman, Md5Mac}
|
import net.psforever.util.{Config, DiffieHellman, Md5Mac}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -219,17 +219,12 @@ class MiddlewareActor(
|
||||||
* Increment the outbound sequence number.
|
* Increment the outbound sequence number.
|
||||||
* The previous sequence number is returned.
|
* The previous sequence number is returned.
|
||||||
* The fidelity of the sequence field in packets is 16 bits, so wrap back to 0 after 65535.
|
* The fidelity of the sequence field in packets is 16 bits, so wrap back to 0 after 65535.
|
||||||
* @return
|
* @return next sequence number
|
||||||
*/
|
*/
|
||||||
private def nextSequence: Int = {
|
private def nextSequence: Int = {
|
||||||
if (outSequence >= 0xffff) {
|
if (outSequence >= 0xffff) {
|
||||||
// TODO resetting the sequence to 0 causes a client crash
|
performResetSequenceReset()
|
||||||
// but that does not happen when we always send the same number
|
0
|
||||||
// the solution is most likely to send the proper ResetSequence payload
|
|
||||||
// send(ResetSequence(), None, crypto)
|
|
||||||
// outSequence = -1
|
|
||||||
// return nextSequence
|
|
||||||
outSequence
|
|
||||||
} else {
|
} else {
|
||||||
outSequence += 1
|
outSequence += 1
|
||||||
outSequence
|
outSequence
|
||||||
|
|
@ -257,6 +252,17 @@ class MiddlewareActor(
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the sequence is reset,
|
||||||
|
* the server resets both the expectant packet sequence id and the delivered packet sequence id
|
||||||
|
* and gaslight the client to send packets using refreshed (delivered) id's.
|
||||||
|
*/
|
||||||
|
private def performResetSequenceReset(): Unit = {
|
||||||
|
send(ResetSequence(), None, crypto)
|
||||||
|
outSequence = 0
|
||||||
|
inSequence = 0
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do not bundle these packets together with other packets
|
* Do not bundle these packets together with other packets
|
||||||
*/
|
*/
|
||||||
|
|
@ -623,7 +629,7 @@ class MiddlewareActor(
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
case _: PlanetSideResetSequencePacket =>
|
case _: PlanetSideResetSequencePacket =>
|
||||||
log.error(s"Unexpected crypto packet: received a PlanetSideResetSequencePacket when it should never happen")
|
performResetSequenceReset()
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,91 +23,90 @@ object PacketCoding {
|
||||||
final val PLANETSIDE_MIN_PACKET_SIZE = 1
|
final val PLANETSIDE_MIN_PACKET_SIZE = 1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform a kind of packet into the sequence of data that represents it.
|
* Transform a kind of packet into the sequence of data that represents it.
|
||||||
* Wraps around the encoding process for all valid packet container types.
|
* Wraps around the encoding process for all valid packet container types.
|
||||||
* @param packet the packet to encode
|
* @param packet packet to encode
|
||||||
* @param sequence the packet's sequence number. Must be set for all non ControlPacket packets (but always for encrypted packets).
|
* @param sequenceOpt packet's sequence number;
|
||||||
* @param crypto if set, encrypt final payload
|
* must be set for all packets that are not `ControlPacket`;
|
||||||
* @return a `BitVector` translated from the packet's data
|
* always for encrypted packets
|
||||||
*/
|
* @param crypto if set, encrypt final payload
|
||||||
|
* @return a `BitVector` translated from the packet's data
|
||||||
|
*/
|
||||||
def marshalPacket(
|
def marshalPacket(
|
||||||
packet: PlanetSidePacket,
|
packet: PlanetSidePacket,
|
||||||
sequence: Option[Int] = None,
|
sequenceOpt: Option[Int] = None,
|
||||||
crypto: Option[CryptoCoding] = None
|
crypto: Option[CryptoCoding] = None
|
||||||
): Attempt[BitVector] = {
|
): Attempt[BitVector] = {
|
||||||
val seq = packet match {
|
packet match {
|
||||||
case _: PlanetSideControlPacket if crypto.isEmpty => BitVector.empty
|
case _: PlanetSideControlPacket if crypto.isEmpty =>
|
||||||
case _: PlanetSideResetSequencePacket => BitVector.empty
|
marshalPacketWithSequence(packet, BitVector.empty, crypto)
|
||||||
|
case _: PlanetSideResetSequencePacket =>
|
||||||
|
marshalPacketWithSequence(packet, BitVector.empty, crypto)
|
||||||
case _ =>
|
case _ =>
|
||||||
sequence match {
|
sequenceOpt match {
|
||||||
case Some(_sequence) =>
|
case Some(sequence) =>
|
||||||
uint16L.encode(_sequence) match {
|
uint16L.encode(sequence) match {
|
||||||
case Successful(_seq) => _seq
|
case Successful(encodedSequence) => marshalPacketWithSequence(packet, encodedSequence, crypto)
|
||||||
case f @ Failure(_) => return f
|
case f: Failure => f
|
||||||
}
|
}
|
||||||
case None =>
|
case None =>
|
||||||
return Failure(Err(s"Missing sequence"))
|
Failure(Err(s"can not marshal $packet without a proper sequence id"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val (flags, payload) = packet match {
|
/**
|
||||||
|
* Transform a kind of packet into the sequence of data that represents it.
|
||||||
|
* @param packet the packet to encode
|
||||||
|
* @param sequence the packet's sequence number. Must be set for all non ControlPacket packets (but always for encrypted packets).
|
||||||
|
* @param crypto if set, encrypt final payload
|
||||||
|
* @return a `BitVector` translated from the packet's data
|
||||||
|
*/
|
||||||
|
private def marshalPacketWithSequence(
|
||||||
|
packet: PlanetSidePacket,
|
||||||
|
sequence: BitVector,
|
||||||
|
crypto: Option[CryptoCoding] = None
|
||||||
|
): Attempt[BitVector] = {
|
||||||
|
packet match {
|
||||||
case _: PlanetSideGamePacket | _: PlanetSideControlPacket if crypto.isDefined =>
|
case _: PlanetSideGamePacket | _: PlanetSideControlPacket if crypto.isDefined =>
|
||||||
encodePacket(packet) match {
|
encodePacket(packet)
|
||||||
case Successful(_payload) =>
|
.flatMap { _payload => crypto.get.encrypt(_payload.bytes) }
|
||||||
val encryptedPayload = crypto.get.encrypt(_payload.bytes) match {
|
.flatMap { encryptedPayload =>
|
||||||
case Successful(p) => p
|
// encrypted packets need to be aligned to 4 bytes before encryption/decryption
|
||||||
case f: Failure => return f
|
// first byte are flags, second is the sequence, and third is the pad
|
||||||
}
|
val flags = PlanetSidePacketFlags.codec.encode(PlanetSidePacketFlags(PacketType.Normal, secured = true)).require
|
||||||
(
|
val payload = hex"00".bits ++ encryptedPayload.bits
|
||||||
PlanetSidePacketFlags.codec.encode(PlanetSidePacketFlags(PacketType.Normal, secured = true)).require,
|
Successful(flags ++ sequence ++ payload)
|
||||||
// encrypted packets need to be aligned to 4 bytes before encryption/decryption
|
}
|
||||||
// first byte are flags, second is the sequence, and third is the pad
|
|
||||||
hex"00".bits ++ encryptedPayload.bits
|
|
||||||
)
|
|
||||||
case f @ Failure(_) => return f
|
|
||||||
}
|
|
||||||
case packet: PlanetSideGamePacket =>
|
case packet: PlanetSideGamePacket =>
|
||||||
encodePacket(packet) match {
|
encodePacket(packet)
|
||||||
case Successful(_payload) =>
|
.flatMap { _payload =>
|
||||||
(
|
val flags = PlanetSidePacketFlags.codec.encode(PlanetSidePacketFlags(PacketType.Normal, secured = false)).require
|
||||||
PlanetSidePacketFlags.codec.encode(PlanetSidePacketFlags(PacketType.Normal, secured = false)).require,
|
Successful(flags ++ sequence ++ _payload)
|
||||||
_payload
|
}
|
||||||
)
|
|
||||||
case f @ Failure(_) => return f
|
|
||||||
}
|
|
||||||
case packet: PlanetSideControlPacket =>
|
case packet: PlanetSideControlPacket =>
|
||||||
encodePacket(packet) match {
|
encodePacket(packet)
|
||||||
case Successful(_payload) =>
|
.flatMap { _payload =>
|
||||||
(
|
Successful(sequence ++ _payload)
|
||||||
// control packets don't have flags
|
}
|
||||||
BitVector.empty,
|
|
||||||
_payload
|
|
||||||
)
|
|
||||||
case f @ Failure(_) => return f
|
|
||||||
}
|
|
||||||
case packet: PlanetSideCryptoPacket =>
|
case packet: PlanetSideCryptoPacket =>
|
||||||
encodePacket(packet) match {
|
encodePacket(packet)
|
||||||
case Successful(_payload) =>
|
.flatMap { _payload =>
|
||||||
(
|
val flags = PlanetSidePacketFlags.codec.encode(PlanetSidePacketFlags(PacketType.Crypto, secured = false)).require
|
||||||
PlanetSidePacketFlags.codec.encode(PlanetSidePacketFlags(PacketType.Crypto, secured = false)).require,
|
Successful(flags ++ sequence ++ _payload)
|
||||||
_payload
|
}
|
||||||
)
|
case packet: PlanetSideResetSequencePacket if crypto.isDefined =>
|
||||||
case f @ Failure(_) => return f
|
encodePacket(packet)
|
||||||
}
|
.flatMap { _payload => crypto.get.encrypt(_payload.bytes) }
|
||||||
case packet: PlanetSideResetSequencePacket =>
|
.flatMap { encryptedPayload =>
|
||||||
encodePacket(packet) match {
|
val flags = PlanetSidePacketFlags.codec.encode(PlanetSidePacketFlags(PacketType.ResetSequence, secured = true)).require
|
||||||
case Successful(_payload) =>
|
Successful(flags ++ sequence ++ encryptedPayload.bits)
|
||||||
(
|
}
|
||||||
PlanetSidePacketFlags.codec
|
case _: PlanetSideResetSequencePacket =>
|
||||||
.encode(PlanetSidePacketFlags(PacketType.ResetSequence, secured = false))
|
Failure(Err(s"can not marshal a ResetSequence packet when crypto state is unavailable"))
|
||||||
.require,
|
case _ =>
|
||||||
_payload
|
Failure(Err(s"can not marshal $packet"))
|
||||||
)
|
|
||||||
case f @ Failure(_) => return f
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Successful(flags ++ seq ++ payload)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -131,17 +130,16 @@ object PacketCoding {
|
||||||
case Successful(opcode) => Successful(opcode ++ payload)
|
case Successful(opcode) => Successful(opcode ++ payload)
|
||||||
case f @ Failure(_) => f
|
case f @ Failure(_) => f
|
||||||
}
|
}
|
||||||
|
|
||||||
case packet: PlanetSideResetSequencePacket =>
|
case packet: PlanetSideResetSequencePacket =>
|
||||||
ResetSequenceOpcode.codec.encode(packet.opcode) match {
|
ResetSequenceOpcode.codec.encode(packet.opcode) match {
|
||||||
case Successful(opcode) => Successful(opcode)
|
case Successful(opcode) => Successful(opcode)
|
||||||
case f @ Failure(_) => f
|
case f: Failure => f
|
||||||
}
|
}
|
||||||
|
|
||||||
case _ =>
|
case _ =>
|
||||||
Failure(Err("packet not supported"))
|
Failure(Err("packet not supported"))
|
||||||
}
|
}
|
||||||
case f @ Failure(_) => f
|
case f: Failure => f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -360,6 +358,5 @@ object PacketCoding {
|
||||||
|
|
||||||
Successful(payloadNoMac)
|
Successful(payloadNoMac)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue