[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:
Chord 2016-03-04 13:00:03 -05:00
parent 1643ecc1dd
commit b8ff34c0f9
10 changed files with 363 additions and 102 deletions

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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

View file

@ -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]
}

View file

@ -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 {

View file

@ -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

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

View file

@ -1,5 +1,4 @@
import org.specs2.mutable._
import psforever.crypto.CryptoInterface
import psforever.net._
import scodec.bits._

View file

@ -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)
}
}