diff --git a/common/src/main/scala/net/psforever/packet/ControlPacketOpcode.scala b/common/src/main/scala/net/psforever/packet/ControlPacketOpcode.scala index e54397c8..bf413ba1 100644 --- a/common/src/main/scala/net/psforever/packet/ControlPacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/ControlPacketOpcode.scala @@ -51,19 +51,23 @@ object ControlPacketOpcode extends Enumeration { Unknown30 = Value - def getPacketDecoder(opcode : ControlPacketOpcode.Type) : (BitVector) => Attempt[DecodeResult[PlanetSideControlPacket]] = { - import net.psforever - - opcode match { + def getPacketDecoder(opcode : ControlPacketOpcode.Type) : (BitVector) => Attempt[DecodeResult[PlanetSideControlPacket]] = opcode match { case HandleGamePacket => control.HandleGamePacket.decode case ServerStart => control.ServerStart.decode case ClientStart => control.ClientStart.decode case MultiPacket => control.MultiPacket.decode - case SlottedMetaPacket0 => SlottedMetaPacket.decode + // IT'S GETTING "WET" IN HERE + case SlottedMetaPacket0 => SlottedMetaPacket.decodeWithOpcode(SlottedMetaPacket0) + case SlottedMetaPacket1 => SlottedMetaPacket.decodeWithOpcode(SlottedMetaPacket1) + case SlottedMetaPacket2 => SlottedMetaPacket.decodeWithOpcode(SlottedMetaPacket2) + case SlottedMetaPacket3 => SlottedMetaPacket.decodeWithOpcode(SlottedMetaPacket3) + case SlottedMetaPacket4 => SlottedMetaPacket.decodeWithOpcode(SlottedMetaPacket4) + case SlottedMetaPacket5 => SlottedMetaPacket.decodeWithOpcode(SlottedMetaPacket5) + case SlottedMetaPacket6 => SlottedMetaPacket.decodeWithOpcode(SlottedMetaPacket6) + case SlottedMetaPacket7 => SlottedMetaPacket.decodeWithOpcode(SlottedMetaPacket7) case ConnectionClose => control.ConnectionClose.decode case default => (a : BitVector) => Attempt.failure(Err(s"Could not find a marshaller for control packet ${opcode}")) } - } implicit val codec: Codec[this.Value] = PacketHelpers.createEnumerationCodec(this, uint8L) } diff --git a/common/src/main/scala/net/psforever/packet/CryptoPacketOpcode.scala b/common/src/main/scala/net/psforever/packet/CryptoPacketOpcode.scala index 4dd63948..74e64937 100644 --- a/common/src/main/scala/net/psforever/packet/CryptoPacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/CryptoPacketOpcode.scala @@ -10,10 +10,7 @@ object CryptoPacketOpcode extends Enumeration { val Ignore, ClientChallengeXchg, ServerChallengeXchg, ClientFinished, ServerFinished = Value - def getPacketDecoder(opcode : CryptoPacketOpcode.Type) : (BitVector) => Attempt[DecodeResult[PlanetSideCryptoPacket]] = { - import net.psforever - - opcode match { + def getPacketDecoder(opcode : CryptoPacketOpcode.Type) : (BitVector) => Attempt[DecodeResult[PlanetSideCryptoPacket]] = opcode match { case ClientChallengeXchg => crypto.ClientChallengeXchg.decode case ServerChallengeXchg => crypto.ServerChallengeXchg.decode case ServerFinished => crypto.ServerFinished.decode @@ -21,5 +18,4 @@ object CryptoPacketOpcode extends Enumeration { case default => (a : BitVector) => Attempt.failure(Err(s"Could not find a marshaller for crypto packet ${opcode}") .pushContext("get_marshaller")) } - } } diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index ecf08ad5..373b9764 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -31,16 +31,12 @@ object GamePacketOpcode extends Enumeration { DismountVehicleMsg = Value - def getPacketDecoder(opcode : GamePacketOpcode.Type) : (BitVector) => Attempt[DecodeResult[PlanetSideGamePacket]] = { - import net.psforever - - opcode match { + def getPacketDecoder(opcode : GamePacketOpcode.Type) : (BitVector) => Attempt[DecodeResult[PlanetSideGamePacket]] = opcode match { case LoginMessage => game.LoginMessage.decode case LoginRespMessage => game.LoginRespMessage.decode case VNLWorldStatusMessage => game.VNLWorldStatusMessage.decode case default => (a : BitVector) => Attempt.failure(Err(s"Could not find a marshaller for game packet ${opcode}")) } - } implicit val codec: Codec[this.Value] = PacketHelpers.createEnumerationCodec(this, uint8L) } diff --git a/common/src/main/scala/net/psforever/packet/control/SlottedMetaPacket.scala b/common/src/main/scala/net/psforever/packet/control/SlottedMetaPacket.scala index ea763067..869f7b8c 100644 --- a/common/src/main/scala/net/psforever/packet/control/SlottedMetaPacket.scala +++ b/common/src/main/scala/net/psforever/packet/control/SlottedMetaPacket.scala @@ -3,27 +3,34 @@ package net.psforever.packet.control import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideControlPacket} import scodec.Codec -import scodec.bits.ByteVector +import scodec.bits.{BitVector, ByteOrdering, ByteVector} import scodec.codecs._ -final case class SlottedMetaPacket(/*slot : Int,*/ packet : ByteVector) +final case class SlottedMetaPacket(slot : Int, subslot : Int, packet : ByteVector) extends PlanetSideControlPacket { type Packet = SlottedMetaPacket - //assert(slot >= 0 && slot <= 7, "Slot number is out of range") + assert(slot >= 0 && slot <= 7, s"Slot number ($slot) is out of range") def opcode = { val base = ControlPacketOpcode.SlottedMetaPacket0.id - ControlPacketOpcode(base/* + slot*/) + ControlPacketOpcode(base + slot) } - def encode = SlottedMetaPacket.encode(this) + // XXX: a nasty hack to ignore the "slot" field + // There is so much wrong with this it's not even funny. Why scodec, whyyyy... + // I've never had a library make me feel so stupid and smart at the same time + def encode = SlottedMetaPacket.encode(this).map(vect => vect.drop(8)) } object SlottedMetaPacket extends Marshallable[SlottedMetaPacket] { implicit val codec : Codec[SlottedMetaPacket] = ( - ("unknown" | constant(0)) :: - ("unknown" | constant(0)) :: + ("slot" | uint8L.xmap[Int](a => a - ControlPacketOpcode.SlottedMetaPacket0.id, a=>a) ) :: + ("subslot" | uint16) :: // the slot is big endian. see 0x00A42F76 ("rest" | bytes) ).as[SlottedMetaPacket] + + def decodeWithOpcode(slot : ControlPacketOpcode.Value)(bits : BitVector) = { + decode(ControlPacketOpcode.codec.encode(slot).require ++ bits) + } } \ No newline at end of file diff --git a/common/src/test/scala/ControlPacketTest.scala b/common/src/test/scala/ControlPacketTest.scala new file mode 100644 index 00000000..59a2442f --- /dev/null +++ b/common/src/test/scala/ControlPacketTest.scala @@ -0,0 +1,82 @@ +// Copyright (c) 2016 PSForever.net to present + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.control._ +import scodec.Attempt.Successful +import scodec.bits._ +import scodec.codecs.uint16 + +class ControlPacketTest extends Specification { + + "PlanetSide control packet" in { + val cNonce = 656287232 + + "SlottedMetaPacket" should { + val string = hex"00 09 00 00 00194302484C36563130433F" ++ + hex"4C6835316369774A0000000018FABE0C" ++ + hex"00000000000000000000000001000000" ++ + hex"020000006B7BD8288C6469666671756F" ++ + hex"7469656E740000000000440597570065" ++ + hex"006C0063006F006D006500200074006F" ++ + hex"00200050006C0061006E006500740053" ++ + hex"0069006400650021002000018667656D" ++ + hex"696E690100040001459E2540377540" + + def createMetaPacket(slot : Int, subslot : Int, rest : ByteVector) = hex"00" ++ + ControlPacketOpcode.codec.encode( + ControlPacketOpcode(ControlPacketOpcode.SlottedMetaPacket0.id + slot) + ).require.toByteVector ++ uint16.encode(subslot).require.toByteVector ++ rest + + "decode as the base slot and subslot" in { + PacketCoding.DecodePacket(string).require match { + case SlottedMetaPacket(slot, subslot, rest) => + slot mustEqual 0 + subslot mustEqual 0 + rest mustEqual string.drop(4) + case default => + true mustEqual false + } + } + + "decode as an arbitrary slot and subslot" in { + val maxSlots = ControlPacketOpcode.SlottedMetaPacket7.id - ControlPacketOpcode.SlottedMetaPacket0.id + + // create all possible SlottedMetaPackets + for(i <- 0 until maxSlots) { + val subslot = 12323 + val pkt = createMetaPacket(i, subslot, ByteVector.empty) + + PacketCoding.DecodePacket(pkt).require match { + case SlottedMetaPacket(slot, subslotDecoded, rest) => + + // XXX: there isn't a simple solution to Slot0 and Slot4 be aliases of each other structurally + // This is probably best left to higher layers + //slot mustEqual i % 4 // this is seen at 0x00A3FBFA + slot mustEqual i + subslotDecoded mustEqual subslot + rest mustEqual ByteVector.empty // empty in this case + case default => + true mustEqual false + } + } + + true + } + + "encode" in { + val encoded = PacketCoding.EncodePacket(SlottedMetaPacket(0, 0x1000, ByteVector.empty)).require + val encoded2 = PacketCoding.EncodePacket(SlottedMetaPacket(3, 0xffff, hex"414243")).require + val encoded3 = PacketCoding.EncodePacket(SlottedMetaPacket(7, 0, hex"00")).require + + encoded.toByteVector mustEqual createMetaPacket(0, 0x1000, ByteVector.empty) + encoded2.toByteVector mustEqual createMetaPacket(3, 0xffff, hex"414243") + encoded3.toByteVector mustEqual createMetaPacket(7, 0, hex"00") + + PacketCoding.EncodePacket(SlottedMetaPacket(8, 0, hex"00")).require must throwA[AssertionError] + PacketCoding.EncodePacket(SlottedMetaPacket(-1, 0, hex"00")).require must throwA[AssertionError] + PacketCoding.EncodePacket(SlottedMetaPacket(0, 0x10000, hex"00")).require must throwA[IllegalArgumentException] + } + } + } +} \ No newline at end of file diff --git a/pslogin/src/main/scala/CryptoSessionActor.scala b/pslogin/src/main/scala/CryptoSessionActor.scala index 2f165d3d..da390dc4 100644 --- a/pslogin/src/main/scala/CryptoSessionActor.scala +++ b/pslogin/src/main/scala/CryptoSessionActor.scala @@ -260,18 +260,23 @@ class CryptoSessionActor extends Actor with MDCContextAware { val packet = PacketCoding.encryptPacket(cryptoState.get, cont).require sendResponse(packet) } else { - log.error("Invalid sender") + log.error(s"Invalid sender when handling a message in Established ${from}") } } def sendResponse(cont : PlanetSidePacketContainer) : ByteVector = { //println("CRYPTO SEND: " + cont) - val pkt = PacketCoding.MarshalPacket(cont).require - val bytes = pkt.toByteVector + val pkt = PacketCoding.MarshalPacket(cont) - leftRef ! ResponsePacket(bytes) - - bytes + pkt match { + case Failure(e) => + log.error(s"Failed to marshal packet ${cont.getClass.getName} when sending response") + ByteVector.empty + case Successful(v) => + val bytes = v.toByteVector + leftRef ! ResponsePacket(bytes) + bytes + } } def getRandBytes(amount : Int) : ByteVector = { diff --git a/pslogin/src/main/scala/LoginSessionActor.scala b/pslogin/src/main/scala/LoginSessionActor.scala index f21c0ad7..adffa715 100644 --- a/pslogin/src/main/scala/LoginSessionActor.scala +++ b/pslogin/src/main/scala/LoginSessionActor.scala @@ -1,9 +1,9 @@ // Copyright (c) 2016 PSForever.net to present import java.net.{InetAddress, InetSocketAddress} -import akka.actor.{Actor, ActorLogging, ActorRef, Identify, MDCContextAware} +import akka.actor.{Actor, ActorRef, MDCContextAware} import net.psforever.packet._ -import net.psforever.packet.control.{ConnectionClose, SlottedMetaPacket} +import net.psforever.packet.control._ import net.psforever.packet.game._ import scodec.Attempt.{Failure, Successful} import scodec.bits._ @@ -28,16 +28,24 @@ class LoginSessionActor extends Actor with MDCContextAware { } def Started : Receive = { - case ctrl @ ControlPacket(opcode, pkt) => - handleControlPkt(pkt) - case game @ GamePacket(opcode, seq, pkt) => - handleGamePkt(pkt) - case default => failWithError(s"Invalid message received $default") + case ctrl @ ControlPacket(_, _) => + handlePkt(ctrl) + case game @ GamePacket(_, _, _) => + handlePkt(game) + case default => failWithError(s"Invalid packet class received: $default") + } + + def handlePkt(pkt : PlanetSidePacketContainer) : Unit = pkt match { + case ctrl @ ControlPacket(opcode, ctrlPkt) => + handleControlPkt(ctrlPkt) + case game @ GamePacket(opcode, seq, gamePkt) => + handleGamePkt(gamePkt) + case default => failWithError(s"Invalid packet class received: $default") } def handleControlPkt(pkt : PlanetSideControlPacket) = { pkt match { - case SlottedMetaPacket(innerPacket) => + case meta @ SlottedMetaPacket(slot, subslot, innerPacket) => PacketCoding.DecodePacket(innerPacket) match { case Successful(p) => log.trace("RECV[INNER]: " + p) @@ -59,6 +67,17 @@ class LoginSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, msg)) case Failure(e) => log.error("Failed to decode inner packet " + e) } + case MultiPacket(packets) => + packets.foreach { pkt => + PacketCoding.UnmarshalPacket(pkt) match { + case Failure(e) => + log.error(s"Failed to decode inner packet of MultiPacket: $e") + case Successful(v) => + handlePkt(v) // dont send a message to ourselves as then packets will be processed in the wrong order + } + } + case default => + log.debug(s"Unhandled ControlPacket $default") } } @@ -68,7 +87,7 @@ class LoginSessionActor extends Actor with MDCContextAware { def failWithError(error : String) = { log.error(error) - sendResponse(PacketCoding.CreateControlPacket(ConnectionClose())) + //sendResponse(PacketCoding.CreateControlPacket(ConnectionClose())) } def sendResponse(cont : PlanetSidePacketContainer) = {