Skip to content

Commit

Permalink
implement first steps for packet/response handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Kevin Wiesmüller committed May 20, 2018
1 parent 86ee2fa commit 05aced5
Show file tree
Hide file tree
Showing 3 changed files with 268 additions and 7 deletions.
97 changes: 94 additions & 3 deletions pkg/rcon/battleye/reader.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,101 @@
package battleye

import (
be "github.com/playnet-public/battleye/battleye"
"time"

"github.com/pkg/errors"
be_proto "github.com/playnet-public/battleye/battleye"
)

// HandlePacket received from UDP connection
func (c *Connection) HandlePacket(data be.Packet) {
c.Protocol.Verify(data)
func (c *Connection) HandlePacket(p be_proto.Packet) (err error) {
defer func() {
err = errors.Wrap(err, "handling packet")
}()
err = c.Protocol.Verify(p)
if err != nil {
// TODO: Add logging
return err
}
data, err := c.Protocol.Data(p)
if err != nil {
// TODO: Add logging
return err
}

// Handle KeepAlive Pingback
if len(data) < 1 {
// TODO: Add logging
c.AddPingback()
return nil
}

t, err := c.Protocol.Type(p)
if err != nil {
// TODO: Add logging
return err
}

switch t {
case be_proto.Command | be_proto.MultiCommand:
return c.HandleResponse(p)

case be_proto.ServerMessage:
// Handle MultiCommand
return nil

}

return nil
}

// HandleResponse by retrieving the corresponding transmission and updating it
func (c *Connection) HandleResponse(p be_proto.Packet) error {
s, err := c.Protocol.Sequence(p)
if err != nil {
return errors.Wrap(err, "handling response")
}

trm := c.GetTransmission(s)
if trm == nil {
return errors.New("no transmission for response")
}

t, err := c.Protocol.Type(p)
if err != nil {
return errors.Wrap(err, "handling response")
}

data, err := c.Protocol.Data(p)
if err != nil {
return errors.Wrap(err, "handling response")
}

last := true
if t == be_proto.MultiCommand {
count, index, single := c.Protocol.Multi(p)
if !single {
trm.multiBuffer[int(index)] = data
last = (index+1 >= count)
}
} else {
trm.multiBuffer[0] = data
}

if last {
select {
case trm.done <- true:
return nil
case <-time.After(time.Second):
// TODO: Add debug log for transmission done timeouts
return nil
}
}

return nil
}

// HandleServerMessage containing chat and events
func (c *Connection) HandleServerMessage() error {
return nil
}
149 changes: 147 additions & 2 deletions pkg/rcon/battleye/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/pkg/errors"
be_proto "github.com/playnet-public/battleye/battleye"
be_mocks "github.com/playnet-public/battleye/mocks"
be "github.com/playnet-public/gorcon/pkg/rcon/battleye"
Expand All @@ -23,13 +24,157 @@ var _ = Describe("Reader", func() {
})

Describe("HandlePacket", func() {
It("should call VerifyPacket", func() {
BeforeEach(func() {
pr.VerifyReturns(nil)
pr.DataReturns([]byte("test"), nil)
pr.TypeReturns(be_proto.Command, nil)
})
It("does return nil", func() {
pr.TypeReturns(0x12, nil)
Expect(con.HandlePacket(nil)).To(BeNil())
})
It("should call Verify", func() {
con.HandlePacket([]byte(""))
Expect(pr.VerifyCallCount()).To(BeEquivalentTo(1))
})
It("should call VerifyPacket with data", func() {
It("does call Verify with packet", func() {
con.HandlePacket([]byte("test"))
Expect(pr.VerifyArgsForCall(0)).To(BeEquivalentTo(be_proto.Packet("test")))
})
It("does return error on invalid packet", func() {
pr.VerifyReturns(errors.New("test"))
Expect(con.HandlePacket(nil)).NotTo(BeNil())
})
It("does call Data", func() {
con.HandlePacket([]byte(""))
Expect(pr.DataCallCount()).To(BeEquivalentTo(1))
})
It("should call Data with packet", func() {
con.HandlePacket([]byte("test"))
Expect(pr.DataArgsForCall(0)).To(BeEquivalentTo(be_proto.Packet("test")))
})
It("does return error on corrupt data", func() {
pr.DataReturns(nil, errors.New("test"))
Expect(con.HandlePacket(nil)).NotTo(BeNil())
})
It("does call Type", func() {
con.HandlePacket([]byte("test"))
Expect(pr.TypeCallCount()).To(BeEquivalentTo(1))
})
It("does call Type with packet", func() {
con.HandlePacket([]byte("test"))
Expect(pr.TypeArgsForCall(0)).To(BeEquivalentTo(be_proto.Packet("test")))
})
It("does return error if Type returns error", func() {
pr.TypeReturns(0x00, errors.New("test"))
Expect(con.HandlePacket(nil)).NotTo(BeNil())
})
Context("when given empty data", func() {
It("does increase pingback", func() {
pb := con.Pingback()
pr.DataReturns([]byte(""), nil)
con.HandlePacket([]byte(""))
Expect(con.Pingback()).To(BeNumerically(">", pb))
})
})
It("does return nil when handling ServerMessage", func() {
pr.TypeReturns(be_proto.ServerMessage, nil)
Expect(con.HandlePacket(nil)).To(BeNil())
})
})

Describe("HandleResponse", func() {
BeforeEach(func() {
pr.SequenceReturns(0, nil)
trm := be.NewTransmission("test")
go func() {
<-trm.Done()
}()
con.AddTransmission(0, trm)
pr.TypeReturns(be_proto.Command, nil)
pr.DataReturns([]byte("test data"), nil)
pr.MultiReturns(2, 1, false)
})
It("does not return error", func() {
Expect(con.HandleResponse(be_proto.Packet("test"))).To(BeNil())
})
It("does return error if no transmission is present", func() {
con.AddTransmission(0, nil)
Expect(con.HandleResponse(be_proto.Packet("test"))).NotTo(BeNil())
})
It("does call Sequence with packet", func() {
con.HandleResponse([]byte("test"))
Expect(pr.SequenceCallCount()).To(BeEquivalentTo(1))
Expect(pr.SequenceArgsForCall(0)).To(BeEquivalentTo(be_proto.Packet("test")))
})
It("does return error if Sequence returns error", func() {
pr.SequenceReturns(0, errors.New("test"))
Expect(con.HandleResponse(nil)).NotTo(BeNil())
})
It("does call Type with packet", func() {
con.HandleResponse([]byte("test"))
Expect(pr.TypeCallCount()).To(BeEquivalentTo(1))
Expect(pr.TypeArgsForCall(0)).To(BeEquivalentTo(be_proto.Packet("test")))
})
It("does return error if Type returns error", func() {
pr.TypeReturns(0x02, errors.New("test"))
Expect(con.HandleResponse(nil)).NotTo(BeNil())
})
It("does call Data with packet", func() {
con.HandleResponse([]byte("test"))
Expect(pr.DataCallCount()).To(BeEquivalentTo(1))
Expect(pr.DataArgsForCall(0)).To(BeEquivalentTo(be_proto.Packet("test")))
})
It("does return error if Data returns error", func() {
pr.DataReturns([]byte(""), errors.New("test"))
Expect(con.HandleResponse(nil)).NotTo(BeNil())
})
Context("on multi command", func() {
BeforeEach(func() {
pr.TypeReturns(be_proto.MultiCommand, nil)
})
It("does add correct index to buffer", func() {
pr.DataReturns([]byte("test "), nil)
pr.MultiReturns(2, 0, false)
con.HandleResponse(nil)
pr.DataReturns([]byte("data"), nil)
pr.MultiReturns(2, 1, false)
con.HandleResponse(nil)
trm := con.GetTransmission(0)
Expect(trm.Response()).To(BeEquivalentTo("test data"))
})
})
Context("on single command", func() {
BeforeEach(func() {
pr.TypeReturns(be_proto.Command, nil)
})
It("does add correct index to buffer", func() {
pr.DataReturns([]byte("test data"), nil)
con.HandleResponse(nil)
trm := con.GetTransmission(0)
Expect(trm.Response()).To(BeEquivalentTo("test data"))
})
})
It("does send to done channel", func() {
trm := be.NewTransmission("test")
con.AddTransmission(0, trm)
go func() {
pr.TypeReturns(be_proto.Command, nil)
pr.DataReturns([]byte("test data"), nil)
con.HandleResponse(nil)
}()
Expect(<-trm.Done()).To(BeTrue())
})
It("does timeout on blocking done channel", func() {
trm := be.NewTransmission("test")
con.AddTransmission(0, trm)
Expect(con.HandleResponse(nil)).To(BeNil())
})
})

Describe("HandleServerMessage", func() {
It("does return nil", func() {
Expect(con.HandleServerMessage()).To(BeNil())
})
})
})
29 changes: 27 additions & 2 deletions pkg/rcon/battleye/transmission.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
package battleye

import (
"sort"
)

// Transmission is the BattlEye implementation of rcon.Transmission
type Transmission struct {
seq uint32
request []byte
done chan bool
response []byte

// As we might receive multiple packets responding to a single transmission
// we have to collect them by their respective id and return them after a final sort
// Thanks UDP.
multiBuffer map[int][]byte
}

// NewTransmission containing request
func NewTransmission(request string) *Transmission {
return &Transmission{
request: []byte(request),
done: make(chan bool),
request: []byte(request),
done: make(chan bool),
multiBuffer: make(map[int][]byte),
}
}

Expand All @@ -33,6 +43,21 @@ func (t *Transmission) Done() <-chan bool {

// Response returns the final response
// Checking if the transmission is done before retrieving is suggested
// Otherwise this might render the transaction useless caused by the way multiResponsePackets get handled
// TODO: If this might as well be done for every call
func (t *Transmission) Response() string {
if len(t.response) < 1 {
// Sort the responses stored in buffer
var keys []int
for k := range t.multiBuffer {
keys = append(keys, k)
}
sort.Ints(keys)

// Build final response
for _, k := range keys {
t.response = append(t.response, t.multiBuffer[k]...)
}
}
return string(t.response)
}

0 comments on commit 05aced5

Please sign in to comment.