mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-03-01 03:03:39 +00:00
initial RelatedA0 and RelatedB0 packets
modified HandleGamePacket so that it should encode properly; modified CSA so that it attempt to detect packets that encode into ByteVectors that are 'too big' and will attempt to split them up separated the ControlPacket tests into invdividual files and wrote tests for RelatedA0, RelatedB0, and HandleGamePacket proof of concept MTU packet split in CSA; example in WSA @ character select modified session pipeline to accept n queued Actors rather than just two; special packet decoder in progress some effort separating useful sub-operations in encryption/decryption/encoding/decoding functions; introduced PacketCodingActor , devoted to encoding and decoding packets; simplified CSA so that it is devoted just to encrypting and decrypting
This commit is contained in:
parent
3e5e8a2573
commit
294d5335c9
23 changed files with 833 additions and 370 deletions
|
|
@ -49,8 +49,8 @@ object ControlPacketOpcode extends Enumeration {
|
|||
Unknown30
|
||||
= Value
|
||||
|
||||
private def noDecoder(opcode : ControlPacketOpcode.Type) = (a : BitVector) =>
|
||||
Attempt.failure(Err(s"Could not find a marshaller for control packet ${opcode}"))
|
||||
private def noDecoder(opcode : ControlPacketOpcode.Type) = (_ : BitVector) =>
|
||||
Attempt.failure(Err(s"Could not find a marshaller for control packet $opcode"))
|
||||
|
||||
def getPacketDecoder(opcode : ControlPacketOpcode.Type) : (BitVector) => Attempt[DecodeResult[PlanetSideControlPacket]] = (opcode.id : @switch) match {
|
||||
// OPCODES 0x00-0f
|
||||
|
|
@ -74,11 +74,11 @@ object ControlPacketOpcode extends Enumeration {
|
|||
|
||||
// OPCODES 0x10-1e
|
||||
case 0x10 => SlottedMetaPacket.decodeWithOpcode(SlottedMetaPacket7)
|
||||
case 0x11 => noDecoder(RelatedA0)
|
||||
case 0x11 => control.RelatedA0.decode
|
||||
case 0x12 => noDecoder(RelatedA1)
|
||||
case 0x13 => noDecoder(RelatedA2)
|
||||
case 0x14 => noDecoder(RelatedA3)
|
||||
case 0x15 => noDecoder(RelatedB0)
|
||||
case 0x15 => control.RelatedB0.decode
|
||||
case 0x16 => noDecoder(RelatedB1)
|
||||
case 0x17 => noDecoder(RelatedB2)
|
||||
// 0x18
|
||||
|
|
@ -89,7 +89,7 @@ object ControlPacketOpcode extends Enumeration {
|
|||
case 0x1c => noDecoder(Unknown28)
|
||||
case 0x1d => control.ConnectionClose.decode
|
||||
case 0x1e => noDecoder(Unknown30)
|
||||
case default => noDecoder(opcode)
|
||||
case _ => noDecoder(opcode)
|
||||
}
|
||||
|
||||
implicit val codec: Codec[this.Value] = PacketHelpers.createEnumerationCodec(this, uint8L)
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
package net.psforever.packet
|
||||
|
||||
import net.psforever.crypto.CryptoInterface
|
||||
import scodec.Attempt.{Successful, Failure}
|
||||
import scodec.Attempt.{Failure, Successful}
|
||||
import scodec.bits._
|
||||
import scodec.{Err, Attempt, Codec}
|
||||
import scodec.codecs.{uint16L, uint8L, bytes}
|
||||
import scodec.{Attempt, Codec, Err}
|
||||
import scodec.codecs.{bytes, uint16L, uint8L}
|
||||
|
||||
/**
|
||||
* Base trait of the packet container `case class`es.
|
||||
* Base trait of the packet container `case class`.
|
||||
*/
|
||||
sealed trait PlanetSidePacketContainer
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ object PacketCoding {
|
|||
/* Marshalling and Encoding. */
|
||||
|
||||
/**
|
||||
* Transforms a type of packet into the `BitVector` representations of its component data and then reconstructs those components.
|
||||
* Transform a kind of packet into the sequence of data that represents it.
|
||||
* Wraps around the encoding process for all valid packet container types.
|
||||
* @param packet the packet to encode
|
||||
* @return a `BitVector` translated from the packet's data
|
||||
|
|
@ -140,12 +140,11 @@ object PacketCoding {
|
|||
case Successful(p) => seqEncoded = p
|
||||
}
|
||||
}
|
||||
|
||||
Attempt.successful(flagsEncoded ++ seqEncoded ++ paddingEncoded ++ payloadEncoded)
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloaded method for transforming a control packet into its `BitVector` representation.
|
||||
* Overloaded method for transforming a `ControlPacket` into its `BitVector` representation.
|
||||
* @param packet the control packet to encode
|
||||
* @return a `BitVector` translated from the packet's data
|
||||
*/
|
||||
|
|
@ -156,7 +155,6 @@ object PacketCoding {
|
|||
case Failure(e) => return Attempt.failure(Err(s"Failed to marshal opcode in control packet $opcode: " + e.messageWithContext))
|
||||
case Successful(p) => opcodeEncoded = p
|
||||
}
|
||||
|
||||
var payloadEncoded = BitVector.empty
|
||||
encodePacket(packet) match {
|
||||
case Failure(e) => return Attempt.failure(Err(s"Failed to marshal control packet $packet: " + e.messageWithContext))
|
||||
|
|
@ -166,7 +164,7 @@ object PacketCoding {
|
|||
}
|
||||
|
||||
/**
|
||||
* Overloaded method for transforming a crypto packet into its `BitVector` representation.
|
||||
* Overloaded method for transforming a `CryptoPacket` into its `BitVector` representation.
|
||||
* @param packet the crypto packet to encode
|
||||
* @return a `BitVector` translated from the packet's data
|
||||
*/
|
||||
|
|
@ -178,7 +176,7 @@ object PacketCoding {
|
|||
}
|
||||
|
||||
/**
|
||||
* Overloaded method for transforming a game packet into its `BitVector` representation.
|
||||
* Overloaded method for transforming a `GamePacket` into its `BitVector` representation.
|
||||
* @param packet the game packet to encode
|
||||
* @return a `BitVector` translated from the packet's data
|
||||
*/
|
||||
|
|
@ -189,7 +187,6 @@ object PacketCoding {
|
|||
case Failure(e) => return Attempt.failure(Err(s"Failed to marshal opcode in game packet $opcode: " + e.messageWithContext))
|
||||
case Successful(p) => opcodeEncoded = p
|
||||
}
|
||||
|
||||
var payloadEncoded = BitVector.empty
|
||||
encodePacket(packet) match {
|
||||
case Failure(e) => return Attempt.failure(Err(s"Failed to marshal game packet $packet: " + e.messageWithContext))
|
||||
|
|
@ -199,7 +196,7 @@ object PacketCoding {
|
|||
}
|
||||
|
||||
/**
|
||||
* Calls the packet-specific encode function.
|
||||
* Calls the packet's own `encode` function.
|
||||
* Lowest encode call before the packet-specific implementations.
|
||||
* @param packet the packet to encode
|
||||
* @return a `BitVector` translated from the packet's data
|
||||
|
|
@ -214,12 +211,9 @@ object PacketCoding {
|
|||
final val PLANETSIDE_MIN_PACKET_SIZE = 1
|
||||
|
||||
/**
|
||||
* Transforms `BitVector` data into a PlanetSide packet.<br>
|
||||
* <br>
|
||||
* 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.
|
||||
* Transforms `ByteVector` data into a PlanetSide packet.
|
||||
* Attempt to decode with an optional header and required payload.
|
||||
* Does not decode into a `GamePacket`.
|
||||
* @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
|
||||
|
|
@ -237,18 +231,18 @@ 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.
|
||||
* Helper function to decode a packet without specifying a crypto state.
|
||||
* Used when there is no crypto state available such as in tests.
|
||||
* @param msg packet data bytes
|
||||
* @return `PlanetSidePacketContainer`
|
||||
*/
|
||||
def UnmarshalPacket(msg : ByteVector) : Attempt[PlanetSidePacketContainer] = UnmarshalPacket(msg, CryptoPacketOpcode.Ignore)
|
||||
|
||||
/**
|
||||
* Handle decoding for a packet that has been identified as not a control packet.
|
||||
* It may just be encrypted or it may be involved in the encryption process itself.
|
||||
* Handle decoding for a packet that has been identified as not a `ControlPacket`.
|
||||
* It may just be encrypted (`EncryptedPacket`) or it may be involved in the encryption process itself (`CryptoPacket`).
|
||||
* @param msg the packet
|
||||
* @param cryptoState the current state of the connection's crypto
|
||||
* @param cryptoState the current cryptographic state
|
||||
* @return a `PlanetSidePacketContainer`
|
||||
*/
|
||||
private def unmarshalFlaggedPacket(msg : ByteVector, cryptoState : CryptoPacketOpcode.Type) : Attempt[PlanetSidePacketContainer] = {
|
||||
|
|
@ -258,7 +252,6 @@ object PacketCoding {
|
|||
return Attempt.failure(Err("Failed to parse packet flags: " + e.message))
|
||||
case _ =>
|
||||
}
|
||||
|
||||
val flags = decodedFlags.require.value
|
||||
val packetType = flags.packetType
|
||||
packetType match {
|
||||
|
|
@ -275,7 +268,6 @@ object PacketCoding {
|
|||
case _ =>
|
||||
return Attempt.failure(Err("Unsupported packet type: " + flags.packetType.toString))
|
||||
}
|
||||
|
||||
//all packets have a two byte sequence ID
|
||||
val decodedSeq = uint16L.decode(decodedFlags.require.remainder) //TODO: make this a codec for reuse
|
||||
decodedSeq match {
|
||||
|
|
@ -285,7 +277,6 @@ object PacketCoding {
|
|||
}
|
||||
val sequence = decodedSeq.require.value
|
||||
val payload = decodedSeq.require.remainder.toByteVector
|
||||
|
||||
packetType match {
|
||||
case PacketType.Crypto =>
|
||||
unmarshalCryptoPacket(cryptoState, sequence, payload)
|
||||
|
|
@ -295,35 +286,37 @@ object PacketCoding {
|
|||
}
|
||||
|
||||
/**
|
||||
* Handle decoding for a control packet.
|
||||
* Handle decoding for a `ControlPacket`.
|
||||
* @param msg the packet
|
||||
* @return a `ControlPacket`
|
||||
*/
|
||||
private def unmarshalControlPacket(msg : ByteVector) : Attempt[ControlPacket] = {
|
||||
DecodeControlPacket(msg) match {
|
||||
case f @ Failure(_) => f
|
||||
case f @ Failure(_) =>
|
||||
f
|
||||
case Successful(p) =>
|
||||
Attempt.successful(CreateControlPacket(p))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle decoding for a game packet.
|
||||
* Handle decoding for a `GamePacket`.
|
||||
* @param sequence na
|
||||
* @param msg the packet data
|
||||
* @return a `GamePacket`
|
||||
*/
|
||||
private def unmarshalGamePacket(sequence : Int, msg : ByteVector) : Attempt[GamePacket] = {
|
||||
DecodeGamePacket(msg) match {
|
||||
case f @ Failure(_) => f
|
||||
case f @ Failure(_) =>
|
||||
f
|
||||
case Successful(p) =>
|
||||
Attempt.successful(CreateGamePacket(sequence, p))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle decoding for a crypto packet.
|
||||
* @param state the current state of the connection's crypto
|
||||
* Handle decoding for a `CryptoPacket`.
|
||||
* @param state the current cryptographic state
|
||||
* @param sequence na
|
||||
* @param payload the packet data
|
||||
* @return a `CryptoPacket`
|
||||
|
|
@ -338,8 +331,8 @@ object PacketCoding {
|
|||
}
|
||||
|
||||
/**
|
||||
* Handle decoding for an encrypted packet.
|
||||
* That is, it's already encrypted.
|
||||
* Handle decoding for an `EncryptedPacket`.
|
||||
* The payload is already encrypted.
|
||||
* Just repackage the data.
|
||||
* @param sequence na
|
||||
* @param payload the packet data
|
||||
|
|
@ -350,15 +343,17 @@ object PacketCoding {
|
|||
}
|
||||
|
||||
/**
|
||||
* Similar to `UnmarshalPacket`, but does not process any packet header and does not support decoding of crypto packets.
|
||||
* Transforms `ByteVector` data into a PlanetSide packet.
|
||||
* Similar to the `UnmarshalPacket` but it does not process packet headers.
|
||||
* It supports `GamePacket` in exchange for not supporting `CryptoPacket` (like `UnMarshalPacket`).
|
||||
* Mostly used in tests.
|
||||
* @param msg raw, unencrypted packet
|
||||
* @return `PlanetSidePacket`
|
||||
* @see `UnMarshalPacket`
|
||||
*/
|
||||
def DecodePacket(msg : ByteVector) : Attempt[PlanetSidePacket] = {
|
||||
if(msg.length < PLANETSIDE_MIN_PACKET_SIZE)
|
||||
return Attempt.failure(Err(s"Packet does not meet the minimum length of $PLANETSIDE_MIN_PACKET_SIZE bytes"))
|
||||
|
||||
val firstByte = msg{0}
|
||||
firstByte match {
|
||||
case 0x00 => DecodeControlPacket(msg.drop(1)) //control packets dont need the first byte
|
||||
|
|
@ -367,7 +362,7 @@ object PacketCoding {
|
|||
}
|
||||
|
||||
/**
|
||||
* Transform a `BitVector` into a control packet.
|
||||
* Transform a `ByteVector` into a `ControlPacket`.
|
||||
* @param msg the the raw data to decode
|
||||
* @return a `PlanetSideControlPacket`
|
||||
*/
|
||||
|
|
@ -388,7 +383,7 @@ object PacketCoding {
|
|||
}
|
||||
|
||||
/**
|
||||
* Transform a `BitVector` into a game packet.
|
||||
* Transform a `ByteVector` into a `GamePacket`.
|
||||
* @param msg the the raw data to decode
|
||||
* @return a `PlanetSideGamePacket`
|
||||
*/
|
||||
|
|
@ -411,28 +406,45 @@ object PacketCoding {
|
|||
/* Encrypting and Decrypting. */
|
||||
|
||||
/**
|
||||
* Encrypt the provided packet using the provided crypto state.
|
||||
* @param crypto the current state of the connection's crypto
|
||||
* Transform the privileged `packet` into a `RawPacket` representation to get:
|
||||
* the sequence number,
|
||||
* and the raw `ByteVector` data.
|
||||
* @param packet the unencrypted packet
|
||||
* @return an `EncryptedPacket`
|
||||
* @return paired data based on the packet
|
||||
*/
|
||||
def encryptPacket(crypto : CryptoInterface.CryptoStateWithMAC, packet : PlanetSidePacketContainer) : Attempt[EncryptedPacket] = {
|
||||
def getPacketDataForEncryption(packet : PlanetSidePacketContainer) : Attempt[(Int, ByteVector)] = {
|
||||
makeRawPacket(packet) match {
|
||||
case Successful(rawPacket) =>
|
||||
var sequenceNumber = 0
|
||||
packet match { //the sequence is a not default if this is a GamePacket
|
||||
case GamePacket(_, seq, _) => sequenceNumber = seq
|
||||
case _ => ;
|
||||
val sequenceNumber = packet match { //the sequence is variable if this is a GamePacket
|
||||
case GamePacket(_, seq, _) => seq
|
||||
case _ => 0
|
||||
}
|
||||
encryptPacket(crypto, sequenceNumber, rawPacket.toByteVector)
|
||||
Successful((sequenceNumber, rawPacket.toByteVector))
|
||||
|
||||
case f @ Failure(_) => f;
|
||||
case f @ Failure(_) =>
|
||||
f
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform either a game packet or a control packet into a `BitVector`.
|
||||
* This is more thorough than the process of unmarshalling, though the results are very similar.
|
||||
* Encrypt the provided packet using the provided cryptographic state.
|
||||
* Translate the packet into data to send on to the actual encryption process.
|
||||
* @param crypto the current cryptographic state
|
||||
* @param packet the unencrypted packet
|
||||
* @return an `EncryptedPacket`
|
||||
*/
|
||||
def encryptPacket(crypto : CryptoInterface.CryptoStateWithMAC, packet : PlanetSidePacketContainer) : Attempt[EncryptedPacket] = {
|
||||
getPacketDataForEncryption(packet) match {
|
||||
case Successful((seq, data)) =>
|
||||
encryptPacket(crypto, seq, data)
|
||||
case f @ Failure(_) =>
|
||||
f
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform either a `GamePacket` or a `ControlPacket` into a `BitVector`.
|
||||
* This is not as thorough as the process of unmarshalling though the results are very similar.
|
||||
* @param packet a packet
|
||||
* @return a `BitVector` that represents the packet
|
||||
*/
|
||||
|
|
@ -440,7 +452,8 @@ object PacketCoding {
|
|||
case GamePacket(opcode, _, payload) =>
|
||||
val opcodeEncoded = GamePacketOpcode.codec.encode(opcode)
|
||||
opcodeEncoded match {
|
||||
case Failure(e) => Attempt.failure(Err(s"Failed to marshal opcode in packet $opcode: " + e.message))
|
||||
case Failure(e) =>
|
||||
Attempt.failure(Err(s"Failed to marshal opcode in packet $opcode: " + e.message))
|
||||
case _ =>
|
||||
encodePacket(payload) match {
|
||||
case Failure(e) => Attempt.failure(Err(s"Failed to marshal packet $opcode: " + e.messageWithContext))
|
||||
|
|
@ -451,7 +464,8 @@ object PacketCoding {
|
|||
case ControlPacket(opcode, payload) =>
|
||||
val opcodeEncoded = ControlPacketOpcode.codec.encode(opcode)
|
||||
opcodeEncoded match {
|
||||
case Failure(e) => Attempt.failure(Err(s"Failed to marshal opcode in packet $opcode: " + e.messageWithContext))
|
||||
case Failure(e) =>
|
||||
Attempt.failure(Err(s"Failed to marshal opcode in packet $opcode: " + e.messageWithContext))
|
||||
case _ =>
|
||||
encodePacket(payload) match {
|
||||
case Failure(e) => Attempt.failure(Err(s"Failed to marshal packet $opcode: " + e.messageWithContext))
|
||||
|
|
@ -465,10 +479,10 @@ object PacketCoding {
|
|||
|
||||
/**
|
||||
* Perform encryption on the packet's raw data.
|
||||
* @param crypto the current state of the connection's crypto
|
||||
* @param crypto the current cryptographic state
|
||||
* @param sequenceNumber na
|
||||
* @param rawPacket a `ByteVector` that represents the packet data
|
||||
* @return
|
||||
* @return an `EncryptedPacket`
|
||||
*/
|
||||
def encryptPacket(crypto : CryptoInterface.CryptoStateWithMAC, sequenceNumber : Int, rawPacket : ByteVector) : Attempt[EncryptedPacket] = {
|
||||
val packetMac = crypto.macForEncrypt(rawPacket)
|
||||
|
|
@ -482,12 +496,39 @@ object PacketCoding {
|
|||
}
|
||||
|
||||
/**
|
||||
* Perform decryption on a packet's data.
|
||||
* Perform decryption on an `EncryptedPacket`.
|
||||
* @param crypto the current state of the connection's crypto
|
||||
* @param packet an encrypted packet
|
||||
* @return
|
||||
* @return a general packet container type
|
||||
*/
|
||||
def decryptPacket(crypto : CryptoInterface.CryptoStateWithMAC, packet : EncryptedPacket) : Attempt[PlanetSidePacketContainer] = {
|
||||
decryptPacketData(crypto, packet) match {
|
||||
case Successful(payload) => unmarshalPayload(packet.sequenceNumber, payload)
|
||||
case f @ Failure(_) => f
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform decrypted packet data into the type of packet that it represents.
|
||||
* Will not compose data into an `EncryptedPacket` or into a `CryptoPacket`.
|
||||
* @param sequenceNumber na
|
||||
* @param payload the decrypted packet data
|
||||
* @return a general packet container type
|
||||
*/
|
||||
def unmarshalPayload(sequenceNumber : Int, payload : ByteVector) : Attempt[PlanetSidePacketContainer] = {
|
||||
payload{0} match {
|
||||
case 0x00 => unmarshalControlPacket(payload.drop(1))
|
||||
case _ => unmarshalGamePacket(sequenceNumber, payload)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose the decrypted payload data from a formerly encrypted packet.
|
||||
* @param crypto the current state of the connection's crypto
|
||||
* @param packet an encrypted packet
|
||||
* @return a sequence of decrypted data
|
||||
*/
|
||||
def decryptPacketData(crypto : CryptoInterface.CryptoStateWithMAC, packet : EncryptedPacket) : Attempt[ByteVector] = {
|
||||
val payloadDecrypted = crypto.decrypt(packet.payload)
|
||||
val payloadJustLen = payloadDecrypted.takeRight(1) //get the last byte which is the padding length
|
||||
val padding = uint8L.decode(payloadJustLen.bits)
|
||||
|
|
@ -523,9 +564,6 @@ object PacketCoding {
|
|||
if(payloadNoMac.length < PLANETSIDE_MIN_PACKET_SIZE) {
|
||||
return Attempt.failure(Err(s"Decrypted packet does not meet the minimum length of $PLANETSIDE_MIN_PACKET_SIZE bytes"))
|
||||
}
|
||||
payloadNoMac{0} match {
|
||||
case 0x00 => unmarshalControlPacket(payloadNoMac.drop(1))
|
||||
case _ => unmarshalGamePacket(packet.sequenceNumber, payloadNoMac)
|
||||
}
|
||||
Successful(payloadNoMac)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,27 @@ import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideContro
|
|||
import scodec.Codec
|
||||
import scodec.codecs._
|
||||
|
||||
final case class ControlSync(timeDiff : Int, unk : Long,
|
||||
field1 : Long, field2 : Long, field3 : Long, field4 : Long,
|
||||
field64A : Long, field64B : Long)
|
||||
/**
|
||||
* Dispatched by the client periodically (approximately once every ten seconds).
|
||||
* @param timeDiff the exact number of milliseconds since the last `ControlSync` packet
|
||||
* @param unk na
|
||||
* @param field1 na
|
||||
* @param field2 na
|
||||
* @param field3 na
|
||||
* @param field4 na
|
||||
* @param field64A na;
|
||||
* increments by 41 per packet
|
||||
* @param field64B na;
|
||||
* increments by 21 per packet
|
||||
*/
|
||||
final case class ControlSync(timeDiff : Int,
|
||||
unk : Long,
|
||||
field1 : Long,
|
||||
field2 : Long,
|
||||
field3 : Long,
|
||||
field4 : Long,
|
||||
field64A : Long,
|
||||
field64B : Long)
|
||||
extends PlanetSideControlPacket {
|
||||
type Packet = ControlSync
|
||||
def opcode = ControlPacketOpcode.ControlSync
|
||||
|
|
|
|||
|
|
@ -5,8 +5,27 @@ import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideContro
|
|||
import scodec.Codec
|
||||
import scodec.codecs._
|
||||
|
||||
final case class ControlSyncResp(timeDiff : Int, serverTick : Long,
|
||||
field1 : Long, field2 : Long, field3 : Long, field4 : Long)
|
||||
/**
|
||||
* The response packet dispatched by the server to a client's `ControlSync` packet.
|
||||
* As noted, it echoes most of the fields originating from within its companion packet except for `serverTick`.
|
||||
* @param timeDiff na;
|
||||
* echoes `ControlSync.timeDiff`
|
||||
* @param serverTick na
|
||||
* @param field1 na;
|
||||
* echoes `ControlSync.field64A`
|
||||
* @param field2 na;
|
||||
* echoes `ControlSync.field64B`
|
||||
* @param field3 na;
|
||||
* echoes `ControlSync.field64B` (+/- 1)
|
||||
* @param field4 na;
|
||||
* echoes `ControlSync.field64A`
|
||||
*/
|
||||
final case class ControlSyncResp(timeDiff : Int,
|
||||
serverTick : Long,
|
||||
field1 : Long,
|
||||
field2 : Long,
|
||||
field3 : Long,
|
||||
field4 : Long)
|
||||
extends PlanetSideControlPacket {
|
||||
type Packet = ControlSyncResp
|
||||
def opcode = ControlPacketOpcode.ControlSyncResp
|
||||
|
|
|
|||
|
|
@ -3,15 +3,25 @@ package net.psforever.packet.control
|
|||
|
||||
import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideControlPacket}
|
||||
import scodec.Codec
|
||||
import scodec.bits.ByteVector
|
||||
import scodec.bits.{BitVector, ByteVector}
|
||||
import scodec.codecs._
|
||||
|
||||
final case class HandleGamePacket(packet : ByteVector)
|
||||
final case class HandleGamePacket(len : Int,
|
||||
stream : ByteVector,
|
||||
rest : BitVector = BitVector.empty)
|
||||
extends PlanetSideControlPacket {
|
||||
def opcode = ControlPacketOpcode.HandleGamePacket
|
||||
def encode = throw new Exception("This packet type should never be encoded")
|
||||
def encode = HandleGamePacket.encode(this)
|
||||
}
|
||||
|
||||
object HandleGamePacket extends Marshallable[HandleGamePacket] {
|
||||
implicit val codec : Codec[HandleGamePacket] = bytes.as[HandleGamePacket].decodeOnly
|
||||
}
|
||||
def apply(stream : ByteVector) : HandleGamePacket = {
|
||||
new HandleGamePacket(stream.length.toInt, stream)
|
||||
}
|
||||
|
||||
implicit val codec : Codec[HandleGamePacket] = (
|
||||
("len" | uint16) >>:~ { len =>
|
||||
("stream" | bytes(len)) ::
|
||||
("rest" | bits)
|
||||
}).as[HandleGamePacket]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.packet.control
|
||||
|
||||
import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideControlPacket}
|
||||
import scodec.Codec
|
||||
import scodec.codecs._
|
||||
|
||||
/**
|
||||
* Dispatched from the client in regards to errors trying to process prior `ControlPackets`.
|
||||
* Explains which packet was in error by sending back its `subslot` number.
|
||||
* @param subslot identification of a control packet
|
||||
*/
|
||||
final case class RelatedA0(subslot : Int)
|
||||
extends PlanetSideControlPacket {
|
||||
type Packet = RelatedA0
|
||||
def opcode = ControlPacketOpcode.RelatedA0
|
||||
def encode = RelatedA0.encode(this)
|
||||
}
|
||||
|
||||
object RelatedA0 extends Marshallable[RelatedA0] {
|
||||
implicit val codec : Codec[RelatedA0] = ("subslot" | uint16).as[RelatedA0]
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.packet.control
|
||||
|
||||
import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideControlPacket}
|
||||
import scodec.Codec
|
||||
import scodec.codecs._
|
||||
|
||||
/**
|
||||
* Dispatched to coordinate information regarding `ControlPacket` packets between the client and server.
|
||||
* When dispatched by the client, it relates the current (or last received) `SlottedMetaPacket` `subslot` number back to the server.
|
||||
* When dispatched by the server, it relates ???
|
||||
* @param subslot identification of a control packet
|
||||
*/
|
||||
final case class RelatedB0(subslot : Int)
|
||||
extends PlanetSideControlPacket {
|
||||
type Packet = RelatedB0
|
||||
def opcode = ControlPacketOpcode.RelatedB0
|
||||
def encode = RelatedB0.encode(this)
|
||||
}
|
||||
|
||||
object RelatedB0 extends Marshallable[RelatedB0] {
|
||||
implicit val codec : Codec[RelatedB0] = ("subslot" | uint16).as[RelatedB0]
|
||||
}
|
||||
|
|
@ -1,190 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
|
||||
import org.specs2.mutable._
|
||||
import org.specs2.specification
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.control._
|
||||
import org.specs2.specification.core.Fragment
|
||||
import scodec.Attempt.Successful
|
||||
import scodec.bits._
|
||||
import scodec.codecs.uint16
|
||||
|
||||
class ControlPacketTest extends Specification {
|
||||
|
||||
"PlanetSide control packet" in {
|
||||
"ControlSync" should {
|
||||
val string = hex"0007 5268 0000004D 00000052 0000004D 0000007C 0000004D 0000000000000276 0000000000000275"
|
||||
|
||||
"decode" in {
|
||||
PacketCoding.DecodePacket(string).require match {
|
||||
case ControlSync(a, b, c, d, e, f, g, h) =>
|
||||
a mustEqual 21096
|
||||
|
||||
b mustEqual 0x4d
|
||||
c mustEqual 0x52
|
||||
d mustEqual 0x4d
|
||||
e mustEqual 0x7c
|
||||
f mustEqual 0x4d
|
||||
|
||||
g mustEqual 0x276
|
||||
h mustEqual 0x275
|
||||
case default =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
val encoded = PacketCoding.EncodePacket(ControlSync(21096, 0x4d, 0x52, 0x4d, 0x7c, 0x4d, 0x276, 0x275)).require
|
||||
|
||||
encoded.toByteVector mustEqual string
|
||||
}
|
||||
}
|
||||
|
||||
"ControlSyncResp" should {
|
||||
val string = hex"0008 5268 21392D92 0000000000000276 0000000000000275 0000000000000275 0000000000000276"
|
||||
|
||||
"decode" in {
|
||||
PacketCoding.DecodePacket(string).require match {
|
||||
case ControlSyncResp(a, b, c, d, e, f) =>
|
||||
a mustEqual 21096
|
||||
|
||||
b mustEqual 0x21392D92
|
||||
c mustEqual 0x276
|
||||
d mustEqual 0x275
|
||||
e mustEqual 0x275
|
||||
f mustEqual 0x276
|
||||
case default =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
val encoded = PacketCoding.EncodePacket(ControlSyncResp(21096, 0x21392D92, 0x276, 0x275, 0x275, 0x276)).require
|
||||
|
||||
encoded.toByteVector mustEqual string
|
||||
}
|
||||
}
|
||||
|
||||
"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 =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode as an arbitrary slot and subslot" in {
|
||||
val maxSlots = ControlPacketOpcode.SlottedMetaPacket7.id - ControlPacketOpcode.SlottedMetaPacket0.id
|
||||
|
||||
// create all possible SlottedMetaPackets
|
||||
Fragment.foreach(0 to maxSlots) { i =>
|
||||
"slot " + i ! {
|
||||
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 =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"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]
|
||||
}
|
||||
}
|
||||
|
||||
"MultiPacketEx" should {
|
||||
val strings = Vector(
|
||||
hex"00",
|
||||
hex"01 41",
|
||||
hex"01 41" ++ hex"02 4142",
|
||||
hex"fe" ++ ByteVector.fill(0xfe)(0x41),
|
||||
hex"ffff00" ++ ByteVector.fill(0xff)(0x41),
|
||||
hex"ff0001" ++ ByteVector.fill(0x100)(0x41),
|
||||
hex"ff ffff ffff 0000" ++ ByteVector.fill(0x0000ffff)(0x41),
|
||||
hex"ff ffff 0000 0100" ++ ByteVector.fill(0x00010000)(0x41)
|
||||
)
|
||||
|
||||
val packets = Vector(
|
||||
MultiPacketEx(Vector(ByteVector.empty)),
|
||||
MultiPacketEx(Vector(hex"41")),
|
||||
MultiPacketEx(Vector(hex"41", hex"4142")),
|
||||
MultiPacketEx(Vector(ByteVector.fill(0xfe)(0x41))),
|
||||
MultiPacketEx(Vector(ByteVector.fill(0xff)(0x41))),
|
||||
MultiPacketEx(Vector(ByteVector.fill(0x100)(0x41))),
|
||||
MultiPacketEx(Vector(ByteVector.fill(0x0000ffff)(0x41))),
|
||||
MultiPacketEx(Vector(ByteVector.fill(0x00010000)(0x41)))
|
||||
)
|
||||
|
||||
"decode" in {
|
||||
Fragment.foreach(strings.indices) { i =>
|
||||
"test "+i ! { MultiPacketEx.decode(strings{i}.bits).require.value mustEqual packets{i} }
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
Fragment.foreach(packets.indices) { i =>
|
||||
"test "+i ! { MultiPacketEx.encode(packets{i}).require.toByteVector mustEqual strings{i} }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"TeardownConnection" should {
|
||||
val string = hex"00 05 02 4F 57 17 00 06"
|
||||
|
||||
"decode" in {
|
||||
PacketCoding.DecodePacket(string).require match {
|
||||
case TeardownConnection(nonce) =>
|
||||
nonce mustEqual 391597826
|
||||
case default =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
val encoded = PacketCoding.EncodePacket(TeardownConnection(391597826)).require
|
||||
|
||||
encoded.toByteVector mustEqual string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
common/src/test/scala/control/ControlSyncRespTest.scala
Normal file
32
common/src/test/scala/control/ControlSyncRespTest.scala
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package control
|
||||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.control._
|
||||
import scodec.bits._
|
||||
|
||||
class ControlSyncRespTest extends Specification {
|
||||
val string = hex"0008 5268 21392D92 0000000000000276 0000000000000275 0000000000000275 0000000000000276"
|
||||
|
||||
"decode" in {
|
||||
PacketCoding.DecodePacket(string).require match {
|
||||
case ControlSyncResp(a, b, c, d, e, f) =>
|
||||
a mustEqual 21096
|
||||
|
||||
b mustEqual 0x21392D92
|
||||
c mustEqual 0x276
|
||||
d mustEqual 0x275
|
||||
e mustEqual 0x275
|
||||
f mustEqual 0x276
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
val encoded = PacketCoding.EncodePacket(ControlSyncResp(21096, 0x21392D92, 0x276, 0x275, 0x275, 0x276)).require
|
||||
|
||||
encoded.toByteVector mustEqual string
|
||||
}
|
||||
}
|
||||
32
common/src/test/scala/control/ControlSyncTest.scala
Normal file
32
common/src/test/scala/control/ControlSyncTest.scala
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package control
|
||||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.control._
|
||||
import scodec.bits._
|
||||
|
||||
class ControlSyncTest extends Specification {
|
||||
val string = hex"0007 5268 0000004D 00000052 0000004D 0000007C 0000004D 0000000000000276 0000000000000275"
|
||||
|
||||
"decode" in {
|
||||
PacketCoding.DecodePacket(string).require match {
|
||||
case ControlSync(a, b, c, d, e, f, g, h) =>
|
||||
a mustEqual 21096
|
||||
b mustEqual 0x4d
|
||||
c mustEqual 0x52
|
||||
d mustEqual 0x4d
|
||||
e mustEqual 0x7c
|
||||
f mustEqual 0x4d
|
||||
g mustEqual 0x276
|
||||
h mustEqual 0x275
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
val encoded = PacketCoding.EncodePacket(ControlSync(21096, 0x4d, 0x52, 0x4d, 0x7c, 0x4d, 0x276, 0x275)).require
|
||||
encoded.toByteVector mustEqual string
|
||||
}
|
||||
}
|
||||
30
common/src/test/scala/control/HandleGamePacketTest.scala
Normal file
30
common/src/test/scala/control/HandleGamePacketTest.scala
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package control
|
||||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.control._
|
||||
import scodec.bits._
|
||||
|
||||
class HandleGamePacketTest extends Specification {
|
||||
//this is the first from a series of SlottedMetaPacket4s; the length field was modified from 12 DC to pass the test
|
||||
val base = hex"18 D5 96 00 00 BC 8E 00 03 A2 16 5D A4 5F B0 80 00 04 30 40 00 08 30 46 00 4A 00 48 00 02 02 F0 62 1E 80 80 00 00 00 00 00 3F FF CC 0D 40 00 20 00 03 00 27 C3 01 C8 00 00 03 08 00 00 03 FF FF FF FC A4 04 00 00 62 00 18 02 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 C8 00 00 01 00 7E C8 00 C8 00 00 00 5D B0 81 40 00 00 00 00 00 00 00 00 00 00 00 00 02 C0 00 40 83 85 46 86 C7 07 8A 4A 80 70 0C 00 01 98 00 00 01 24 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 31 30 90 78 70 65 5F 6A 6F 69 6E 5F 70 6C 61 74 6F 6F 6E 92 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 31 34 8F 78 70 65 5F 6A 6F 69 6E 5F 6F 75 74 66 69 74 92 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 31 31 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 39 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 38 92 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 31 33 93 78 70 65 5F 77 61 72 70 5F 67 61 74 65 5F 75 73 61 67 65 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 32 92 78 70 65 5F 69 6E 73 74 61 6E 74 5F 61 63 74 69 6F 6E 8E 78 70 65 5F 66 6F 72 6D 5F 73 71 75 61 64 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 36 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 37 8E 78 70 65 5F 6A 6F 69 6E 5F 73 71 75 61 64 8C 78 70 65 5F 62 69 6E 64 5F 61 6D 73 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 35 91 78 70 65 5F 62 69 6E 64 5F 66 61 63 69 6C 69 74"
|
||||
val string = hex"00 00 01 CB" ++ base
|
||||
|
||||
"decode" in {
|
||||
PacketCoding.DecodePacket(string).require match {
|
||||
case HandleGamePacket(len, data, extra) =>
|
||||
len mustEqual 459
|
||||
data mustEqual base
|
||||
extra mustEqual BitVector.empty
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
val pkt = HandleGamePacket(base)
|
||||
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||
msg mustEqual string
|
||||
}
|
||||
}
|
||||
43
common/src/test/scala/control/MultiPacketExTest.scala
Normal file
43
common/src/test/scala/control/MultiPacketExTest.scala
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package control
|
||||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet.control._
|
||||
import org.specs2.specification.core.Fragment
|
||||
import scodec.bits._
|
||||
|
||||
class MultiPacketExTest extends Specification {
|
||||
val strings = Vector(
|
||||
hex"00",
|
||||
hex"01 41",
|
||||
hex"01 41" ++ hex"02 4142",
|
||||
hex"fe" ++ ByteVector.fill(0xfe)(0x41),
|
||||
hex"ffff00" ++ ByteVector.fill(0xff)(0x41),
|
||||
hex"ff0001" ++ ByteVector.fill(0x100)(0x41),
|
||||
hex"ff ffff ffff 0000" ++ ByteVector.fill(0x0000ffff)(0x41),
|
||||
hex"ff ffff 0000 0100" ++ ByteVector.fill(0x00010000)(0x41)
|
||||
)
|
||||
|
||||
val packets = Vector(
|
||||
MultiPacketEx(Vector(ByteVector.empty)),
|
||||
MultiPacketEx(Vector(hex"41")),
|
||||
MultiPacketEx(Vector(hex"41", hex"4142")),
|
||||
MultiPacketEx(Vector(ByteVector.fill(0xfe)(0x41))),
|
||||
MultiPacketEx(Vector(ByteVector.fill(0xff)(0x41))),
|
||||
MultiPacketEx(Vector(ByteVector.fill(0x100)(0x41))),
|
||||
MultiPacketEx(Vector(ByteVector.fill(0x0000ffff)(0x41))),
|
||||
MultiPacketEx(Vector(ByteVector.fill(0x00010000)(0x41)))
|
||||
)
|
||||
|
||||
"decode" in {
|
||||
Fragment.foreach(strings.indices) { i =>
|
||||
"test "+i ! { MultiPacketEx.decode(strings{i}.bits).require.value mustEqual packets{i} }
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
Fragment.foreach(packets.indices) { i =>
|
||||
"test "+i ! { MultiPacketEx.encode(packets{i}).require.toByteVector mustEqual strings{i} }
|
||||
}
|
||||
}
|
||||
}
|
||||
26
common/src/test/scala/control/RelatedATest.scala
Normal file
26
common/src/test/scala/control/RelatedATest.scala
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package control
|
||||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.control._
|
||||
import scodec.bits._
|
||||
|
||||
class RelatedATest extends Specification {
|
||||
val string0 = hex"00 11 01 04"
|
||||
|
||||
"decode (0)" in {
|
||||
PacketCoding.DecodePacket(string0).require match {
|
||||
case RelatedA0(slot) =>
|
||||
slot mustEqual 260
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode (0)" in {
|
||||
val pkt = RelatedA0(260)
|
||||
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||
msg mustEqual string0
|
||||
}
|
||||
}
|
||||
26
common/src/test/scala/control/RelatedBTest.scala
Normal file
26
common/src/test/scala/control/RelatedBTest.scala
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package control
|
||||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.control._
|
||||
import scodec.bits._
|
||||
|
||||
class RelatedBTest extends Specification {
|
||||
val string0 = hex"00 15 01 04"
|
||||
|
||||
"decode (0)" in {
|
||||
PacketCoding.DecodePacket(string0).require match {
|
||||
case RelatedB0(slot) =>
|
||||
slot mustEqual 260
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode (0)" in {
|
||||
val pkt = RelatedB0(260)
|
||||
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||
msg mustEqual string0
|
||||
}
|
||||
}
|
||||
76
common/src/test/scala/control/SlottedMetaPacketTest.scala
Normal file
76
common/src/test/scala/control/SlottedMetaPacketTest.scala
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package control
|
||||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.control._
|
||||
import org.specs2.specification.core.Fragment
|
||||
import scodec.bits._
|
||||
import scodec.codecs.uint16
|
||||
|
||||
class SlottedMetaPacketTest extends Specification {
|
||||
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 _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode as an arbitrary slot and subslot" in {
|
||||
val maxSlots = ControlPacketOpcode.SlottedMetaPacket7.id - ControlPacketOpcode.SlottedMetaPacket0.id
|
||||
|
||||
// create all possible SlottedMetaPackets
|
||||
Fragment.foreach(0 to maxSlots) { i =>
|
||||
"slot " + i ! {
|
||||
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 _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"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]
|
||||
}
|
||||
}
|
||||
26
common/src/test/scala/control/TeardownConnectionTest.scala
Normal file
26
common/src/test/scala/control/TeardownConnectionTest.scala
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package control
|
||||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.control._
|
||||
import scodec.bits._
|
||||
|
||||
class TeardownConnectionTest extends Specification {
|
||||
val string = hex"00 05 02 4F 57 17 00 06"
|
||||
|
||||
"decode" in {
|
||||
PacketCoding.DecodePacket(string).require match {
|
||||
case TeardownConnection(nonce) =>
|
||||
nonce mustEqual 391597826
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
val encoded = PacketCoding.EncodePacket(TeardownConnection(391597826)).require
|
||||
|
||||
encoded.toByteVector mustEqual string
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue