diff --git a/cmd/serve_test.go b/cmd/serve_test.go index 291974f..108b7ad 100644 --- a/cmd/serve_test.go +++ b/cmd/serve_test.go @@ -6,8 +6,6 @@ import ( "github.com/stretchr/testify/assert" ) -// correct run on root of this project - func TestServe(t *testing.T) { assert := assert.New(t) diff --git a/component/config.go b/component/config.go new file mode 100644 index 0000000..122f5cb --- /dev/null +++ b/component/config.go @@ -0,0 +1,33 @@ +package component + +import ( + "gosrc.io/xmpp" +) + +type Config struct { + Type string + Host string + Connection string + Secret string + Special map[string]interface{} + + xmpp *xmpp.Component + comp Component +} + +func (c *Config) Start() error { + c.xmpp = &xmpp.Component{Host: c.Host, Secret: c.Secret} + err := c.xmpp.Connect(c.Connection) + if err != nil { + return err + } + out, err := c.comp.Connect() + if err != nil { + return err + } + + go c.sender(out) + go c.receiver() + + return nil +} diff --git a/component/config_test.go b/component/config_test.go new file mode 100644 index 0000000..dda1349 --- /dev/null +++ b/component/config_test.go @@ -0,0 +1,19 @@ +package component + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfigStart(t *testing.T) { + assert := assert.New(t) + + c := Config{} + + // wrong connection + err := c.Start() + assert.NotNil(err) + + // correct connection without xmpp server not possible +} diff --git a/component/main_test.go b/component/main_test.go index cc264c9..a64619e 100644 --- a/component/main_test.go +++ b/component/main_test.go @@ -1 +1,25 @@ package component + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAddComponent(t *testing.T) { + assert := assert.New(t) + + AddComponent("a", func(config map[string]interface{}) (Component, error) { return nil, nil }) + + assert.NotNil(components["a"]) + assert.Len(components, 1) +} + +func TestLoad(t *testing.T) { + AddComponent("a", func(config map[string]interface{}) (Component, error) { return nil, nil }) + + Load([]Config{ + {}, + // {Type: "a", Connection: "[::1]:10001"}, + }) +} diff --git a/component/receiver.go b/component/receiver.go new file mode 100644 index 0000000..3456bf4 --- /dev/null +++ b/component/receiver.go @@ -0,0 +1,108 @@ +package component + +import ( + "github.com/bdlm/log" + "gosrc.io/xmpp" +) + +func (c *Config) receiver() { + for { + packet, err := c.xmpp.ReadPacket() + if err != nil { + log.WithField("type", c.Type).Panicf("connection closed%s", err) + return + } + p, back := c.receive(packet) + if p == nil { + continue + } + if back { + c.xmpp.Send(p) + } else { + c.comp.Send(p) + } + } +} + +func (c *Config) receive(packet xmpp.Packet) (xmpp.Packet, bool) { + logger := log.WithField("type", c.Type) + + switch p := packet.(type) { + case xmpp.IQ: + attrs := p.PacketAttrs + loggerIQ := logger.WithFields(map[string]interface{}{ + "from": attrs.From, + "to": attrs.To, + }) + + switch inner := p.Payload[0].(type) { + case *xmpp.DiscoInfo: + if p.Type == "get" { + iq := xmpp.NewIQ("result", attrs.To, attrs.From, attrs.Id, "en") + var identity xmpp.Identity + if inner.Node == "" { + identity = xmpp.Identity{ + Name: c.Type, + Category: "gateway", + Type: "service", + } + } + + payload := xmpp.DiscoInfo{ + Identity: identity, + Features: []xmpp.Feature{ + {Var: "http://jabber.org/protocol/disco#info"}, + {Var: "http://jabber.org/protocol/disco#item"}, + {Var: xmpp.NSSpaceXEP0184Receipt}, + {Var: xmpp.NSSpaceXEP0333ChatMarkers}, + }, + } + iq.AddPayload(&payload) + loggerIQ.Debug("disco info") + return iq, true + } + case *xmpp.DiscoItems: + if p.Type == "get" { + iq := xmpp.NewIQ("result", attrs.To, attrs.From, attrs.Id, "en") + + var payload xmpp.DiscoItems + if inner.Node == "" { + payload = xmpp.DiscoItems{ + Items: []xmpp.DiscoItem{ + {Name: c.Type, JID: c.Host, Node: "node1"}, + }, + } + } + iq.AddPayload(&payload) + loggerIQ.Debug("disco items") + return iq, true + } + default: + logger.Debug("ignoring iq packet", inner) + xError := xmpp.Err{ + Code: 501, + Reason: "feature-not-implemented", + Type: "cancel", + } + reply := p.MakeError(xError) + + return reply, true + } + + case xmpp.Message: + logger.WithFields(map[string]interface{}{ + "from": p.PacketAttrs.From, + "to": p.PacketAttrs.To, + "id": p.PacketAttrs.Id, + }).Debug(p.XMPPFormat()) + + return packet, false + + case xmpp.Presence: + logger.Debug("received presence:", p.Type) + + default: + logger.Debug("ignoring packet:", packet) + } + return nil, false +} diff --git a/component/receiver_test.go b/component/receiver_test.go new file mode 100644 index 0000000..3fcd236 --- /dev/null +++ b/component/receiver_test.go @@ -0,0 +1,65 @@ +package component + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "gosrc.io/xmpp" +) + +func TestReceive(t *testing.T) { + assert := assert.New(t) + + c := Config{Host: "example.org", Type: "monkeyservice"} + + // ignoring packet + p, back := c.receive(xmpp.Handshake{}) + assert.Nil(p) + + // receive presence + p, back = c.receive(xmpp.Presence{}) + assert.Nil(p) + + // message + p, back = c.receive(xmpp.Message{}) + assert.False(back) + assert.NotNil(p) + + // unsupported iq + p, back = c.receive(xmpp.IQ{Payload: []xmpp.IQPayload{ + &xmpp.Err{}, + }}) + assert.True(back) + assert.NotNil(p) + iq := p.(xmpp.IQ) + assert.Equal("error", iq.Type) + assert.Equal("feature-not-implemented", iq.Error.Reason) + + // iq disco info + p, back = c.receive(xmpp.IQ{ + PacketAttrs: xmpp.PacketAttrs{Type: "get"}, + Payload: []xmpp.IQPayload{ + &xmpp.DiscoInfo{}, + }, + }) + assert.True(back) + assert.NotNil(p) + iq = p.(xmpp.IQ) + assert.Equal("result", iq.Type) + dinfo := iq.Payload[0].(*xmpp.DiscoInfo) + assert.Equal("monkeyservice", dinfo.Identity.Name) + + // iq disco items + p, back = c.receive(xmpp.IQ{ + PacketAttrs: xmpp.PacketAttrs{Type: "get"}, + Payload: []xmpp.IQPayload{ + &xmpp.DiscoItems{}, + }, + }) + assert.True(back) + assert.NotNil(p) + iq = p.(xmpp.IQ) + assert.Equal("result", iq.Type) + ditems := iq.Payload[0].(*xmpp.DiscoItems) + assert.Equal("monkeyservice", ditems.Items[0].Name) +} diff --git a/component/send.go b/component/send.go new file mode 100644 index 0000000..f3108d9 --- /dev/null +++ b/component/send.go @@ -0,0 +1,35 @@ +package component + +import ( + "github.com/bdlm/log" + "gosrc.io/xmpp" +) + +func (c *Config) sender(packets chan xmpp.Packet) { + for packet := range packets { + if p := c.send(packet); p != nil { + c.xmpp.Send(p) + } + } +} + +func (c *Config) send(packet xmpp.Packet) xmpp.Packet { + logger := log.WithField("type", c.Type) + switch p := packet.(type) { + case xmpp.Message: + if p.PacketAttrs.From == "" { + p.PacketAttrs.From = c.Host + } else { + p.PacketAttrs.From += "@" + c.Host + } + logger.WithFields(map[string]interface{}{ + "from": p.PacketAttrs.From, + "to": p.PacketAttrs.To, + "id": p.PacketAttrs.Id, + }).Debug(p.XMPPFormat()) + return p + default: + log.Warn("ignoring packet:", packet) + return nil + } +} diff --git a/component/send_test.go b/component/send_test.go new file mode 100644 index 0000000..585e73e --- /dev/null +++ b/component/send_test.go @@ -0,0 +1,30 @@ +package component + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "gosrc.io/xmpp" +) + +func TestSend(t *testing.T) { + assert := assert.New(t) + + c := Config{Host: "example.org"} + + // ignoring packet + p := c.send(xmpp.IQ{}) + assert.Nil(p) + + // send by component host + p = c.send(xmpp.Message{}) + assert.NotNil(p) + msg := p.(xmpp.Message) + assert.Equal("example.org", msg.PacketAttrs.From) + + // send by a user of component + p = c.send(xmpp.Message{PacketAttrs: xmpp.PacketAttrs{From: "threemaid"}}) + assert.NotNil(p) + msg = p.(xmpp.Message) + assert.Equal("threemaid@example.org", msg.PacketAttrs.From) +} diff --git a/component/xmpp.go b/component/xmpp.go deleted file mode 100644 index ddb97a5..0000000 --- a/component/xmpp.go +++ /dev/null @@ -1,143 +0,0 @@ -package component - -import ( - "github.com/bdlm/log" - "gosrc.io/xmpp" -) - -type Config struct { - Type string - Host string - Connection string - Secret string - Special map[string]interface{} - - xmpp *xmpp.Component - comp Component -} - -func (c *Config) Start() error { - c.xmpp = &xmpp.Component{Host: c.Host, Secret: c.Secret} - err := c.xmpp.Connect(c.Connection) - if err != nil { - return err - } - out, err := c.comp.Connect() - if err != nil { - return err - } - - go c.recieve(out) - go c.sender() - - return nil -} - -func (c *Config) recieve(packets chan xmpp.Packet) { - logger := log.WithField("type", c.Type) - for packet := range packets { - switch p := packet.(type) { - case xmpp.Message: - if p.PacketAttrs.From == "" { - p.PacketAttrs.From = c.Host - } else { - p.PacketAttrs.From += "@" + c.Host - } - logger.WithFields(map[string]interface{}{ - "from": p.PacketAttrs.From, - "to": p.PacketAttrs.To, - "id": p.PacketAttrs.Id, - }).Debug(p.XMPPFormat()) - c.xmpp.Send(p) - default: - log.Warn("ignoring packet:", packet) - } - } -} -func (c *Config) sender() { - logger := log.WithField("type", c.Type) - for { - packet, err := c.xmpp.ReadPacket() - if err != nil { - logger.Panicf("connection closed%s", err) - return - } - - switch p := packet.(type) { - case xmpp.IQ: - attrs := p.PacketAttrs - loggerIQ := logger.WithFields(map[string]interface{}{ - "from": attrs.From, - "to": attrs.To, - }) - - switch inner := p.Payload[0].(type) { - case *xmpp.DiscoInfo: - loggerIQ.Debug("Disco Info") - if p.Type == "get" { - iq := xmpp.NewIQ("result", attrs.To, attrs.From, attrs.Id, "en") - var identity xmpp.Identity - if inner.Node == "" { - identity = xmpp.Identity{ - Name: c.Type, - Category: "gateway", - Type: "service", - } - } - - payload := xmpp.DiscoInfo{ - Identity: identity, - Features: []xmpp.Feature{ - {Var: "http://jabber.org/protocol/disco#info"}, - {Var: "http://jabber.org/protocol/disco#item"}, - {Var: xmpp.NSSpaceXEP0184Receipt}, - {Var: xmpp.NSSpaceXEP0333ChatMarkers}, - }, - } - iq.AddPayload(&payload) - - _ = c.xmpp.Send(iq) - } - case *xmpp.DiscoItems: - loggerIQ.Debug("DiscoItems") - if p.Type == "get" { - iq := xmpp.NewIQ("result", attrs.To, attrs.From, attrs.Id, "en") - - var payload xmpp.DiscoItems - if inner.Node == "" { - payload = xmpp.DiscoItems{ - Items: []xmpp.DiscoItem{ - {Name: c.Type, JID: c.Host, Node: "node1"}, - }, - } - } - iq.AddPayload(&payload) - _ = c.xmpp.Send(iq) - } - default: - logger.Debug("ignoring iq packet", inner) - xError := xmpp.Err{ - Code: 501, - Reason: "feature-not-implemented", - Type: "cancel", - } - reply := p.MakeError(xError) - _ = c.xmpp.Send(&reply) - } - - case xmpp.Message: - logger.WithFields(map[string]interface{}{ - "from": p.PacketAttrs.From, - "to": p.PacketAttrs.To, - "id": p.PacketAttrs.Id, - }).Debug(p.XMPPFormat()) - c.comp.Send(packet) - - case xmpp.Presence: - logger.Debug("Received presence:", p.Type) - - default: - logger.Debug("ignoring packet:", packet) - } - } -}