mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-07-15 16:34:42 +00:00
Another Reset Sequence (#1358)
* proper reset sequence handling, with an added small refactor to the marshalling process * reset sequence at 0x8000, but only when retrieving a new sequence number; caching common flag values when marshalling packets
This commit is contained in:
parent
575f5fd7fc
commit
1b31b3bad7
2 changed files with 95 additions and 85 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,13 @@ 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 >= 0x8000) {
|
||||||
// TODO resetting the sequence to 0 causes a client crash
|
send(ResetSequence(), Some(outSequence), crypto)
|
||||||
// but that does not happen when we always send the same number
|
performResetSequenceReset()
|
||||||
// the solution is most likely to send the proper ResetSequence payload
|
0
|
||||||
// send(ResetSequence(), None, crypto)
|
|
||||||
// outSequence = -1
|
|
||||||
// return nextSequence
|
|
||||||
outSequence
|
|
||||||
} else {
|
} else {
|
||||||
outSequence += 1
|
outSequence += 1
|
||||||
outSequence
|
outSequence
|
||||||
|
|
@ -257,6 +253,16 @@ 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 = {
|
||||||
|
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,97 @@ 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
|
marshalPacket(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) => marshalPacket(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 {
|
/** Common flags for normal encrypted packets. */
|
||||||
|
private val normalSecuredFlags: BitVector = PlanetSidePacketFlags.codec
|
||||||
|
.encode(PlanetSidePacketFlags(PacketType.Normal, secured = true))
|
||||||
|
.require
|
||||||
|
/** Common flags for normal unencrypted packets. */
|
||||||
|
private val normalNotSecuredFlags: BitVector = PlanetSidePacketFlags.codec
|
||||||
|
.encode(PlanetSidePacketFlags(PacketType.Normal, secured = false))
|
||||||
|
.require
|
||||||
|
/** Common flags for crypto unencrypted packets (that's not illogical). */
|
||||||
|
private val cryptoNotSecuredFlags: BitVector = PlanetSidePacketFlags.codec
|
||||||
|
.encode(PlanetSidePacketFlags(PacketType.Crypto, secured = false))
|
||||||
|
.require
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 marshalPacket(
|
||||||
|
packet: PlanetSidePacket,
|
||||||
|
sequence: BitVector,
|
||||||
|
crypto: Option[CryptoCoding]
|
||||||
|
): 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
|
// these 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 payload = hex"00".bits ++ encryptedPayload.bits
|
||||||
(
|
Successful(normalSecuredFlags ++ sequence ++ payload)
|
||||||
PlanetSidePacketFlags.codec.encode(PlanetSidePacketFlags(PacketType.Normal, secured = true)).require,
|
}
|
||||||
// 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 =>
|
||||||
(
|
Successful(normalNotSecuredFlags ++ sequence ++ _payload)
|
||||||
PlanetSidePacketFlags.codec.encode(PlanetSidePacketFlags(PacketType.Normal, secured = false)).require,
|
}
|
||||||
_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 =>
|
||||||
(
|
Successful(cryptoNotSecuredFlags ++ sequence ++ _payload)
|
||||||
PlanetSidePacketFlags.codec.encode(PlanetSidePacketFlags(PacketType.Crypto, secured = false)).require,
|
}
|
||||||
_payload
|
case packet: PlanetSideResetSequencePacket if crypto.isDefined =>
|
||||||
)
|
encodePacket(packet)
|
||||||
case f @ Failure(_) => return f
|
.flatMap { _payload => crypto.get.encrypt(_payload.bytes) }
|
||||||
}
|
.flatMap { encryptedPayload =>
|
||||||
case packet: PlanetSideResetSequencePacket =>
|
//uncommon, so it's fine to not cache these flags (above)
|
||||||
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 _ =>
|
||||||
.encode(PlanetSidePacketFlags(PacketType.ResetSequence, secured = false))
|
Failure(Err(s"can not marshal $packet, sequence? $sequence, crypto state? ${crypto.isDefined}"))
|
||||||
.require,
|
|
||||||
_payload
|
|
||||||
)
|
|
||||||
case f @ Failure(_) => return f
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Successful(flags ++ seq ++ payload)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -131,17 +137,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 +365,5 @@ object PacketCoding {
|
||||||
|
|
||||||
Successful(payloadNoMac)
|
Successful(payloadNoMac)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue