Skip to content

Commit

Permalink
Initial commit of XEP-0114 implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
sheenobu committed Feb 14, 2016
0 parents commit b17c2ef
Show file tree
Hide file tree
Showing 11 changed files with 470 additions and 0 deletions.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2016 Sheena Artrip

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# go-xco

Library for building XMPP/Jabber ( XEP-0114 ) components in golang.

## Usage:

import (
"github.com/sheenobu/go-xco"
)

func main(){

opts := &xco.Options{
Name: Name,
SharedSecret: SharedSecret,
Address: Address,
}

c, err := opts.NewComponent()
if err != nil {
panic(err)
}

// Uppercase Echo Component
c.MessageHandler = func(c *xco.Component, msg *xco.Message) error {
m := xco.Message{
Header: xco.Header{
From: msg.To,
To: msg.From,
ID: msg.ID,
},
Subject: msg.Subject,
Thread: msg.Thread,
Type: msg.Type,
Body: strings.ToUpper(msg.Body),
XMLName: msg.XMLName,
}

return c.Send(m)
}

c.Run()
}



61 changes: 61 additions & 0 deletions cmd/echo-component/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package main

import (
"flag"
"os"
"strings"

"github.com/sheenobu/go-xco"
)

var Name string
var SharedSecret string
var Address string

func init() {
flag.StringVar(&Name, "name", "", "Name of Component")
flag.StringVar(&SharedSecret, "secret", "", "Shared Secret between server and component")
flag.StringVar(&Address, "address", "", "Hostname:port address of XMPP component listener")
}

func main() {

flag.Parse()

if Name == "" || SharedSecret == "" || Address == "" {
flag.Usage()
os.Exit(-1)
return
}

opts := &xco.Options{
Name: Name,
SharedSecret: SharedSecret,
Address: Address,
}

c, err := opts.NewComponent()
if err != nil {
panic(err)
}

// Uppercase Echo Component
c.MessageHandler = func(c *xco.Component, msg *xco.Message) error {
m := xco.Message{
Header: xco.Header{
From: msg.To,
To: msg.From,
ID: msg.ID,
},
Subject: msg.Subject,
Thread: msg.Thread,
Type: msg.Type,
Body: strings.ToUpper(msg.Body),
XMLName: msg.XMLName,
}

return c.Send(m)
}

c.Run()
}
78 changes: 78 additions & 0 deletions component.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package xco

import (
"encoding/xml"
"fmt"
"net"
"os"

"golang.org/x/net/context"
)

type stateFn func() (stateFn, error)

// A Component is an instance of a Jabber Component (XEP-0114)
type Component struct {
MessageHandler MessageHandler
PresenceHandler PresenceHandler
IqHandler IqHandler

ctx context.Context
cancelFn context.CancelFunc

conn net.Conn
dec *xml.Decoder
enc *xml.Encoder

stateFn stateFn

sharedSecret string
name string
}

func (c *Component) dial(o *Options) error {
conn, err := net.Dial("tcp", o.Address)
if err != nil {
return err
}

c.MessageHandler = noOpMessageHandler
c.PresenceHandler = noOpPresenceHandler
c.IqHandler = noOpIqHandler

c.conn = conn
c.name = o.Name
c.sharedSecret = o.SharedSecret
c.dec = xml.NewDecoder(conn)
c.enc = xml.NewEncoder(conn)
c.stateFn = c.handshakeState

return nil
}

func (c *Component) Close() {
c.cancelFn()
}

func (c *Component) Run() {

defer func() {
c.conn.Close()
}()

var err error

for {
if c.stateFn == nil {
return
}
c.stateFn, err = c.stateFn()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
}
}
}

func (c *Component) Send(i interface{}) error {
return c.enc.Encode(i)
}
66 changes: 66 additions & 0 deletions handshake.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package xco

import (
"crypto/sha1"
"encoding/hex"
"encoding/xml"
"errors"
"fmt"
)

func (c *Component) handshakeState() (stateFn, error) {

if _, err := c.conn.Write([]byte(fmt.Sprintf(`
<stream:stream
xmlns='jabber:component:accept'
xmlns:stream='http://etherx.jabber.org/streams'
to='%s'>`, c.name))); err != nil {
return nil, err
}

for {
t, err := c.dec.Token()
if err != nil {
return nil, err
}

stream, ok := t.(xml.StartElement)
if !ok {
continue
}

var id string

for _, a := range stream.Attr {
if a.Name.Local == "id" {
id = a.Value
}
}

if id == "" {
return nil, errors.New("Unable to find ID in stream response")
}

handshakeInput := id + c.sharedSecret
handshake := sha1.Sum([]byte(handshakeInput))
hexHandshake := hex.EncodeToString(handshake[:])
if _, err := c.conn.Write([]byte(fmt.Sprintf("<handshake>%s</handshake>", hexHandshake))); err != nil {
return nil, err
}

//TODO: separate each step into a state

// get handshake response
t, err = c.dec.Token()
if err != nil {
return nil, err
}

t, err = c.dec.Token()
if err != nil {
return nil, err
}

return c.readLoopState, nil
}
}
7 changes: 7 additions & 0 deletions header.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package xco

type Header struct {
ID string `xml:"id,attr"`
From string `xml:"from,attr"` //TODO: make address type
To string `xml:"to,attr"` //TODO: make address type
}
17 changes: 17 additions & 0 deletions iq.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package xco

type Iq struct {
Header

Type string `xml:"type,attr"`

Content string `xml:",innerxml"`

XMLName string `xml:"iq"`
}

type IqHandler func(c *Component, iq *Iq) error

func noOpIqHandler(c *Component, iq *Iq) error {
return nil
}
43 changes: 43 additions & 0 deletions message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package xco

import "encoding/xml"

// MessageType defines the constants for the types of messages within XEP-0114
type MessageType string

const (

// CHAT defines the chat message type
CHAT = MessageType("chat")

// ERROR defines the error message type
ERROR = MessageType("error")

// GROUPCHAT defines the group chat message type
GROUPCHAT = MessageType("groupchat")

// HEADLINE defines the headline message type
HEADLINE = MessageType("headline")

// NORMAL defines the normal message type
NORMAL = MessageType("normal")
)

// A Message is an incoming or outgoing Component message
type Message struct {
Header
Type MessageType `xml:"type,attr,omitempty"`

Subject string `xml:"subject,omitempty"`
Body string `xml:"body"`
Thread string `xml:"thread,omitempty"`

XMLName xml.Name
}

// A MessageHandler handles a message
type MessageHandler func(*Component, *Message) error

func noOpMessageHandler(c *Component, m *Message) error {
return nil
}
36 changes: 36 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package xco

import "golang.org/x/net/context"

// Options define the series of options required to build a component
type Options struct {

// Name defines the component name
Name string

// SharedSecret is the secret shared between the server and component
SharedSecret string

// Address is the address of the XMPP server
Address string

// The (optional) parent context
Context context.Context
}

// NewComponent creates a new component from the given options
func (opts *Options) NewComponent() (*Component, error) {

if opts.Context == nil {
opts.Context = context.Background()
}

var c Component
c.ctx, c.cancelFn = context.WithCancel(opts.Context)

if err := c.dial(opts); err != nil {
return nil, err
}

return &c, nil
}
Loading

0 comments on commit b17c2ef

Please sign in to comment.