From 05aced51b3e847e63fb1273a8be513256ba64506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Wiesmu=CC=88ller?= Date: Sun, 20 May 2018 03:39:10 +0200 Subject: [PATCH] implement first steps for packet/response handling --- pkg/rcon/battleye/reader.go | 97 ++++++++++++++++++- pkg/rcon/battleye/reader_test.go | 149 +++++++++++++++++++++++++++++- pkg/rcon/battleye/transmission.go | 29 +++++- 3 files changed, 268 insertions(+), 7 deletions(-) diff --git a/pkg/rcon/battleye/reader.go b/pkg/rcon/battleye/reader.go index cb3e550..73d18e3 100644 --- a/pkg/rcon/battleye/reader.go +++ b/pkg/rcon/battleye/reader.go @@ -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 } diff --git a/pkg/rcon/battleye/reader_test.go b/pkg/rcon/battleye/reader_test.go index 870211a..442c635 100644 --- a/pkg/rcon/battleye/reader_test.go +++ b/pkg/rcon/battleye/reader_test.go @@ -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" @@ -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()) + }) }) }) diff --git a/pkg/rcon/battleye/transmission.go b/pkg/rcon/battleye/transmission.go index 0ad1669..aba76d9 100644 --- a/pkg/rcon/battleye/transmission.go +++ b/pkg/rcon/battleye/transmission.go @@ -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), } } @@ -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) }