mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-03-04 04:30:21 +00:00
commit
fc3837268c
15 changed files with 1156 additions and 268 deletions
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.packet
|
||||
|
||||
import net.psforever.packet.control.SlottedMetaPacket
|
||||
import net.psforever.packet.control.{RelatedA, RelatedB, SlottedMetaPacket}
|
||||
import scodec.bits.BitVector
|
||||
import scodec.{Attempt, Codec, DecodeResult, Err}
|
||||
import scodec.codecs._
|
||||
|
|
@ -74,15 +74,15 @@ object ControlPacketOpcode extends Enumeration {
|
|||
|
||||
// OPCODES 0x10-1e
|
||||
case 0x10 => SlottedMetaPacket.decodeWithOpcode(SlottedMetaPacket7)
|
||||
case 0x11 => control.RelatedA0.decode
|
||||
case 0x12 => noDecoder(RelatedA1)
|
||||
case 0x13 => noDecoder(RelatedA2)
|
||||
case 0x14 => noDecoder(RelatedA3)
|
||||
case 0x15 => control.RelatedB0.decode
|
||||
case 0x16 => noDecoder(RelatedB1)
|
||||
case 0x17 => noDecoder(RelatedB2)
|
||||
case 0x11 => RelatedA.decodeWithOpcode(RelatedA0)
|
||||
case 0x12 => RelatedA.decodeWithOpcode(RelatedA1)
|
||||
case 0x13 => RelatedA.decodeWithOpcode(RelatedA2)
|
||||
case 0x14 => RelatedA.decodeWithOpcode(RelatedA3)
|
||||
case 0x15 => RelatedB.decodeWithOpcode(RelatedB0)
|
||||
case 0x16 => RelatedB.decodeWithOpcode(RelatedB1)
|
||||
case 0x17 => RelatedB.decodeWithOpcode(RelatedB2)
|
||||
// 0x18
|
||||
case 0x18 => noDecoder(RelatedB3)
|
||||
case 0x18 => RelatedB.decodeWithOpcode(RelatedB3)
|
||||
case 0x19 => control.MultiPacketEx.decode
|
||||
case 0x1a => noDecoder(Unknown26)
|
||||
case 0x1b => noDecoder(Unknown27)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.packet.control
|
||||
|
||||
import net.psforever.packet.PlanetSidePacket
|
||||
|
||||
/**
|
||||
* Message for holding a series of packets being moved through the system (server),
|
||||
* eventually be bundled into a `MultiPacketEx` and dispatched to the client.
|
||||
* Invalid packets are eliminated at the time of creation.
|
||||
* At least one packet is necessary.
|
||||
* @param packets a series of packets to be bundled together;
|
||||
* this list is effectively immutable;
|
||||
* the only way to access these packets is through pattern matching
|
||||
*/
|
||||
final case class MultiPacketBundle(private var packets : List[PlanetSidePacket]) {
|
||||
MultiPacketBundle.collectValidPackets(packets) match {
|
||||
case Nil =>
|
||||
throw new IllegalArgumentException("can not create with zero packets")
|
||||
case list =>
|
||||
packets = list
|
||||
}
|
||||
|
||||
def +(t : MultiPacketBundle) : MultiPacketBundle = t match {
|
||||
case MultiPacketBundle(list) =>
|
||||
MultiPacketBundle(packets ++ list)
|
||||
case _ =>
|
||||
MultiPacketBundle(packets)
|
||||
}
|
||||
}
|
||||
|
||||
object MultiPacketBundle {
|
||||
/**
|
||||
* Accept a series of packets of a specific supertype (`PlanetSidePacket`)
|
||||
* and filter out subtypes that should be excluded.
|
||||
* Show a generic disclaimer if any packets were filtered.
|
||||
* Two of the four subclasses of `PlanetSidePacket` are accepted - `PlanetSideGamePacket` and `PlanetSideControlPacket`.
|
||||
* @param packets a series of packets
|
||||
* @return the accepted packets from the original group
|
||||
*/
|
||||
def collectValidPackets(packets : List[PlanetSidePacket]) : List[PlanetSidePacket] = {
|
||||
import net.psforever.packet.{PlanetSideGamePacket, PlanetSideControlPacket}
|
||||
val (good, bad) = packets.partition( {
|
||||
case _ : PlanetSideGamePacket => true
|
||||
case _ : PlanetSideControlPacket => true
|
||||
case _ => false
|
||||
})
|
||||
if(bad.nonEmpty) {
|
||||
org.log4s.getLogger("MultiPacketBundle")
|
||||
.warn(s"attempted to include packet types that are not in the whitelist; ${bad.size} items have been excluded")
|
||||
}
|
||||
good
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulator for packets that will eventually be bundled and submitted for composing a `MultiPacketEx` packet.
|
||||
*/
|
||||
class MultiPacketCollector() {
|
||||
private var bundle : List[PlanetSidePacket] = List.empty
|
||||
|
||||
def Add(t : PlanetSidePacket) : Unit = Add(List(t))
|
||||
|
||||
def Add(t : MultiPacketBundle) : Unit = t match {
|
||||
case MultiPacketBundle(list) =>
|
||||
Add(list)
|
||||
}
|
||||
|
||||
def Add(t : List[PlanetSidePacket]) : Unit = {
|
||||
if(t.nonEmpty) {
|
||||
bundle = bundle ++ t
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the internal collection of packets.
|
||||
* Reset the internal list of packets by clearing it.
|
||||
* @return a loaded `MultiPacketBundle` object
|
||||
*/
|
||||
def Bundle : MultiPacketBundle = {
|
||||
try {
|
||||
val out = MultiPacketBundle(bundle)
|
||||
bundle = List.empty
|
||||
out
|
||||
}
|
||||
catch {
|
||||
case _ : Exception => //catch and rethrow the exception
|
||||
throw new RuntimeException("no packets")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A safer `Bundle` that consumes any` Exceptions` that might be thrown in the process of producing output.
|
||||
* @see `Bundle`
|
||||
* @return a loaded `MultiPacketBundle` object, or `None`
|
||||
*/
|
||||
def BundleOption : Option[MultiPacketBundle] = {
|
||||
try {
|
||||
Some(Bundle)
|
||||
}
|
||||
catch {
|
||||
case _ : Exception =>
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object MultiPacketCollector {
|
||||
/**
|
||||
* Overload constructor that accepts initial packets.
|
||||
* @param bundle previously bundled packets
|
||||
* @return a `MultiPacketCollector` object
|
||||
*/
|
||||
def apply(bundle : MultiPacketBundle) : MultiPacketCollector = {
|
||||
val obj = new MultiPacketCollector()
|
||||
obj.Add(bundle)
|
||||
obj
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload constructor that accepts initial packets.
|
||||
* @param packets a series of packets
|
||||
* @return a `MultiPacketCollector` object
|
||||
*/
|
||||
def apply(packets : List[PlanetSidePacket]) : MultiPacketCollector = {
|
||||
val obj = new MultiPacketCollector()
|
||||
obj.Add(packets)
|
||||
obj
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.packet.control
|
||||
|
||||
import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideControlPacket}
|
||||
import scodec.Codec
|
||||
import scodec.bits.BitVector
|
||||
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 slot the type of `ResultA` packet;
|
||||
* valid types are integers 0-3
|
||||
* @param subslot identification of a control packet
|
||||
*/
|
||||
final case class RelatedA(slot : Int, subslot : Int) extends PlanetSideControlPacket {
|
||||
type Packet = RelatedA
|
||||
if(slot < 0 || slot > 3) {
|
||||
throw new IllegalArgumentException(s"slot number is out of range - $slot")
|
||||
}
|
||||
|
||||
def opcode = {
|
||||
val base = ControlPacketOpcode.RelatedA0.id
|
||||
ControlPacketOpcode(base + slot)
|
||||
}
|
||||
def encode = RelatedA.encode(this).map(vect => vect.drop(8))
|
||||
}
|
||||
|
||||
object RelatedA extends Marshallable[RelatedA] {
|
||||
implicit val codec : Codec[RelatedA] = (
|
||||
("slot" | uint8L.xmap[Int](a => a - ControlPacketOpcode.RelatedA0.id, a=>a) ) ::
|
||||
("subslot" | uint16) // the slot is big endian. see 0x00A42F76
|
||||
).as[RelatedA]
|
||||
|
||||
def decodeWithOpcode(slot : ControlPacketOpcode.Value)(bits : BitVector) = {
|
||||
decode(ControlPacketOpcode.codec.encode(slot).require ++ bits)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
// 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,39 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.packet.control
|
||||
|
||||
import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideControlPacket}
|
||||
import scodec.Codec
|
||||
import scodec.bits.BitVector
|
||||
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 slot the type of `ResultB` packet;
|
||||
* valid types are integers 0-3
|
||||
* @param subslot identification of a control packet
|
||||
*/
|
||||
final case class RelatedB(slot : Int, subslot : Int) extends PlanetSideControlPacket {
|
||||
type Packet = RelatedB
|
||||
if(slot < 0 || slot > 3) {
|
||||
throw new IllegalArgumentException(s"slot number is out of range - $slot")
|
||||
}
|
||||
|
||||
def opcode = {
|
||||
val base = ControlPacketOpcode.RelatedB0.id
|
||||
ControlPacketOpcode(base + slot)
|
||||
}
|
||||
def encode = RelatedB.encode(this).map(vect => vect.drop(8))
|
||||
}
|
||||
|
||||
object RelatedB extends Marshallable[RelatedB] {
|
||||
implicit val codec : Codec[RelatedB] = (
|
||||
("slot" | uint8L.xmap[Int](a => a - ControlPacketOpcode.RelatedB0.id, a=>a) ) ::
|
||||
("subslot" | uint16) // the slot is big endian. see 0x00A42F76
|
||||
).as[RelatedB]
|
||||
|
||||
def decodeWithOpcode(slot : ControlPacketOpcode.Value)(bits : BitVector) = {
|
||||
decode(ControlPacketOpcode.codec.encode(slot).require ++ bits)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
// 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,35 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.packet.control
|
||||
|
||||
import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideControlPacket}
|
||||
import scodec.Codec
|
||||
import scodec.bits.BitVector
|
||||
import scodec.codecs._
|
||||
|
||||
final case class SlottedMetaAck(slot : Int, subslot : Int)
|
||||
extends PlanetSideControlPacket {
|
||||
type Packet = SlottedMetaAck
|
||||
|
||||
assert(slot >= 0 && slot <= 7, s"Slot number ($slot) is out of range") //TODO 7 types of SlottedMeta, 4 types of ResultB?
|
||||
|
||||
def opcode = {
|
||||
val base = ControlPacketOpcode.RelatedB0.id
|
||||
ControlPacketOpcode(base + slot % 4)
|
||||
}
|
||||
|
||||
// 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 = SlottedMetaAck.encode(this).map(vect => vect.drop(8))
|
||||
}
|
||||
|
||||
object SlottedMetaAck extends Marshallable[SlottedMetaAck] {
|
||||
implicit val codec : Codec[SlottedMetaAck] = (
|
||||
("slot" | uint8L.xmap[Int](a => a - ControlPacketOpcode.RelatedB0.id, a=>a) ) ::
|
||||
("subslot" | uint16)
|
||||
).as[SlottedMetaAck]
|
||||
|
||||
def decodeWithOpcode(slot : ControlPacketOpcode.Value)(bits : BitVector) = {
|
||||
decode(ControlPacketOpcode.codec.encode(slot).require ++ bits)
|
||||
}
|
||||
}
|
||||
183
common/src/test/scala/control/MultiPacketCollectorTest.scala
Normal file
183
common/src/test/scala/control/MultiPacketCollectorTest.scala
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package control
|
||||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet.control.{ControlSync, MultiPacketBundle, MultiPacketCollector}
|
||||
import net.psforever.packet.crypto.{ClientFinished, ServerFinished}
|
||||
import net.psforever.packet.game.{ObjectDeleteMessage, PlanetSideGUID}
|
||||
|
||||
class MultiPacketCollectorTest extends Specification {
|
||||
val packet1 = ObjectDeleteMessage(PlanetSideGUID(1103), 2)
|
||||
|
||||
"MultiPacketBundle" should {
|
||||
import scodec.bits._
|
||||
val packet2 = ControlSync(21096, 0x4d, 0x52, 0x4d, 0x7c, 0x4d, 0x276, 0x275)
|
||||
|
||||
"construct" in {
|
||||
MultiPacketBundle(List(packet1))
|
||||
ok
|
||||
}
|
||||
|
||||
"fail to construct if not initialized with PlanetSidePackets" in {
|
||||
MultiPacketBundle(Nil) must throwA[IllegalArgumentException]
|
||||
}
|
||||
|
||||
"concatenate bundles into a new bundle" in {
|
||||
val obj1 = MultiPacketBundle(List(packet1))
|
||||
val obj2 = MultiPacketBundle(List(packet2))
|
||||
val obj3 = obj1 + obj2
|
||||
obj3 match {
|
||||
case MultiPacketBundle(list) =>
|
||||
list.size mustEqual 2
|
||||
list.head mustEqual packet1
|
||||
list(1) mustEqual packet2
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"accept PlanetSideGamePackets and PlanetSideControlPackets" in {
|
||||
MultiPacketBundle(List(packet2, packet1)) match {
|
||||
case MultiPacketBundle(list) =>
|
||||
list.size mustEqual 2
|
||||
list.head mustEqual packet2
|
||||
list(1) mustEqual packet1
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"ignore other types of PlanetSideContainerPackets" in {
|
||||
val param = List(packet2, ClientFinished(hex"", hex""), packet1, ServerFinished(hex""))
|
||||
MultiPacketBundle(param) match { //warning message will display in log
|
||||
case MultiPacketBundle(list) =>
|
||||
list.size mustEqual 2
|
||||
list.head mustEqual param.head
|
||||
list(1) mustEqual param(2)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"MultiPacketCollector" should {
|
||||
val packet2 = ObjectDeleteMessage(PlanetSideGUID(1105), 2)
|
||||
val packet3 = ObjectDeleteMessage(PlanetSideGUID(1107), 2)
|
||||
|
||||
"construct" in {
|
||||
new MultiPacketCollector()
|
||||
ok
|
||||
}
|
||||
|
||||
"construct with initial packets" in {
|
||||
MultiPacketCollector(List(packet1, packet2))
|
||||
ok
|
||||
}
|
||||
|
||||
"can retrieve a bundle packets" in {
|
||||
val obj = MultiPacketCollector(List(packet1, packet2))
|
||||
obj.Bundle match {
|
||||
case MultiPacketBundle(list) =>
|
||||
list.size mustEqual 2
|
||||
list.head mustEqual packet1
|
||||
list(1) mustEqual packet2
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"can not retrieve a bundle of non-existent packets" in {
|
||||
val obj = new MultiPacketCollector()
|
||||
obj.Bundle must throwA[RuntimeException]
|
||||
}
|
||||
|
||||
"can safely retrieve a bundle of potential packets" in {
|
||||
val obj1 = new MultiPacketCollector()
|
||||
obj1.BundleOption match {
|
||||
case Some(_) =>
|
||||
ko
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
val obj2 = MultiPacketCollector(List(packet1, packet2))
|
||||
obj2.BundleOption match {
|
||||
case None =>
|
||||
ko
|
||||
case Some(MultiPacketBundle(list)) =>
|
||||
list.size mustEqual 2
|
||||
list.head mustEqual packet1
|
||||
list(1) mustEqual packet2
|
||||
}
|
||||
}
|
||||
|
||||
"clear packets after being asked to bundle" in {
|
||||
val list = List(packet1, packet2)
|
||||
val obj = MultiPacketCollector(list)
|
||||
obj.Bundle mustEqual MultiPacketBundle(list)
|
||||
obj.Bundle must throwA[RuntimeException]
|
||||
}
|
||||
|
||||
"add a packet" in {
|
||||
val obj = new MultiPacketCollector()
|
||||
obj.Add(packet1)
|
||||
obj.Bundle match {
|
||||
case MultiPacketBundle(list) =>
|
||||
list.size mustEqual 1
|
||||
list.head mustEqual packet1
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"add packets" in {
|
||||
val obj = new MultiPacketCollector()
|
||||
obj.Add(List(packet1, packet2))
|
||||
obj.Bundle match {
|
||||
case MultiPacketBundle(list) =>
|
||||
list.size mustEqual 2
|
||||
list.head mustEqual packet1
|
||||
list(1) mustEqual packet2
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"concatenate bundles (1)" in {
|
||||
|
||||
val obj1 = new MultiPacketCollector()
|
||||
obj1.Add(List(packet1, packet2))
|
||||
val bundle1 = obj1.Bundle
|
||||
|
||||
val obj2 = MultiPacketCollector(bundle1)
|
||||
obj2.Add(packet3)
|
||||
obj2.Bundle match {
|
||||
case MultiPacketBundle(list) =>
|
||||
list.size mustEqual 3
|
||||
list.head mustEqual packet1
|
||||
list(1) mustEqual packet2
|
||||
list(2) mustEqual packet3
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"concatenate bundles (2)" in {
|
||||
val obj1 = new MultiPacketCollector()
|
||||
obj1.Add(List(packet1, packet2))
|
||||
val bundle1 = obj1.Bundle
|
||||
|
||||
val obj2 = new MultiPacketCollector()
|
||||
obj2.Add(packet3)
|
||||
obj2.Add(bundle1)
|
||||
obj2.Bundle match {
|
||||
case MultiPacketBundle(list) =>
|
||||
list.size mustEqual 3
|
||||
list.head mustEqual packet3
|
||||
list(1) mustEqual packet1
|
||||
list(2) mustEqual packet2
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,24 +3,80 @@ package control
|
|||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.control._
|
||||
import net.psforever.packet.control.RelatedA
|
||||
import scodec.bits._
|
||||
|
||||
class RelatedATest extends Specification {
|
||||
val string0 = hex"00 11 01 04"
|
||||
val string1 = hex"00 12 01 04"
|
||||
val string2 = hex"00 13 01 04"
|
||||
val string3 = hex"00 14 01 04"
|
||||
|
||||
"decode (0)" in {
|
||||
PacketCoding.DecodePacket(string0).require match {
|
||||
case RelatedA0(slot) =>
|
||||
slot mustEqual 260
|
||||
case RelatedA(slot, subslot) =>
|
||||
slot mustEqual 0
|
||||
subslot mustEqual 260
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (1)" in {
|
||||
PacketCoding.DecodePacket(string1).require match {
|
||||
case RelatedA(slot, subslot) =>
|
||||
slot mustEqual 1
|
||||
subslot mustEqual 260
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (2)" in {
|
||||
PacketCoding.DecodePacket(string2).require match {
|
||||
case RelatedA(slot, subslot) =>
|
||||
slot mustEqual 2
|
||||
subslot mustEqual 260
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (3)" in {
|
||||
PacketCoding.DecodePacket(string3).require match {
|
||||
case RelatedA(slot, subslot) =>
|
||||
slot mustEqual 3
|
||||
subslot mustEqual 260
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode (0)" in {
|
||||
val pkt = RelatedA0(260)
|
||||
val pkt = RelatedA(0, 260)
|
||||
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||
msg mustEqual string0
|
||||
}
|
||||
|
||||
"encode (1)" in {
|
||||
val pkt = RelatedA(1, 260)
|
||||
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||
msg mustEqual string1
|
||||
}
|
||||
|
||||
"encode (2)" in {
|
||||
val pkt = RelatedA(2, 260)
|
||||
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||
msg mustEqual string2
|
||||
}
|
||||
|
||||
"encode (3)" in {
|
||||
val pkt = RelatedA(3, 260)
|
||||
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||
msg mustEqual string3
|
||||
}
|
||||
|
||||
"encode (n)" in {
|
||||
RelatedA(4, 260) must throwA[IllegalArgumentException]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,24 +3,80 @@ package control
|
|||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.control._
|
||||
import net.psforever.packet.control.RelatedB
|
||||
import scodec.bits._
|
||||
|
||||
class RelatedBTest extends Specification {
|
||||
val string0 = hex"00 15 01 04"
|
||||
val string1 = hex"00 16 01 04"
|
||||
val string2 = hex"00 17 01 04"
|
||||
val string3 = hex"00 18 01 04"
|
||||
|
||||
"decode (0)" in {
|
||||
PacketCoding.DecodePacket(string0).require match {
|
||||
case RelatedB0(slot) =>
|
||||
slot mustEqual 260
|
||||
case RelatedB(slot, subslot) =>
|
||||
slot mustEqual 0
|
||||
subslot mustEqual 260
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (1)" in {
|
||||
PacketCoding.DecodePacket(string1).require match {
|
||||
case RelatedB(slot, subslot) =>
|
||||
slot mustEqual 1
|
||||
subslot mustEqual 260
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (2)" in {
|
||||
PacketCoding.DecodePacket(string2).require match {
|
||||
case RelatedB(slot, subslot) =>
|
||||
slot mustEqual 2
|
||||
subslot mustEqual 260
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (3)" in {
|
||||
PacketCoding.DecodePacket(string3).require match {
|
||||
case RelatedB(slot, subslot) =>
|
||||
slot mustEqual 3
|
||||
subslot mustEqual 260
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode (0)" in {
|
||||
val pkt = RelatedB0(260)
|
||||
val pkt = RelatedB(0, 260)
|
||||
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||
msg mustEqual string0
|
||||
}
|
||||
|
||||
"encode (1)" in {
|
||||
val pkt = RelatedB(1, 260)
|
||||
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||
msg mustEqual string1
|
||||
}
|
||||
|
||||
"encode (2)" in {
|
||||
val pkt = RelatedB(2, 260)
|
||||
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||
msg mustEqual string2
|
||||
}
|
||||
|
||||
"encode (3)" in {
|
||||
val pkt = RelatedB(3, 260)
|
||||
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||
msg mustEqual string3
|
||||
}
|
||||
|
||||
"encode (n)" in {
|
||||
RelatedB(4, 260) must throwA[IllegalArgumentException]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package control
|
||||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.control._
|
||||
import scodec.bits._
|
||||
|
||||
class SlottedMetaAckTest extends Specification {
|
||||
val string = hex"00150da4"
|
||||
|
||||
"decode" in {
|
||||
PacketCoding.DecodePacket(string).require match {
|
||||
case SlottedMetaAck(_, _) =>
|
||||
ko
|
||||
case RelatedB0(subslot) => //important!
|
||||
subslot mustEqual 3492
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
val pkt = SlottedMetaAck(0, 3492)
|
||||
val msg = PacketCoding.EncodePacket(pkt).require.toByteVector
|
||||
|
||||
msg mustEqual string
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue