# HG changeset patch # User Chris Jones # Date 1355721469 25200 # Node ID 7696e6a017090fdee3a4269fc0871ef68cf07101 # Parent 3887d7ad19c17e648c77abc49cbd07148b8962b0 Instead of making Stanza an interface that Iq, Message, and Presence implement, change it to an embedded struct. diff -r 3887d7ad19c1 -r 7696e6a01709 examples/interact.go --- a/examples/interact.go Sun Dec 16 19:55:17 2012 -0700 +++ b/examples/interact.go Sun Dec 16 22:17:49 2012 -0700 @@ -7,21 +7,23 @@ import ( xmpp ".." "crypto/tls" + "encoding/xml" "flag" "fmt" "log" "os" + "strings" ) type StdLogger struct { } func (s *StdLogger) Log(v ...interface{}) { - log.Println(v) + log.Println(v...) } func (s *StdLogger) Logf(fmt string, v ...interface{}) { - log.Printf(fmt, v) + log.Printf(fmt, v...) } func init() { @@ -61,7 +63,7 @@ fmt.Printf("%d: %v\n", i, entry) } - go func(ch <-chan xmpp.Stanza) { + go func(ch <-chan interface{}) { for obj := range ch { fmt.Printf("s: %v\n", obj) } @@ -75,7 +77,31 @@ break } s := string(p) - stan, err := xmpp.ParseStanza(s) + dec := xml.NewDecoder(strings.NewReader(s)) + t, err := dec.Token() + if err != nil { + fmt.Printf("token: %s\n", err) + break + } + var se *xml.StartElement + var ok bool + if se, ok = t.(*xml.StartElement) ; !ok { + fmt.Println("Couldn't find start element") + break + } + var stan interface{} + switch se.Name.Local { + case "iq": + stan = &xmpp.Iq{} + case "message": + stan = &xmpp.Message{} + case "presence": + stan = &xmpp.Presence{} + default: + fmt.Println("Can't parse non-stanza.") + continue + } + err = dec.Decode(stan) if err == nil { c.Out <- stan } else { diff -r 3887d7ad19c1 -r 7696e6a01709 roster.go --- a/roster.go Sun Dec 16 19:55:17 2012 -0700 +++ b/roster.go Sun Dec 16 22:17:49 2012 -0700 @@ -48,14 +48,14 @@ func fetchRoster(client *Client) error { rosterUpdate := rosterClients[client.Uid].rosterUpdate - iq := &Iq{From: client.Jid.String(), Id: <-Id, Type: "get", - Nested: []interface{}{RosterQuery{}}} + iq := &Iq{Stanza: Stanza{From: client.Jid.String(), Type: "get", + Id: <-Id, Nested: []interface{}{RosterQuery{}}}} ch := make(chan error) - f := func(st Stanza) bool { + f := func(v interface{}) bool { defer close(ch) - iq, ok := st.(*Iq) + iq, ok := v.(*Iq) if !ok { - ch <- fmt.Errorf("response to iq wasn't iq: %s", st) + ch <- fmt.Errorf("response to iq wasn't iq: %s", v) return false } if iq.Type == "error" { @@ -71,7 +71,7 @@ } if rq == nil { ch <- fmt.Errorf( - "Roster query result not query: %v", st) + "Roster query result not query: %v", v) return false } for _, item := range rq.Item { @@ -91,9 +91,9 @@ // the roster feeder, which is the goroutine that provides data on // client.Roster. func startRosterFilter(client *Client) { - out := make(chan Stanza) + out := make(chan interface{}) in := client.AddFilter(out) - go func(in <-chan Stanza, out chan<- Stanza) { + go func(in <-chan interface{}, out chan<- interface{}) { defer close(out) for st := range in { maybeUpdateRoster(client, st) @@ -108,7 +108,7 @@ go feedRoster(rosterCh, rosterUpdate) } -func maybeUpdateRoster(client *Client, st Stanza) { +func maybeUpdateRoster(client *Client, st interface{}) { iq, ok := st.(*Iq) if !ok { return @@ -128,7 +128,8 @@ rosterUpdate <- item } // Send a reply. - reply := &Iq{To: iq.From, Id: iq.Id, Type: "result"} + reply := &Iq{Stanza: Stanza{To: iq.From, Id: iq.Id, + Type: "result"}} client.Out <- reply } } diff -r 3887d7ad19c1 -r 7696e6a01709 roster_test.go --- a/roster_test.go Sun Dec 16 19:55:17 2012 -0700 +++ b/roster_test.go Sun Dec 16 22:17:49 2012 -0700 @@ -13,8 +13,8 @@ // This is mostly just tests of the roster data structures. func TestRosterIqMarshal(t *testing.T) { - iq := &Iq{From: "from", Lang: "en", - Nested: []interface{}{RosterQuery{}}} + iq := &Iq{Stanza: Stanza{From: "from", Lang: "en", + Nested: []interface{}{RosterQuery{}}}} exp := `` assertMarshal(t, exp, iq) @@ -26,7 +26,7 @@ iq := Iq{} xml.Unmarshal([]byte(str), &iq) m := map[string]func(*xml.Name) interface{}{NsRoster: newRosterQuery} - err := parseExtended(&iq, m) + err := parseExtended(&iq.Stanza, m) if err != nil { t.Fatalf("parseExtended: %v", err) } diff -r 3887d7ad19c1 -r 7696e6a01709 stream.go --- a/stream.go Sun Dec 16 19:55:17 2012 -0700 +++ b/stream.go Sun Dec 16 22:17:49 2012 -0700 @@ -29,7 +29,7 @@ type stanzaHandler struct { id string // Return true means pass this to the application - f func(Stanza) bool + f func(interface{}) bool } // BUG(cjyar) Review all these *Client receiver methods. They should @@ -148,7 +148,7 @@ // If it's a Stanza, we try to unmarshal its innerxml // into objects of the appropriate respective // types. This is specified by our extensions. - if st, ok := obj.(Stanza); ok { + if st := getStanza(obj) ; st != nil { err = parseExtended(st, extStanza) if err != nil { Warn.Logf("ext unmarshal: %s", err) @@ -161,10 +161,10 @@ } } -func parseExtended(st Stanza, extStanza map[string]func(*xml.Name) interface{}) error { +func parseExtended(st *Stanza, extStanza map[string]func(*xml.Name) interface{}) error { // Now parse the stanza's innerxml to find the string that we // can unmarshal this nested element from. - reader := strings.NewReader(st.innerxml()) + reader := strings.NewReader(st.Innerxml) p := xml.NewDecoder(reader) for { t, err := p.Token() @@ -185,7 +185,7 @@ if err != nil { return err } - st.addNested(nested) + st.Nested = append(st.Nested, nested) } } } @@ -225,10 +225,10 @@ } } -func (cl *Client) readStream(srvIn <-chan interface{}, cliOut chan<- Stanza) { +func (cl *Client) readStream(srvIn <-chan interface{}, cliOut chan<- interface{}) { defer close(cliOut) - handlers := make(map[string]func(Stanza) bool) + handlers := make(map[string]func(interface{}) bool) Loop: for { select { @@ -238,7 +238,7 @@ if !ok { break Loop } - send := false + var st *Stanza switch obj := x.(type) { case *stream: handleStream(obj) @@ -250,24 +250,24 @@ cl.handleTls(obj) case *auth: cl.handleSasl(obj) + case *Iq, *Message, *Presence: + st = getStanza(obj) default: - send = true + Warn.Logf("Unhandled non-stanza: %T %#v", x, x) } - if !send { + + if st == nil { continue } - st, ok := x.(Stanza) - if !ok { - Warn.Logf("Unhandled non-stanza: %v", x) - continue - } - if handlers[st.GetId()] != nil { - f := handlers[st.GetId()] - delete(handlers, st.GetId()) - send = f(st) + + send := true + if handlers[st.Id] != nil { + f := handlers[st.Id] + delete(handlers, st.Id) + send = f(x) } if send { - cliOut <- st + cliOut <- x } } } @@ -277,11 +277,11 @@ // the app might inject something inappropriate into our negotiations // with the server. The control channel controls this loop's // activity. -func writeStream(srvOut chan<- interface{}, cliIn <-chan Stanza, +func writeStream(srvOut chan<- interface{}, cliIn <-chan interface{}, control <-chan int) { defer close(srvOut) - var input <-chan Stanza + var input <-chan interface{} Loop: for { select { @@ -309,8 +309,8 @@ // Stanzas from the remote go up through a stack of filters to the // app. This function manages the filters. -func filterTop(filterOut <-chan <-chan Stanza, filterIn chan<- <-chan Stanza, -topFilter <-chan Stanza, app chan<- Stanza) { +func filterTop(filterOut <-chan <-chan interface{}, filterIn chan<- <-chan interface{}, +topFilter <-chan interface{}, app chan<- interface{}) { defer close(app) Loop: for { @@ -333,7 +333,7 @@ } } -func filterBottom(from <-chan Stanza, to chan<- Stanza) { +func filterBottom(from <-chan interface{}, to chan<- interface{}) { defer close(to) for data := range from { to <- data @@ -596,8 +596,9 @@ if res != "" { bindReq.Resource = &res } - msg := &Iq{Type: "set", Id: <-Id, Nested: []interface{}{bindReq}} - f := func(st Stanza) bool { + msg := &Iq{Stanza: Stanza{Type: "set", Id: <-Id, + Nested: []interface{}{bindReq}}} + f := func(st interface{}) bool { iq, ok := st.(*Iq) if !ok { Warn.Log("non-iq response") @@ -614,7 +615,7 @@ } } if bindRepl == nil { - Warn.Logf("Bad bind reply: %v", iq) + Warn.Logf("Bad bind reply: %#v", iq) return false } jidStr := bindRepl.Jid @@ -642,7 +643,7 @@ // available on the normal Client.In channel. The stanza handler // must not read from that channel, as deliveries on it cannot proceed // until the handler returns true or false. -func (cl *Client) HandleStanza(id string, f func(Stanza) bool) { +func (cl *Client) HandleStanza(id string, f func(interface{}) bool) { h := &stanzaHandler{id: id, f: f} cl.handlers <- h } diff -r 3887d7ad19c1 -r 7696e6a01709 structs.go --- a/structs.go Sun Dec 16 19:55:17 2012 -0700 +++ b/structs.go Sun Dec 16 22:17:49 2012 -0700 @@ -9,7 +9,6 @@ import ( "bytes" "encoding/xml" - "errors" "flag" "fmt" // BUG(cjyar): We should use stringprep @@ -80,65 +79,7 @@ // One of the three core XMPP stanza types: iq, message, presence. See // RFC3920, section 9. -type Stanza interface { - // // Returns "iq", "message", or "presence". - // GetName() string - // // The to attribute. - // GetTo() string - // // The from attribute. - // GetFrom() string - // The id attribute. TODO maybe remove this. - GetId() string - // // The type attribute. - // GetType() string - // // The xml:lang attribute. - // GetLang() string - // // A nested error element, if any. - // GetError() *Error - // // Zero or more (non-error) nested elements. These will be in - // // namespaces managed by extensions. - // GetNested() []interface{} - addNested(interface{}) - innerxml() string -} - -// message stanza -type Message struct { - XMLName xml.Name `xml:"message"` - To string `xml:"to,attr,omitempty"` - From string `xml:"from,attr,omitempty"` - Id string `xml:"id,attr,omitempty"` - Type string `xml:"type,attr,omitempty"` - Lang string `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"` - Innerxml string `xml:",innerxml"` - Error *Error - Subject *Generic `xml:"subject"` - Body *Generic `xml:"body"` - Thread *Generic `xml:"thread"` - Nested []interface{} -} -var _ Stanza = &Message{} - -// presence stanza -type Presence struct { - XMLName xml.Name `xml:"presence"` - To string `xml:"to,attr,omitempty"` - From string `xml:"from,attr,omitempty"` - Id string `xml:"id,attr,omitempty"` - Type string `xml:"type,attr,omitempty"` - Lang string `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"` - Innerxml string `xml:",innerxml"` - Error *Error - Show *Generic `xml:"show"` - Status *Generic `xml:"status"` - Priority *Generic `xml:"priority"` - Nested []interface{} -} -var _ Stanza = &Presence{} - -// iq stanza -type Iq struct { - XMLName xml.Name `xml:"iq"` +type Stanza struct { To string `xml:"to,attr,omitempty"` From string `xml:"from,attr,omitempty"` Id string `xml:"id,attr,omitempty"` @@ -148,7 +89,30 @@ Error *Error Nested []interface{} } -var _ Stanza = &Iq{} + +// message stanza +type Message struct { + XMLName xml.Name `xml:"message"` + Stanza + Subject *Generic `xml:"subject"` + Body *Generic `xml:"body"` + Thread *Generic `xml:"thread"` +} + +// presence stanza +type Presence struct { + XMLName xml.Name `xml:"presence"` + Stanza + Show *Generic `xml:"show"` + Status *Generic `xml:"status"` + Priority *Generic `xml:"priority"` +} + +// iq stanza +type Iq struct { + XMLName xml.Name `xml:"iq"` + Stanza +} // Describes an XMPP stanza error. See RFC 3920, Section 9.3. type Error struct { @@ -280,77 +244,21 @@ return string(buf) } -func (m *Message) GetId() string { - return m.Id -} - -func (m *Message) addNested(n interface{}) { - m.Nested = append(m.Nested, n) -} - -func (m *Message) innerxml() string { - return m.Innerxml -} - -func (p *Presence) GetId() string { - return p.Id -} - -func (p *Presence) addNested(n interface{}) { - p.Nested = append(p.Nested, n) -} - -func (p *Presence) innerxml() string { - return p.Innerxml -} - -func (iq *Iq) GetId() string { - return iq.Id -} - -func (iq *Iq) addNested(n interface{}) { - iq.Nested = append(iq.Nested, n) -} - -func (iq *Iq) innerxml() string { - return iq.Innerxml -} - - -// Parse a string into a struct implementing Stanza -- this will be -// either an Iq, a Message, or a Presence. -func ParseStanza(str string) (Stanza, error) { - r := strings.NewReader(str) - p := xml.NewDecoder(r) - tok, err := p.Token() - if err != nil { - return nil, err - } - se, ok := tok.(xml.StartElement) - if !ok { - return nil, errors.New("Not a start element") - } - var stan Stanza - switch se.Name.Local { - case "iq": - stan = &Iq{} - case "message": - stan = &Message{} - case "presence": - stan = &Presence{} - default: - return nil, errors.New("Not iq, message, or presence") - } - err = p.DecodeElement(stan, &se) - if err != nil { - return nil, err - } - return stan, nil -} - var bindExt Extension = Extension{StanzaHandlers: map[string]func(*xml.Name) interface{}{NsBind: newBind}, Start: func(cl *Client) {}} func newBind(name *xml.Name) interface{} { return &bindIq{} } + +func getStanza(v interface{}) *Stanza { + switch s := v.(type) { + case *Iq: + return &s.Stanza + case *Message: + return &s.Stanza + case *Presence: + return &s.Stanza + } + return nil +} diff -r 3887d7ad19c1 -r 7696e6a01709 structs_test.go --- a/structs_test.go Sun Dec 16 19:55:17 2012 -0700 +++ b/structs_test.go Sun Dec 16 22:17:49 2012 -0700 @@ -89,71 +89,14 @@ } func TestIqMarshal(t *testing.T) { - iq := &Iq{Type: "set", Id: "3", Nested: []interface{}{Generic{XMLName: xml.Name{Space: NsBind, - Local: "bind"}}}} + iq := &Iq{Stanza: Stanza{Type: "set", Id: "3", + Nested: []interface{}{Generic{XMLName: xml.Name{Space: NsBind, + Local: "bind"}}}}} exp := `` assertMarshal(t, exp, iq) } -func TestParseStanza(t *testing.T) { - str := `text` - st, err := ParseStanza(str) - if err != nil { - t.Fatalf("iq: %v", err) - } - iq, ok := st.(*Iq) - if !ok { - t.Fatalf("not iq: %v", st) - } - assertEquals(t, "iq", iq.XMLName.Local) - assertEquals(t, "alice", iq.To) - assertEquals(t, "bob", iq.From) - assertEquals(t, "1", iq.Id) - assertEquals(t, "A", iq.Type) - assertEquals(t, "en", iq.Lang) - if iq.Error != nil { - t.Errorf("iq: error %v", iq.Error) - } - if st.innerxml() == "" { - t.Errorf("iq: empty child") - } - assertEquals(t, "text", st.innerxml()) - assertEquals(t, st.innerxml(), iq.Innerxml) - - str = `` - st, err = ParseStanza(str) - if err != nil { - t.Fatalf("message: %v", err) - } - m, ok := st.(*Message) - if !ok { - t.Fatalf("not message: %v", st) - } - assertEquals(t, "message", m.XMLName.Local) - assertEquals(t, "alice", m.To) - assertEquals(t, "bob", m.From) - assertEquals(t, "", m.Id) - assertEquals(t, "", m.Lang) - if m.Error != nil { - t.Errorf("message: error %v", m.Error) - } - if st.innerxml() != "" { - t.Errorf("message: child %v", st.innerxml()) - } - - str = `` - st, err = ParseStanza(str) - if err != nil { - t.Fatalf("presence: %v", err) - } - _, ok = st.(*Presence) - if !ok { - t.Fatalf("not presence: %v", st) - } -} - func TestMarshalEscaping(t *testing.T) { msg := &Message{Body: &Generic{XMLName: xml.Name{Local: "body"}, Chardata: `&