mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
[packet] VNLWorldStatusMessage
Added VNL packet type from IDA. Moved definition in to its own file. Refactored PacketCoding MarshalPacket. The whole structure needs a rework. Now able to get a PlanetSide client to the server screen with a server of choice.
This commit is contained in:
parent
1643ecc1dd
commit
b8ff34c0f9
|
|
@ -59,10 +59,5 @@ object ControlPacketOpcode extends Enumeration {
|
|||
case default => (a : BitVector) => Attempt.failure(Err(s"Could not find a marshaller for control packet ${opcode}"))
|
||||
}
|
||||
|
||||
val storageType = uint8L
|
||||
|
||||
assert(maxId <= Math.pow(storageType.sizeBound.exact.get, 2),
|
||||
this.getClass.getCanonicalName + ": maxId exceeds primitive type")
|
||||
|
||||
implicit val codec: Codec[this.Value] = PacketHelpers.createEnumerationCodec(this, storageType)
|
||||
implicit val codec: Codec[this.Value] = PacketHelpers.createEnumerationCodec(this, uint8L)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ object GamePacketOpcode extends Enumeration {
|
|||
type Type = Value
|
||||
val
|
||||
|
||||
// Opcodes should have a marker every 10
|
||||
// Opcodes should have a marker every 10 (decimal)
|
||||
// OPCODE 0
|
||||
Unknown0,
|
||||
LoginMessage,
|
||||
LoginRespMessage,
|
||||
Unknown3,
|
||||
ConnectToWorldMessage,
|
||||
Unknown5,
|
||||
VNLWorldStatusMessage,
|
||||
UnknownMessage6,
|
||||
UnknownMessage7,
|
||||
PlayerStateMessage,
|
||||
|
|
@ -33,13 +33,9 @@ object GamePacketOpcode extends Enumeration {
|
|||
def getPacketDecoder(opcode : GamePacketOpcode.Type) : (BitVector) => Attempt[DecodeResult[PlanetSideGamePacket]] = opcode match {
|
||||
case LoginMessage => psforever.net.LoginMessage.decode
|
||||
case LoginRespMessage => psforever.net.LoginRespMessage.decode
|
||||
case VNLWorldStatusMessage => psforever.net.VNLWorldStatusMessage.decode
|
||||
case default => (a : BitVector) => Attempt.failure(Err(s"Could not find a marshaller for game packet ${opcode}"))
|
||||
}
|
||||
|
||||
val storageType = uint8L
|
||||
|
||||
assert(maxId <= Math.pow(storageType.sizeBound.exact.get, 2),
|
||||
this.getClass.getCanonicalName + ": maxId exceeds primitive type")
|
||||
|
||||
implicit val codec: Codec[this.Value] = PacketHelpers.createEnumerationCodec(this, storageType)
|
||||
implicit val codec: Codec[this.Value] = PacketHelpers.createEnumerationCodec(this, uint8L)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package psforever.net
|
||||
|
||||
import java.nio.charset.Charset
|
||||
|
||||
import scodec.{DecodeResult, Err, Codec, Attempt}
|
||||
import scodec.bits._
|
||||
import scodec.codecs._
|
||||
|
|
@ -14,22 +16,22 @@ sealed trait PlanetSidePacket extends Serializable {
|
|||
}
|
||||
|
||||
// Used by companion objects to create encoders and decoders
|
||||
sealed trait Marshallable[T] {
|
||||
trait Marshallable[T] {
|
||||
implicit val codec : Codec[T]
|
||||
def encode(a : T) : Attempt[BitVector] = codec.encode(a)
|
||||
// assert that when decoding a marshallable type, that no bits are left over
|
||||
def decode(a : BitVector) : Attempt[DecodeResult[T]] = codec.complete.decode(a)
|
||||
def decode(a : BitVector) : Attempt[DecodeResult[T]] = codec.decode(a)
|
||||
}
|
||||
|
||||
sealed trait PlanetSideGamePacket extends PlanetSidePacket {
|
||||
trait PlanetSideGamePacket extends PlanetSidePacket {
|
||||
def opcode : GamePacketOpcode.Type
|
||||
}
|
||||
|
||||
sealed trait PlanetSideControlPacket extends PlanetSidePacket {
|
||||
trait PlanetSideControlPacket extends PlanetSidePacket {
|
||||
def opcode : ControlPacketOpcode.Type
|
||||
}
|
||||
|
||||
sealed trait PlanetSideCryptoPacket extends PlanetSidePacket {
|
||||
trait PlanetSideCryptoPacket extends PlanetSidePacket {
|
||||
def opcode : CryptoPacketOpcode.Type
|
||||
}
|
||||
|
||||
|
|
@ -143,6 +145,17 @@ object LoginMessage extends Marshallable[LoginMessage] {
|
|||
|
||||
type Struct = String :: Option[String] :: Option[String] :: HNil
|
||||
|
||||
/* Okay, okay, here's what's happening here:
|
||||
|
||||
PlanetSide's *wonderful* packet design reuses packets for different encodings.
|
||||
What we have here is that depending on a boolean in the LoginPacket, we will either
|
||||
be decoding a username & password OR a token & username. Yeah...so this doesn't
|
||||
really fit in to a fixed packet decoding scheme.
|
||||
|
||||
The below code abstracts away from this by using pattern matching.
|
||||
The scodec specific part is the either(...) Codec, which decodes one bit and chooses
|
||||
Left or Right depending on it.
|
||||
*/
|
||||
implicit val credentialChoice : Codec[Struct] = {
|
||||
type InStruct = Either[String :: String :: HNil, String :: String :: HNil]
|
||||
|
||||
|
|
@ -307,12 +320,7 @@ object PacketType extends Enumeration(1) {
|
|||
type Type = Value
|
||||
val ResetSequence, Unknown2, Crypto, Normal = Value
|
||||
|
||||
val storageType = uint4L
|
||||
|
||||
assert(maxId <= Math.pow(storageType.sizeBound.exact.get, 2),
|
||||
this.getClass.getCanonicalName + ": maxId exceeds primitive type")
|
||||
|
||||
implicit val codec: Codec[this.Value] = PacketHelpers.createEnumerationCodec(this, storageType)
|
||||
implicit val codec: Codec[this.Value] = PacketHelpers.createEnumerationCodec(this, uint4L)
|
||||
}
|
||||
|
||||
object PlanetSidePacketFlags extends Marshallable[PlanetSidePacketFlags] {
|
||||
|
|
@ -327,10 +335,16 @@ object PlanetSidePacketFlags extends Marshallable[PlanetSidePacketFlags] {
|
|||
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
// TODO: figure out why I can't insert codecs without using a new case class
|
||||
// Notes: https://mpilquist.github.io/blog/2013/06/09/scodec-part-3/
|
||||
// https://stackoverflow.com/questions/29585649/using-nested-case-classes-with-scodec
|
||||
// https://gist.github.com/travisbrown/3945529
|
||||
/*class MarshallableEnum[+T] extends Enumeration {
|
||||
type StorageType = Codec[Int]
|
||||
|
||||
implicit val storageType : StorageType = uint8
|
||||
|
||||
assert(maxId <= Math.pow(storageType.sizeBound.exact.get, 2),
|
||||
this.getClass.getCanonicalName + ": maxId exceeds primitive type")
|
||||
|
||||
implicit val codec: Codec[T] = PacketHelpers.createEnumerationCodec(this, storageType)
|
||||
}*/
|
||||
|
||||
object PacketHelpers {
|
||||
def emptyCodec[T](instance : T) = {
|
||||
|
|
@ -339,13 +353,14 @@ object PacketHelpers {
|
|||
Codec[HNil].xmap[T](from, to)
|
||||
}
|
||||
|
||||
|
||||
def createEnumerationCodec[E <: Enumeration](enum : E, storageCodec : Codec[Int]) : Codec[E#Value] = {
|
||||
type Struct = Int :: HNil
|
||||
val struct: Codec[Struct] = storageCodec.hlist
|
||||
|
||||
// Assure that the enum will always be able to fit in a N-bit int
|
||||
assert(enum.maxId <= Math.pow(storageCodec.sizeBound.exact.get, 2),
|
||||
this.getClass.getCanonicalName + ": maxId exceeds primitive type")
|
||||
enum.getClass.getCanonicalName + ": maxId exceeds primitive type")
|
||||
|
||||
def to(pkt: E#Value): Struct = {
|
||||
pkt.id :: HNil
|
||||
|
|
@ -372,15 +387,47 @@ object PacketHelpers {
|
|||
private def encodedStringSize : Codec[Int] = either(bool, uint(15), uint(7)).
|
||||
xmap[Int](
|
||||
(a : Either[Int, Int]) => a.fold[Int](a => a, a => a),
|
||||
(a : Int) => if(a > 0x7f) Left(a) else Right(a)
|
||||
(a : Int) =>
|
||||
// if the specified goes above 0x7f (127) then we need two bytes to represent it
|
||||
if(a > 0x7f) Left(a) else Right(a)
|
||||
)
|
||||
|
||||
private def encodedStringSizeWithPad(pad : Int) : Codec[Int] = either(bool, uint(15), uint(7)).
|
||||
xmap[Int](
|
||||
(a : Either[Int, Int]) => a.fold[Int](a => a, a => a),
|
||||
(a : Int) => if(a > 0x7f) Left(a) else Right(a)
|
||||
) <~ ignore(pad)
|
||||
/*private def encodedStringSizeWithLimit(limit : Int) : Codec[Int] = {
|
||||
either(bool, uint(15), uint(7)).
|
||||
exmap[Int](
|
||||
(a : Either[Int, Int]) => {
|
||||
val result = a.fold[Int](a => a, a => a)
|
||||
|
||||
if(result > limit)
|
||||
Attempt.failure(Err(s"Encoded string exceeded byte limit of $limit"))
|
||||
else
|
||||
Attempt.successful(result)
|
||||
},
|
||||
(a : Int) => {
|
||||
if(a > limit)
|
||||
return Attempt.failure(Err("adsf"))
|
||||
//return Left(Attempt.failure(Err(s"Encoded string exceeded byte limit of $limit")))
|
||||
|
||||
if(a > 0x7f)
|
||||
return Attempt.successful(Left(a))
|
||||
else
|
||||
Right(a)
|
||||
}
|
||||
)
|
||||
}*/
|
||||
|
||||
private def encodedStringSizeWithPad(pad : Int) : Codec[Int] = encodedStringSize <~ ignore(pad)
|
||||
|
||||
def encodedString : Codec[String] = variableSizeBytes(encodedStringSize, ascii)
|
||||
//def encodedStringWithLimit(limit : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithLimit(limit), ascii)
|
||||
def encodedStringAligned(adjustment : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithPad(adjustment), ascii)
|
||||
|
||||
/// Variable for the charset that planetside uses for unicode
|
||||
val utf16 = string(Charset.forName("UTF-16LE"))
|
||||
|
||||
/// An encoded *wide* string is twice the length of the given encoded size and half of the length of the
|
||||
/// input string. We use xmap to transform the encodedString codec as this change is just a division and multiply
|
||||
def encodedWideString : Codec[String] = variableSizeBytes(encodedStringSize.xmap(
|
||||
insize => insize*2,
|
||||
outSize => outSize/2), utf16)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,32 +7,41 @@ import scodec.bits._
|
|||
import scodec.{DecodeResult, Err, Attempt, Codec}
|
||||
import scodec.codecs.{uint16L, uint8L, uint4L, bytes}
|
||||
|
||||
// Packet containers
|
||||
/// Packet container base trait
|
||||
sealed trait PlanetSidePacketContainer
|
||||
|
||||
// A sequence, encrypted opcode, encrypted payload, and MD5MAC plus padding
|
||||
/// A sequence, encrypted opcode, encrypted payload, and implicit MD5MAC plus padding
|
||||
final case class EncryptedPacket(sequenceNumber : Int,
|
||||
payload : ByteVector) extends PlanetSidePacketContainer
|
||||
|
||||
// A sequence, and payload. Crypto packets have no discernible opcodes
|
||||
/// A sequence, and payload. Crypto packets have no discernible opcodes an rely off of implicit
|
||||
/// state to decode properly
|
||||
final case class CryptoPacket(sequenceNumber : Int,
|
||||
packet : PlanetSideCryptoPacket) extends PlanetSidePacketContainer
|
||||
|
||||
/// A sequenced game packet with an opcode and payload
|
||||
final case class GamePacket(opcode : GamePacketOpcode.Value,
|
||||
sequenceNumber : Int,
|
||||
packet : PlanetSideGamePacket) extends PlanetSidePacketContainer
|
||||
|
||||
// Just an opcode + payload (does not expect a response)
|
||||
/// Just an opcode + payload
|
||||
final case class ControlPacket(opcode : ControlPacketOpcode.Value,
|
||||
packet : PlanetSideControlPacket) extends PlanetSidePacketContainer
|
||||
|
||||
object PacketCoding {
|
||||
/// A lower bound on the packet size
|
||||
final val PLANETSIDE_MIN_PACKET_SIZE = 2
|
||||
|
||||
def UnmarshalPacket(msg : ByteVector) : Attempt[PlanetSidePacketContainer] = {
|
||||
UnmarshalPacket(msg, CryptoPacketOpcode.Ignore)
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a full and complete planetside packet as it would be sent on the wire, attempt to
|
||||
* decode it given an optional header and required payload. This function does all of the
|
||||
* hard work of making decisions along the way in order to decode a planetside packet to
|
||||
* completion.
|
||||
* @param msg the raw packet
|
||||
* @param cryptoState the current state of the connection's crypto. This is only used when decoding
|
||||
* crypto packets as they do not have opcodes
|
||||
* @return PlanetSidePacketContainer
|
||||
*/
|
||||
def UnmarshalPacket(msg : ByteVector, cryptoState : CryptoPacketOpcode.Type) : Attempt[PlanetSidePacketContainer] = {
|
||||
// check for a minimum length
|
||||
if(msg.length < PLANETSIDE_MIN_PACKET_SIZE)
|
||||
|
|
@ -47,6 +56,22 @@ object PacketCoding {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to decode a packet without specifying a crypto packet state.
|
||||
* Mostly used when there is no crypto state available, such as tests.
|
||||
* @param msg packet data bytes
|
||||
* @return PlanetSidePacketContainer
|
||||
*/
|
||||
def UnmarshalPacket(msg : ByteVector) : Attempt[PlanetSidePacketContainer] = {
|
||||
UnmarshalPacket(msg, CryptoPacketOpcode.Ignore)
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to UnmarshalPacket, but does not process any packet header and does not support
|
||||
* decoding of crypto packets. Mostly used in tests.
|
||||
* @param msg raw, unencrypted packet
|
||||
* @return PlanetSidePacket
|
||||
*/
|
||||
def DecodePacket(msg : ByteVector) : Attempt[PlanetSidePacket] = {
|
||||
// check for a minimum length
|
||||
if(msg.length < PLANETSIDE_MIN_PACKET_SIZE)
|
||||
|
|
@ -68,70 +93,110 @@ object PacketCoding {
|
|||
var opcodeEncoded : BitVector = BitVector.empty
|
||||
var payloadEncoded : BitVector = BitVector.empty
|
||||
|
||||
var controlPacket = false
|
||||
var sequenceNum = 0
|
||||
|
||||
// packet flags
|
||||
var hasFlags = true
|
||||
var secured = false
|
||||
var packetType = PacketType.Crypto
|
||||
|
||||
packet match {
|
||||
case GamePacket(opcode, seq, payload) =>
|
||||
val flags = PlanetSidePacketFlags(PacketType.Normal, secured = false)
|
||||
flagsEncoded = PlanetSidePacketFlags.codec.encode(flags).require
|
||||
secured = false
|
||||
packetType = PacketType.Normal
|
||||
sequenceNum = seq
|
||||
|
||||
GamePacketOpcode.codec.encode(opcode) match {
|
||||
case Failure(e) => return Attempt.failure(Err(s"Failed to marshal opcode in packet $opcode: " + e.messageWithContext))
|
||||
case Successful(p) => opcodeEncoded = p
|
||||
}
|
||||
|
||||
uint16L.encode(seq) match {
|
||||
case Failure(e) => return Attempt.failure(Err(s"Failed to marshal sequence in packet $opcode: " + e.messageWithContext))
|
||||
case Successful(p) => seqEncoded = p
|
||||
}
|
||||
|
||||
encodePacket(payload) match {
|
||||
case Failure(e) => return Attempt.failure(Err(s"Failed to marshal packet $opcode: " + e.messageWithContext))
|
||||
EncodePacket(payload) match {
|
||||
case f @ Failure(e) => return f
|
||||
case Successful(p) => payloadEncoded = p
|
||||
}
|
||||
case ControlPacket(opcode, payload) =>
|
||||
flagsEncoded = hex"00".bits
|
||||
controlPacket = true
|
||||
|
||||
ControlPacketOpcode.codec.encode(opcode) match {
|
||||
case Failure(e) => return Attempt.failure(Err(s"Failed to marshal opcode in packet $opcode: " + e.messageWithContext))
|
||||
case Successful(p) => opcodeEncoded = p
|
||||
}
|
||||
|
||||
encodePacket(payload) match {
|
||||
case Failure(e) => return Attempt.failure(Err(s"Failed to marshal packet $opcode: " + e.messageWithContext))
|
||||
EncodePacket(payload) match {
|
||||
case f @ Failure(e) => return f
|
||||
case Successful(p) => payloadEncoded = p
|
||||
}
|
||||
case CryptoPacket(seq, payload) =>
|
||||
val flags = PlanetSidePacketFlags(PacketType.Crypto, secured = false)
|
||||
flagsEncoded = PlanetSidePacketFlags.codec.encode(flags).require
|
||||
secured = false
|
||||
packetType = PacketType.Crypto
|
||||
sequenceNum = seq
|
||||
|
||||
uint16L.encode(seq) match {
|
||||
case Failure(e) => return Attempt.failure(Err(s"Failed to marshal sequence in packet $payload: " + e.messageWithContext))
|
||||
case Successful(p) => seqEncoded = p
|
||||
}
|
||||
|
||||
encodePacket(payload) match {
|
||||
case Failure(e) => return Attempt.failure(Err(s"Failed to marshal packet $payload: " + e.messageWithContext))
|
||||
EncodePacket(payload) match {
|
||||
case f @ Failure(e) => return f
|
||||
case Successful(p) => payloadEncoded = p
|
||||
}
|
||||
case EncryptedPacket(seq, payload) =>
|
||||
val flags = PlanetSidePacketFlags(PacketType.Normal, secured = true)
|
||||
flagsEncoded = PlanetSidePacketFlags.codec.encode(flags).require
|
||||
secured = true
|
||||
packetType = PacketType.Normal
|
||||
sequenceNum = seq
|
||||
|
||||
// encrypted packets need to be aligned to 4 bytes before encryption/decryption
|
||||
// first byte are flags, second and third the sequence, and fourth is the pad
|
||||
paddingEncoded = hex"00".bits
|
||||
|
||||
uint16L.encode(seq) match {
|
||||
case Failure(e) => return Attempt.failure(Err(s"Failed to marshal sequence in packet $payload: " + e.messageWithContext))
|
||||
case Successful(p) => seqEncoded = p
|
||||
}
|
||||
|
||||
payloadEncoded = payload.bits
|
||||
}
|
||||
|
||||
val flags = PlanetSidePacketFlags(packetType, secured = secured)
|
||||
|
||||
// crypto packets DONT have flags
|
||||
if(!controlPacket) {
|
||||
flagsEncoded = PlanetSidePacketFlags.codec.encode(flags).require
|
||||
|
||||
uint16L.encode(sequenceNum) match {
|
||||
case Failure(e) => return Attempt.failure(Err(s"Failed to marshal sequence in packet $packet: " + e.messageWithContext))
|
||||
case Successful(p) => seqEncoded = p
|
||||
}
|
||||
}
|
||||
|
||||
val finalPacket = flagsEncoded ++ seqEncoded ++ paddingEncoded ++ opcodeEncoded ++ payloadEncoded
|
||||
Attempt.successful(finalPacket)
|
||||
}
|
||||
|
||||
def EncodePacket(packet : PlanetSideControlPacket) : Attempt[BitVector] = {
|
||||
val opcode = packet.opcode
|
||||
var opcodeEncoded = BitVector.empty
|
||||
var payloadEncoded = BitVector.empty
|
||||
|
||||
ControlPacketOpcode.codec.encode(opcode) match {
|
||||
case Failure(e) => return Attempt.failure(Err(s"Failed to marshal opcode in control packet $opcode: " + e.messageWithContext))
|
||||
case Successful(p) => opcodeEncoded = p
|
||||
}
|
||||
|
||||
encodePacket(packet) match {
|
||||
case Failure(e) => return Attempt.failure(Err(s"Failed to marshal control packet $packet: " + e.messageWithContext))
|
||||
case Successful(p) => payloadEncoded = p
|
||||
}
|
||||
|
||||
Attempt.Successful(hex"00".bits ++ opcodeEncoded ++ payloadEncoded)
|
||||
}
|
||||
|
||||
def EncodePacket(packet : PlanetSideCryptoPacket) : Attempt[BitVector] = {
|
||||
encodePacket(packet) match {
|
||||
case Failure(e) => Attempt.failure(Err(s"Failed to marshal crypto packet $packet: " + e.messageWithContext))
|
||||
case s @ Successful(p) => s
|
||||
}
|
||||
}
|
||||
|
||||
def EncodePacket(packet : PlanetSideGamePacket) : Attempt[BitVector] = {
|
||||
val opcode = packet.opcode
|
||||
var opcodeEncoded = BitVector.empty
|
||||
var payloadEncoded = BitVector.empty
|
||||
|
||||
GamePacketOpcode.codec.encode(opcode) match {
|
||||
case Failure(e) => return Attempt.failure(Err(s"Failed to marshal opcode in game packet $opcode: " + e.messageWithContext))
|
||||
case Successful(p) => opcodeEncoded = p
|
||||
}
|
||||
|
||||
encodePacket(packet) match {
|
||||
case Failure(e) => return Attempt.failure(Err(s"Failed to marshal game packet $packet: " + e.messageWithContext))
|
||||
case Successful(p) => payloadEncoded = p
|
||||
}
|
||||
|
||||
Attempt.Successful(opcodeEncoded ++ payloadEncoded)
|
||||
}
|
||||
|
||||
def CreateControlPacket(packet : PlanetSideControlPacket) = ControlPacket(packet.opcode, packet)
|
||||
def CreateCryptoPacket(sequence : Int, packet : PlanetSideCryptoPacket) = CryptoPacket(sequence, packet)
|
||||
def CreateGamePacket(sequence : Int, packet : PlanetSideGamePacket) = GamePacket(packet.opcode, sequence, packet)
|
||||
|
|
@ -199,6 +264,7 @@ object PacketCoding {
|
|||
val packet = DecodeControlPacket(msg)
|
||||
|
||||
packet match {
|
||||
// just return the failure
|
||||
case f @ Failure(e) => f
|
||||
case Successful(p) =>
|
||||
Attempt.successful(CreateControlPacket(p))
|
||||
|
|
@ -271,7 +337,7 @@ object PacketCoding {
|
|||
///////////////////////////////////////////////////////////
|
||||
|
||||
def encryptPacket(crypto : CryptoInterface.CryptoStateWithMAC, packet : PlanetSidePacketContainer) : Attempt[EncryptedPacket] = {
|
||||
// TODO: this is bad. rework
|
||||
// TODO XXX: this is bad. rework
|
||||
var sequenceNumber = 0
|
||||
|
||||
val rawPacket : BitVector = packet match {
|
||||
|
|
@ -303,10 +369,14 @@ object PacketCoding {
|
|||
case default => throw new IllegalArgumentException("Unsupported packet container type")
|
||||
}
|
||||
|
||||
val packetMac = crypto.macForEncrypt(rawPacket.toByteVector)
|
||||
encryptPacket(crypto, sequenceNumber, rawPacket.toByteVector)
|
||||
}
|
||||
|
||||
def encryptPacket(crypto : CryptoInterface.CryptoStateWithMAC, sequenceNumber : Int, rawPacket : ByteVector) : Attempt[EncryptedPacket] = {
|
||||
val packetMac = crypto.macForEncrypt(rawPacket)
|
||||
|
||||
// opcode, payload, and MAC
|
||||
val packetNoPadding = rawPacket.toByteVector ++ packetMac
|
||||
val packetNoPadding = rawPacket ++ packetMac
|
||||
|
||||
val remainder = packetNoPadding.length % CryptoInterface.RC5_BLOCK_SIZE
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright (c) 2016 PSForever.net to present
|
||||
package psforever.net
|
||||
import scodec._
|
||||
import scodec.bits._
|
||||
import scodec.codecs._
|
||||
import shapeless._
|
||||
|
||||
object WorldStatus extends Enumeration {
|
||||
type Type = Value
|
||||
val Up, Down, Locked, Full = Value
|
||||
}
|
||||
|
||||
object ServerType extends Enumeration {
|
||||
type Type = Value
|
||||
val Development, Beta, Released = Value
|
||||
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint8L)
|
||||
}
|
||||
|
||||
object EmpireNeed extends Enumeration {
|
||||
type Type = Value
|
||||
val TR, NC, VS = Value
|
||||
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint2L)
|
||||
}
|
||||
|
||||
final case class WorldInformation(name : String, status : WorldStatus.Value,
|
||||
serverType : ServerType.Value, empireNeed : EmpireNeed.Value)
|
||||
|
||||
final case class VNLWorldStatusMessage(welcomeMessage : String, worlds : Vector[WorldInformation])
|
||||
extends PlanetSideGamePacket {
|
||||
type Packet = VNLWorldStatusMessage
|
||||
def opcode = GamePacketOpcode.VNLWorldStatusMessage
|
||||
def encode = VNLWorldStatusMessage.encode(this)
|
||||
}
|
||||
|
||||
object VNLWorldStatusMessage extends Marshallable[VNLWorldStatusMessage] {
|
||||
type InStruct = WorldStatus.Value :: ServerType.Value :: HNil
|
||||
type OutStruct = Int :: ServerType.Value :: Int :: HNil
|
||||
|
||||
implicit val statusCodec : Codec[InStruct] = {
|
||||
def from(a : InStruct) : OutStruct = a match {
|
||||
case status :: svrType :: HNil =>
|
||||
status match {
|
||||
case WorldStatus.Down =>
|
||||
0 :: svrType :: 2 :: HNil
|
||||
case WorldStatus.Locked =>
|
||||
0 :: svrType :: 1 :: HNil
|
||||
case WorldStatus.Up =>
|
||||
1 :: svrType :: 0 :: HNil
|
||||
case WorldStatus.Full =>
|
||||
5 :: svrType :: 0 :: HNil
|
||||
}
|
||||
}
|
||||
|
||||
def to(a : OutStruct) : InStruct = a match {
|
||||
case status2 :: svrType :: status1 :: HNil =>
|
||||
if(status1 == 0) {
|
||||
if(status2 >= 5) {
|
||||
WorldStatus.Full :: svrType :: HNil
|
||||
} else {
|
||||
WorldStatus.Up :: svrType :: HNil
|
||||
}
|
||||
} else {
|
||||
if(status1 != 1)
|
||||
WorldStatus.Down :: svrType :: HNil
|
||||
else
|
||||
WorldStatus.Locked :: svrType :: HNil
|
||||
}
|
||||
}
|
||||
|
||||
(("status2" | uint16L) ::
|
||||
("server_type" | ServerType.codec) ::
|
||||
("status1" | uint8L)).xmap(to, from)
|
||||
}
|
||||
|
||||
implicit val codec : Codec[VNLWorldStatusMessage] = (
|
||||
("welcome_message" | PacketHelpers.encodedWideString) ::
|
||||
("worlds" | vectorOfN(uint8L, (
|
||||
// XXX: this needs to be limited to 0x20 bytes
|
||||
// XXX: this needs to be byte aligned, but not sure how to do this
|
||||
("world_name" | PacketHelpers.encodedString) :: (
|
||||
("status_and_type" | statusCodec) :+
|
||||
("unknown" | constant(hex"01459e25403775")) :+
|
||||
("empire_need" | EmpireNeed.codec)
|
||||
)
|
||||
).as[WorldInformation]
|
||||
))).as[VNLWorldStatusMessage]
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import org.specs2.mutable._
|
||||
import psforever.crypto.CryptoInterface
|
||||
import psforever.crypto.CryptoInterface.{CryptoState, CryptoDHState}
|
||||
import psforever.crypto.CryptoInterface.CryptoDHState
|
||||
import scodec.bits._
|
||||
|
||||
class CryptoInterfaceTest extends Specification {
|
||||
|
|
@ -3,7 +3,7 @@ import psforever.net._
|
|||
import scodec.Codec
|
||||
import scodec.bits._
|
||||
|
||||
class CryptoPackets extends Specification {
|
||||
class CryptoPacketTest extends Specification {
|
||||
|
||||
"PlanetSide crypto packet" in {
|
||||
val cNonce = 656287232
|
||||
54
common/src/test/scala/GamePacketTest.scala
Normal file
54
common/src/test/scala/GamePacketTest.scala
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright (c) 2016 PSForever.net to present
|
||||
import org.specs2.mutable._
|
||||
import psforever.net._
|
||||
import scodec.bits._
|
||||
|
||||
class GamePacketTest extends Specification {
|
||||
|
||||
"PlanetSide game packet" in {
|
||||
val cNonce = 656287232
|
||||
|
||||
"VNLWorldStatusMessage" should {
|
||||
val string = hex"0597570065006c0063006f006d006500200074006f00200050006c0061006e00650074005300690064006500210020000186" ++
|
||||
hex"67656d696e69" ++ hex"0100 01 00 01459e2540 3775" ++ bin"01".toByteVector
|
||||
|
||||
"decode" in {
|
||||
PacketCoding.DecodePacket(string).require match {
|
||||
case VNLWorldStatusMessage(message, worlds) =>
|
||||
worlds.length mustEqual 1
|
||||
message mustEqual "Welcome to PlanetSide! "
|
||||
worlds{0}.name mustEqual "gemini"
|
||||
worlds{0}.empireNeed mustEqual EmpireNeed.NC
|
||||
worlds{0}.status mustEqual WorldStatus.Up
|
||||
case default =>
|
||||
true mustEqual false
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
val msg = VNLWorldStatusMessage("Welcome to PlanetSide! ",
|
||||
Vector(WorldInformation("gemini", WorldStatus.Up, ServerType.Beta, EmpireNeed.NC)))
|
||||
//0100 04 00 01459e2540377540
|
||||
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string
|
||||
}
|
||||
|
||||
"encode and decode multiple worlds" in {
|
||||
val msg = VNLWorldStatusMessage("Welcome to PlanetSide! ",
|
||||
Vector(
|
||||
WorldInformation("PSForever1", WorldStatus.Up, ServerType.Released, EmpireNeed.NC),
|
||||
WorldInformation("PSForever2", WorldStatus.Down, ServerType.Beta, EmpireNeed.TR)
|
||||
))
|
||||
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
println(pkt)
|
||||
|
||||
true mustEqual true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import org.specs2.mutable._
|
||||
import psforever.crypto.CryptoInterface
|
||||
import psforever.net._
|
||||
import scodec.bits._
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import scodec.{Err, Attempt, Codec}
|
|||
import scodec.codecs.{uint16L, uint8L, bytes}
|
||||
import java.security.SecureRandom
|
||||
|
||||
/*sealed trait SessionState extends Serializable
|
||||
final case class NewSession() extends SessionState
|
||||
final case class EstablishSecureChannel() extends SessionState
|
||||
final case class SessionDead() extends SessionState*/
|
||||
|
||||
/**
|
||||
* Actor that stores crypto state for a connection and filters away any packet metadata.
|
||||
* Also decrypts and handles packet retries using the sequence numbers.
|
||||
* @param session Per session state
|
||||
*/
|
||||
class LoginSessionActor(session : LoginSession) extends Actor with ActorLogging {
|
||||
var cryptoDHState = new CryptoInterface.CryptoDHState()
|
||||
var cryptoState : Option[CryptoInterface.CryptoStateWithMAC] = None
|
||||
|
|
@ -27,10 +27,11 @@ class LoginSessionActor(session : LoginSession) extends Actor with ActorLogging
|
|||
var clientChallenge = ByteVector.empty
|
||||
var clientChallengeResult = ByteVector.empty
|
||||
|
||||
def receive = clientStart
|
||||
def receive = NewClient
|
||||
|
||||
def clientStart : Receive = {
|
||||
def NewClient : Receive = {
|
||||
case RawPacket(msg) =>
|
||||
// PacketCoding.DecodePacket
|
||||
PacketCoding.UnmarshalPacket(msg) match {
|
||||
case Failure(e) => log.error("Could not decode packet: " + e)
|
||||
case Successful(p) =>
|
||||
|
|
@ -39,7 +40,8 @@ class LoginSessionActor(session : LoginSession) extends Actor with ActorLogging
|
|||
p match {
|
||||
case ControlPacket(_, ClientStart(nonce)) =>
|
||||
sendResponse(PacketCoding.CreateControlPacket(ServerStart(nonce, Math.abs(random.nextInt()))))
|
||||
context.become(clientXchg)
|
||||
|
||||
context.become(CryptoExchange)
|
||||
case default =>
|
||||
log.error("Unexpected packet type " + p)
|
||||
}
|
||||
|
|
@ -47,7 +49,7 @@ class LoginSessionActor(session : LoginSession) extends Actor with ActorLogging
|
|||
case default => log.error(s"Invalid message received ${default}")
|
||||
}
|
||||
|
||||
def clientXchg : Receive = {
|
||||
def CryptoExchange : Receive = {
|
||||
case RawPacket(msg) =>
|
||||
PacketCoding.UnmarshalPacket(msg, CryptoPacketOpcode.ClientChallengeXchg) match {
|
||||
case Failure(e) => log.error("Could not decode packet: " + e)
|
||||
|
|
@ -79,14 +81,14 @@ class LoginSessionActor(session : LoginSession) extends Actor with ActorLogging
|
|||
// save the sent packet a MAC check
|
||||
serverMACBuffer ++= sentPacket.drop(3)
|
||||
|
||||
context.become(clientFinished)
|
||||
context.become(CryptoSetupFinishing)
|
||||
case default => log.error("Unexpected packet type " + p)
|
||||
}
|
||||
}
|
||||
case default => log.error(s"Invalid message received ${default}")
|
||||
}
|
||||
|
||||
def clientFinished : Receive = {
|
||||
def CryptoSetupFinishing : Receive = {
|
||||
case RawPacket(msg) =>
|
||||
PacketCoding.UnmarshalPacket(msg, CryptoPacketOpcode.ClientFinished) match {
|
||||
case Failure(e) => log.error("Could not decode packet: " + e)
|
||||
|
|
@ -163,14 +165,14 @@ class LoginSessionActor(session : LoginSession) extends Actor with ActorLogging
|
|||
|
||||
sendResponse(packet)
|
||||
|
||||
context.become(established)
|
||||
context.become(Established)
|
||||
case default => failWithError("Unexpected packet type " + default)
|
||||
}
|
||||
}
|
||||
case default => failWithError(s"Invalid message received ${default}")
|
||||
}
|
||||
|
||||
def established : Receive = {
|
||||
def Established : Receive = {
|
||||
case RawPacket(msg) =>
|
||||
PacketCoding.UnmarshalPacket(msg) match {
|
||||
case Successful(p) =>
|
||||
|
|
@ -204,6 +206,15 @@ class LoginSessionActor(session : LoginSession) extends Actor with ActorLogging
|
|||
))).require
|
||||
|
||||
sendResponse(packet)
|
||||
|
||||
val msg = VNLWorldStatusMessage("Welcome to PlanetSide! ",
|
||||
Vector(
|
||||
WorldInformation("gemini", WorldStatus.Up, ServerType.Released, EmpireNeed.NC)
|
||||
))
|
||||
|
||||
sendResponse(PacketCoding.encryptPacket(cryptoState.get, PacketCoding.CreateGamePacket(4,
|
||||
msg
|
||||
)).require)
|
||||
case Failure(e) => println("Failed to decode inner packet " + e)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue