Refactor all packet definitions out of PSPacket

This commit is contained in:
Chord 2016-05-03 03:28:10 -04:00
parent f81d67f959
commit 83da72e50e
14 changed files with 354 additions and 278 deletions

View file

@ -0,0 +1,36 @@
// Copyright (c) 2016 PSForever.net to present
package psforever.net
import scodec.bits.ByteVector
import scodec.Codec
import scodec.codecs._
import scodec.bits._
final case class ClientChallengeXchg(time : Long, challenge : ByteVector, p : ByteVector, g : ByteVector)
extends PlanetSideCryptoPacket {
def opcode = CryptoPacketOpcode.ClientChallengeXchg
def encode = ClientChallengeXchg.encode(this)
}
object ClientChallengeXchg extends Marshallable[ClientChallengeXchg] {
implicit val codec: Codec[ClientChallengeXchg] = (
("unknown" | constant(1)) ::
("unknown" | constant(1)) ::
("client_time" | uint32L) ::
("challenge" | bytes(12)) ::
("end_chal?" | constant(0)) ::
("objects?" | constant(1)) ::
("object_type?" | constant(hex"0002".bits)) ::
("unknown" | constant(hex"ff240000".bits)) ::
("P_len" | constant(hex"1000".bits)) ::
("P" | bytes(16)) ::
("G_len" | constant(hex"1000".bits)) ::
("G" | bytes(16)) ::
("end?" | constant(0)) ::
("end?" | constant(0)) ::
("objects?" | constant(1)) ::
("unknown" | constant(hex"03070000".bits)) ::
("end?" | constant(0))
).as[ClientChallengeXchg]
}

View file

@ -0,0 +1,24 @@
// Copyright (c) 2016 PSForever.net to present
package psforever.net
import scodec.bits.ByteVector
import scodec.Codec
import scodec.codecs._
import scodec.bits._
final case class ClientFinished(pubKey : ByteVector, challengeResult: ByteVector)
extends PlanetSideCryptoPacket {
type Packet = ClientFinished
def opcode = CryptoPacketOpcode.ClientFinished
def encode = ClientFinished.encode(this)
}
object ClientFinished extends Marshallable[ClientFinished] {
implicit val codec : Codec[ClientFinished] = (
("obj_type?" | constant(hex"10".bits)) ::
("pub_key_len" | constant(hex"1000")) ::
("pub_key" | bytes(16)) ::
("unknown" | constant(hex"0114".bits)) ::
("challenge_result" | bytes(0xc))
).as[ClientFinished]
}

View file

@ -0,0 +1,21 @@
// Copyright (c) 2016 PSForever.net to present
package psforever.net
import scodec.Codec
import scodec.codecs._
import scodec.bits._
final case class ClientStart(clientNonce : Long)
extends PlanetSideControlPacket {
type Packet = ClientStart
def opcode = ControlPacketOpcode.ClientStart
def encode = ClientStart.encode(this)
}
object ClientStart extends Marshallable[ClientStart] {
implicit val codec : Codec[ClientStart] = (
("unknown" | constant(hex"00000002".bits)) ::
("client_nonce" | uint32L) ::
("unknown" | constant(hex"000001f0".bits))
).as[ClientStart]
}

View file

@ -0,0 +1,16 @@
// Copyright (c) 2016 PSForever.net to present
package psforever.net
import scodec.Codec
import scodec.codecs._
final case class ConnectToWorldMessage(world : String)
extends PlanetSideGamePacket {
type Packet = ConnectToWorldMessage
def opcode = GamePacketOpcode.ConnectToWorldMessage
def encode = ConnectToWorldMessage.encode(this)
}
object ConnectToWorldMessage extends Marshallable[ConnectToWorldMessage] {
implicit val codec : Codec[ConnectToWorldMessage] = ascii.as[ConnectToWorldMessage]
}

View file

@ -0,0 +1,15 @@
// Copyright (c) 2016 PSForever.net to present
package psforever.net
import scodec.Codec
final case class ConnectionClose()
extends PlanetSideControlPacket {
type Packet = ConnectionClose
def opcode = ControlPacketOpcode.ConnectionClose
def encode = ConnectionClose.encode(this)
}
object ConnectionClose extends Marshallable[ConnectionClose] {
implicit val codec: Codec[ConnectionClose] = PacketHelpers.emptyCodec(ConnectionClose())
}

View file

@ -0,0 +1,16 @@
// Copyright (c) 2016 PSForever.net to present
package psforever.net
import scodec.bits.ByteVector
import scodec.Codec
import scodec.codecs._
final case class HandleGamePacket(packet : ByteVector)
extends PlanetSideControlPacket {
def opcode = ControlPacketOpcode.HandleGamePacket
def encode = throw new Exception("This packet type should never be encoded")
}
object HandleGamePacket extends Marshallable[HandleGamePacket] {
implicit val codec : Codec[HandleGamePacket] = bytes.as[HandleGamePacket].decodeOnly
}

View file

@ -0,0 +1,72 @@
// Copyright (c) 2016 PSForever.net to present
package psforever.net
import scodec.Codec
import scodec.codecs._
import scodec.bits._
import shapeless._
final case class LoginMessage(majorVersion : Long,
minorVersion : Long,
buildDate : String,
username : String,
password : Option[String],
token : Option[String],
revision : Long) extends PlanetSideGamePacket {
require(majorVersion >= 0)
require(minorVersion >= 0)
require(revision >= 0)
require(password.isDefined ^ token.isDefined, "Either 'username' or 'token' must be set, but not both")
def opcode = GamePacketOpcode.LoginMessage
def encode = LoginMessage.encode(this)
}
object LoginMessage extends Marshallable[LoginMessage] {
private def username = PacketHelpers.encodedStringAligned(7)
private def password = PacketHelpers.encodedString
private def tokenPath = fixedSizeBytes(32, ascii) :: username
private def passwordPath = username :: password
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]
def from(a : InStruct) : Struct = a match {
case Left(username :: password :: HNil) => username :: Some(password) :: None :: HNil
case Right(token :: username :: HNil) => username :: None :: Some(token) :: HNil
}
// serialization can fail if the user did not specify a token or password (or both)
def to(a : Struct) : InStruct = a match {
case username :: Some(password) :: None :: HNil => Left(username :: password :: HNil)
case username :: None :: Some(token) :: HNil => Right(token :: username :: HNil)
}
either(bool, passwordPath, tokenPath).xmap[Struct](from, to)
}
implicit val codec : Codec[LoginMessage] = (
("major_version" | uint32L) ::
("minor_version" | uint32L) ::
("build_date" | PacketHelpers.encodedString) ::
(
// The :+ operator (and the parens) are required because we are adding an HList to an HList,
// not merely a value (like bool). Weird shit, but hey this works.
("credential_choice" | credentialChoice) :+
("revision" | uint32L)
)
).as[LoginMessage]
}

View file

@ -0,0 +1,34 @@
// Copyright (c) 2016 PSForever.net to present
package psforever.net
import scodec.bits.ByteVector
import scodec.Codec
import scodec.codecs._
import scodec.bits._
final case class LoginRespMessage(token : String, // printable ascii for 16
unknown : ByteVector, // hex"00000000 18FABE0C 00000000 00000000"
error : Long, // 0
stationError : Long, // 1
subscriptionStatus : Long, // 2 or 5
someToken : Long, // 685276011
username : String, // the user
unk5 : Long, // 0 and unset bool
someBit : Boolean) extends PlanetSideGamePacket {
def opcode = GamePacketOpcode.LoginRespMessage
def encode = LoginRespMessage.encode(this)
}
object LoginRespMessage extends Marshallable[LoginRespMessage] {
implicit val codec : Codec[LoginRespMessage] = (
("token" | fixedSizeBytes(16, ascii)) ::
("unknown" | bytes(16)) ::
("error" | uint32L) ::
("station_error" | uint32L) ::
("subscription_status" | uint32L) ::
("unknown" | uint32L) ::
("username" | PacketHelpers.encodedString) ::
("unknown" | uint32L) ::
("unknown" | byteAligned(bool))
).as[LoginRespMessage]
}

View file

@ -0,0 +1,18 @@
// Copyright (c) 2016 PSForever.net to present
package psforever.net
import scodec.bits.ByteVector
import scodec.Codec
import scodec.codecs._
import scodec.bits._
final case class MultiPacket(packets : Vector[ByteVector])
extends PlanetSideControlPacket {
type Packet = MultiPacket
def opcode = ControlPacketOpcode.MultiPacket
def encode = MultiPacket.encode(this)
}
object MultiPacket extends Marshallable[MultiPacket] {
implicit val codec : Codec[MultiPacket] = ("packets" | vector(variableSizeBytes(uint8L, bytes))).as[MultiPacket]
}

View file

@ -8,7 +8,6 @@ import scodec.bits._
import scodec.codecs._ import scodec.codecs._
import scodec._ import scodec._
import shapeless._ import shapeless._
import shapeless.ops.hlist.Prepend
// Base packets // Base packets
sealed trait PlanetSidePacket extends Serializable { sealed trait PlanetSidePacket extends Serializable {
@ -36,283 +35,6 @@ trait PlanetSideCryptoPacket extends PlanetSidePacket {
def opcode : CryptoPacketOpcode.Type def opcode : CryptoPacketOpcode.Type
} }
// Crypto Packets
final case class ClientChallengeXchg(time : Long, challenge : ByteVector, p : ByteVector, g : ByteVector)
extends PlanetSideCryptoPacket {
def opcode = CryptoPacketOpcode.ClientChallengeXchg
def encode = ClientChallengeXchg.encode(this)
}
object ClientChallengeXchg extends Marshallable[ClientChallengeXchg] {
implicit val codec: Codec[ClientChallengeXchg] = (
("unknown" | constant(1)) ::
("unknown" | constant(1)) ::
("client_time" | uint32L) ::
("challenge" | bytes(12)) ::
("end_chal?" | constant(0)) ::
("objects?" | constant(1)) ::
("object_type?" | constant(hex"0002".bits)) ::
("unknown" | constant(hex"ff240000".bits)) ::
("P_len" | constant(hex"1000".bits)) ::
("P" | bytes(16)) ::
("G_len" | constant(hex"1000".bits)) ::
("G" | bytes(16)) ::
("end?" | constant(0)) ::
("end?" | constant(0)) ::
("objects?" | constant(1)) ::
("unknown" | constant(hex"03070000".bits)) ::
("end?" | constant(0))
).as[ClientChallengeXchg]
}
final case class ServerChallengeXchg(time : Long, challenge : ByteVector, pubKey : ByteVector)
extends PlanetSideCryptoPacket {
type Packet = ServerChallengeXchg
def opcode = CryptoPacketOpcode.ServerChallengeXchg
def encode = ServerChallengeXchg.encode(this)
}
object ServerChallengeXchg extends Marshallable[ServerChallengeXchg] {
def getCompleteChallenge(time : Long, rest : ByteVector): ByteVector =
uint32L.encode(time).require.toByteVector ++ rest
implicit val codec: Codec[ServerChallengeXchg] = (
("unknown" | constant(2)) ::
("unknown" | constant(1)) ::
("server_time" | uint32L) ::
("challenge" | bytes(0xC)) ::
("end?" | constant(0)) ::
("objects" | constant(1)) ::
("unknown" | constant(hex"03070000000c00".bits)) ::
("pub_key_len" | constant(hex"1000")) ::
("pub_key" | bytes(16)) ::
("unknown" | constant(0x0e))
).as[ServerChallengeXchg]
}
final case class ClientFinished(pubKey : ByteVector, challengeResult: ByteVector)
extends PlanetSideCryptoPacket {
type Packet = ClientFinished
def opcode = CryptoPacketOpcode.ClientFinished
def encode = ClientFinished.encode(this)
}
object ClientFinished extends Marshallable[ClientFinished] {
implicit val codec : Codec[ClientFinished] = (
("obj_type?" | constant(hex"10".bits)) ::
("pub_key_len" | constant(hex"1000")) ::
("pub_key" | bytes(16)) ::
("unknown" | constant(hex"0114".bits)) ::
("challenge_result" | bytes(0xc))
).as[ClientFinished]
}
final case class ServerFinished(challengeResult : ByteVector)
extends PlanetSideCryptoPacket {
type Packet = ServerFinished
def opcode = CryptoPacketOpcode.ServerFinished
def encode = ServerFinished.encode(this)
}
object ServerFinished extends Marshallable[ServerFinished] {
implicit val codec : Codec[ServerFinished] = (
("unknown" | constant(hex"0114".bits)) ::
("challenge_result" | bytes(0xc))
).as[ServerFinished]
}
// Game Packets
final case class LoginMessage(majorVersion : Long,
minorVersion : Long,
buildDate : String,
username : String,
password : Option[String],
token : Option[String],
revision : Long) extends PlanetSideGamePacket {
require(majorVersion >= 0)
require(minorVersion >= 0)
require(revision >= 0)
require(password.isDefined ^ token.isDefined, "Either 'username' or 'token' must be set, but not both")
def opcode = GamePacketOpcode.LoginMessage
def encode = LoginMessage.encode(this)
}
object LoginMessage extends Marshallable[LoginMessage] {
private def username = PacketHelpers.encodedStringAligned(7)
private def password = PacketHelpers.encodedString
private def tokenPath = fixedSizeBytes(32, ascii) :: username
private def passwordPath = username :: password
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]
def from(a : InStruct) : Struct = a match {
case Left(username :: password :: HNil) => username :: Some(password) :: None :: HNil
case Right(token :: username :: HNil) => username :: None :: Some(token) :: HNil
}
// serialization can fail if the user did not specify a token or password (or both)
def to(a : Struct) : InStruct = a match {
case username :: Some(password) :: None :: HNil => Left(username :: password :: HNil)
case username :: None :: Some(token) :: HNil => Right(token :: username :: HNil)
}
either(bool, passwordPath, tokenPath).xmap[Struct](from, to)
}
implicit val codec : Codec[LoginMessage] = (
("major_version" | uint32L) ::
("minor_version" | uint32L) ::
("build_date" | PacketHelpers.encodedString) ::
(
// The :+ operator (and the parens) are required because we are adding an HList to an HList,
// not merely a value (like bool). Weird shit, but hey this works.
("credential_choice" | credentialChoice) :+
("revision" | uint32L)
)
).as[LoginMessage]
}
final case class LoginRespMessage(token : String, // printable ascii for 16
unknown : ByteVector, // hex"00000000 18FABE0C 00000000 00000000"
error : Long, // 0
stationError : Long, // 1
subscriptionStatus : Long, // 2 or 5
someToken : Long, // 685276011
username : String, // the user
unk5 : Long, // 0 and unset bool
someBit : Boolean) extends PlanetSideGamePacket {
def opcode = GamePacketOpcode.LoginRespMessage
def encode = LoginRespMessage.encode(this)
}
object LoginRespMessage extends Marshallable[LoginRespMessage] {
implicit val codec : Codec[LoginRespMessage] = (
("token" | fixedSizeBytes(16, ascii)) ::
("unknown" | bytes(16)) ::
("error" | uint32L) ::
("station_error" | uint32L) ::
("subscription_status" | uint32L) ::
("unknown" | uint32L) ::
("username" | PacketHelpers.encodedString) ::
("unknown" | uint32L) ::
("unknown" | byteAligned(bool))
).as[LoginRespMessage]
}
final case class ConnectToWorldMessage(world : String)
extends PlanetSideGamePacket {
type Packet = ConnectToWorldMessage
def opcode = GamePacketOpcode.ConnectToWorldMessage
def encode = ConnectToWorldMessage.encode(this)
}
object ConnectToWorldMessage extends Marshallable[ConnectToWorldMessage] {
implicit val codec : Codec[ConnectToWorldMessage] = ascii.as[ConnectToWorldMessage]
}
// Control Packets
final case class HandleGamePacket(packet : ByteVector)
extends PlanetSideControlPacket {
def opcode = ControlPacketOpcode.HandleGamePacket
def encode = throw new Exception("This packet type should never be encoded")
}
object HandleGamePacket extends Marshallable[HandleGamePacket] {
implicit val codec : Codec[HandleGamePacket] = bytes.as[HandleGamePacket].decodeOnly
}
final case class ClientStart(clientNonce : Long)
extends PlanetSideControlPacket {
type Packet = ClientStart
def opcode = ControlPacketOpcode.ClientStart
def encode = ClientStart.encode(this)
}
object ClientStart extends Marshallable[ClientStart] {
implicit val codec : Codec[ClientStart] = (
("unknown" | constant(hex"00000002".bits)) ::
("client_nonce" | uint32L) ::
("unknown" | constant(hex"000001f0".bits))
).as[ClientStart]
}
final case class ServerStart(clientNonce : Long, serverNonce : Long)
extends PlanetSideControlPacket {
type Packet = ServerStart
def opcode = ControlPacketOpcode.ServerStart
def encode = ServerStart.encode(this)
}
object ServerStart extends Marshallable[ServerStart] {
implicit val codec : Codec[ServerStart] = (
("client_nonce" | uint32L) ::
("server_nonce" | uint32L) ::
("unknown" | constant(hex"000000000001d300000002".bits))
).as[ServerStart]
}
final case class MultiPacket(packets : Vector[ByteVector])
extends PlanetSideControlPacket {
type Packet = MultiPacket
def opcode = ControlPacketOpcode.MultiPacket
def encode = MultiPacket.encode(this)
}
object MultiPacket extends Marshallable[MultiPacket] {
implicit val codec : Codec[MultiPacket] = ("packets" | vector(variableSizeBytes(uint8L, bytes))).as[MultiPacket]
}
final case class SlottedMetaPacket(/*slot : Int,*/ packet : ByteVector)
extends PlanetSideControlPacket {
type Packet = SlottedMetaPacket
//assert(slot >= 0 && slot <= 7, "Slot number is out of range")
def opcode = {
val base = ControlPacketOpcode.SlottedMetaPacket0.id
ControlPacketOpcode(base/* + slot*/)
}
def encode = SlottedMetaPacket.encode(this)
}
object SlottedMetaPacket extends Marshallable[SlottedMetaPacket] {
implicit val codec : Codec[SlottedMetaPacket] = (
("unknown" | constant(0)) ::
("unknown" | constant(0)) ::
("rest" | bytes)
).as[SlottedMetaPacket]
}
final case class ConnectionClose()
extends PlanetSideControlPacket {
type Packet = ConnectionClose
def opcode = ControlPacketOpcode.ConnectionClose
def encode = ConnectionClose.encode(this)
}
object ConnectionClose extends Marshallable[ConnectionClose] {
implicit val codec: Codec[ConnectionClose] = PacketHelpers.emptyCodec(ConnectionClose())
}
/////////////////////////////////////////////////////////////////
// Packet typing // Packet typing
final case class PlanetSidePacketFlags(packetType : PacketType.Value, secured : Boolean) final case class PlanetSidePacketFlags(packetType : PacketType.Value, secured : Boolean)

View file

@ -0,0 +1,32 @@
// Copyright (c) 2016 PSForever.net to present
package psforever.net
import scodec.bits.ByteVector
import scodec.Codec
import scodec.codecs._
import scodec.bits._
final case class ServerChallengeXchg(time : Long, challenge : ByteVector, pubKey : ByteVector)
extends PlanetSideCryptoPacket {
type Packet = ServerChallengeXchg
def opcode = CryptoPacketOpcode.ServerChallengeXchg
def encode = ServerChallengeXchg.encode(this)
}
object ServerChallengeXchg extends Marshallable[ServerChallengeXchg] {
def getCompleteChallenge(time : Long, rest : ByteVector): ByteVector =
uint32L.encode(time).require.toByteVector ++ rest
implicit val codec: Codec[ServerChallengeXchg] = (
("unknown" | constant(2)) ::
("unknown" | constant(1)) ::
("server_time" | uint32L) ::
("challenge" | bytes(0xC)) ::
("end?" | constant(0)) ::
("objects" | constant(1)) ::
("unknown" | constant(hex"03070000000c00".bits)) ::
("pub_key_len" | constant(hex"1000")) ::
("pub_key" | bytes(16)) ::
("unknown" | constant(0x0e))
).as[ServerChallengeXchg]
}

View file

@ -0,0 +1,21 @@
// Copyright (c) 2016 PSForever.net to present
package psforever.net
import scodec.bits.ByteVector
import scodec.Codec
import scodec.codecs._
import scodec.bits._
final case class ServerFinished(challengeResult : ByteVector)
extends PlanetSideCryptoPacket {
type Packet = ServerFinished
def opcode = CryptoPacketOpcode.ServerFinished
def encode = ServerFinished.encode(this)
}
object ServerFinished extends Marshallable[ServerFinished] {
implicit val codec : Codec[ServerFinished] = (
("unknown" | constant(hex"0114".bits)) ::
("challenge_result" | bytes(0xc))
).as[ServerFinished]
}

View file

@ -0,0 +1,21 @@
// Copyright (c) 2016 PSForever.net to present
package psforever.net
import scodec.Codec
import scodec.codecs._
import scodec.bits._
final case class ServerStart(clientNonce : Long, serverNonce : Long)
extends PlanetSideControlPacket {
type Packet = ServerStart
def opcode = ControlPacketOpcode.ServerStart
def encode = ServerStart.encode(this)
}
object ServerStart extends Marshallable[ServerStart] {
implicit val codec : Codec[ServerStart] = (
("client_nonce" | uint32L) ::
("server_nonce" | uint32L) ::
("unknown" | constant(hex"000000000001d300000002".bits))
).as[ServerStart]
}

View file

@ -0,0 +1,28 @@
// Copyright (c) 2016 PSForever.net to present
package psforever.net
import scodec.bits.ByteVector
import scodec.Codec
import scodec.codecs._
final case class SlottedMetaPacket(/*slot : Int,*/ packet : ByteVector)
extends PlanetSideControlPacket {
type Packet = SlottedMetaPacket
//assert(slot >= 0 && slot <= 7, "Slot number is out of range")
def opcode = {
val base = ControlPacketOpcode.SlottedMetaPacket0.id
ControlPacketOpcode(base/* + slot*/)
}
def encode = SlottedMetaPacket.encode(this)
}
object SlottedMetaPacket extends Marshallable[SlottedMetaPacket] {
implicit val codec : Codec[SlottedMetaPacket] = (
("unknown" | constant(0)) ::
("unknown" | constant(0)) ::
("rest" | bytes)
).as[SlottedMetaPacket]
}