A disco/categories.go => disco/categories.go +325 -0
@@ 0,0 1,325 @@
+// Code generated by running "go generate" in mellium.im/xmpp/disco. DO NOT EDIT.
+
+package disco
+
+// Predefined identities generated from the Service Discovery Identities
+// Registry as registered with the XMPP Registrar.
+var (
+ // Category: The "account" category is to be used by a server when responding to a disco request sent to the bare JID (user@host addresss) of an account hosted by the server.
+ // Type: The user@host is an administrative account
+ AdminAccount func(name, lang string) Option = newIdentity("account", "admin")
+
+ // Category: The "account" category is to be used by a server when responding to a disco request sent to the bare JID (user@host addresss) of an account hosted by the server.
+ // Type: The user@host is a "guest" account that allows anonymous login by any user
+ AnonymousAccount func(name, lang string) Option = newIdentity("account", "anonymous")
+
+ // Category: The "account" category is to be used by a server when responding to a disco request sent to the bare JID (user@host addresss) of an account hosted by the server.
+ // Type: The user@host is a registered or provisioned account associated with a particular non-administrative user
+ RegisteredAccount func(name, lang string) Option = newIdentity("account", "registered")
+
+ // Category: The "auth" category consists of server components that provide authentication services within a server implementation.
+ // Type: A server component that authenticates based on external certificates
+ CertAuth func(name, lang string) Option = newIdentity("auth", "cert")
+
+ // Category: The "auth" category consists of server components that provide authentication services within a server implementation.
+ // Type: A server authentication component other than one of the registered types
+ GenericAuth func(name, lang string) Option = newIdentity("auth", "generic")
+
+ // Category: The "auth" category consists of server components that provide authentication services within a server implementation.
+ // Type: A server component that authenticates against an LDAP database
+ LDAPAuth func(name, lang string) Option = newIdentity("auth", "ldap")
+
+ // Category: The "auth" category consists of server components that provide authentication services within a server implementation.
+ // Type: A server component that authenticates against an NT domain
+ NTLMAuth func(name, lang string) Option = newIdentity("auth", "ntlm")
+
+ // Category: The "auth" category consists of server components that provide authentication services within a server implementation.
+ // Type: A server component that authenticates against a PAM system
+ PAMAuth func(name, lang string) Option = newIdentity("auth", "pam")
+
+ // Category: The "auth" category consists of server components that provide authentication services within a server implementation.
+ // Type: A server component that authenticates against a Radius system
+ RadiusAuth func(name, lang string) Option = newIdentity("auth", "radius")
+
+ // Category: The "automation" category consists of entities and nodes that provide automated or programmed interaction.
+ // Type: The node for a list of commands; valid only for the node "http://jabber.org/protocol/commands"
+ CommandListAutomation func(name, lang string) Option = newIdentity("automation", "command-list")
+
+ // Category: The "automation" category consists of entities and nodes that provide automated or programmed interaction.
+ // Type: A node for a specific command; the "node" attribute uniquely identifies the command
+ CommandNodeAutomation func(name, lang string) Option = newIdentity("automation", "command-node")
+
+ // Category: The "automation" category consists of entities and nodes that provide automated or programmed interaction.
+ // Type: An entity that supports Jabber-RPC.
+ RpcAutomation func(name, lang string) Option = newIdentity("automation", "rpc")
+
+ // Category: The "automation" category consists of entities and nodes that provide automated or programmed interaction.
+ // Type: An entity that supports the SOAP XMPP Binding.
+ SOAPAutomation func(name, lang string) Option = newIdentity("automation", "soap")
+
+ // Category: The "automation" category consists of entities and nodes that provide automated or programmed interaction.
+ // Type: An entity that provides automated translation services.
+ TranslationAutomation func(name, lang string) Option = newIdentity("automation", "translation")
+
+ // Category: The "client" category consists of different types of clients, mostly for instant messaging.
+ // Type: An automated client that is not controlled by a human user
+ BotClient func(name, lang string) Option = newIdentity("client", "bot")
+
+ // Category: The "client" category consists of different types of clients, mostly for instant messaging.
+ // Type: Minimal non-GUI client used on dumb terminals or text-only screens
+ ConsoleClient func(name, lang string) Option = newIdentity("client", "console")
+
+ // Category: The "client" category consists of different types of clients, mostly for instant messaging.
+ // Type: A client running on a gaming console
+ GameClient func(name, lang string) Option = newIdentity("client", "game")
+
+ // Category: The "client" category consists of different types of clients, mostly for instant messaging.
+ // Type: A client running on a PDA, RIM device, or other handheld
+ HandheldClient func(name, lang string) Option = newIdentity("client", "handheld")
+
+ // Category: The "client" category consists of different types of clients, mostly for instant messaging.
+ // Type: Standard full-GUI client used on desktops and laptops
+ PCClient func(name, lang string) Option = newIdentity("client", "pc")
+
+ // Category: The "client" category consists of different types of clients, mostly for instant messaging.
+ // Type: A client running on a mobile phone or other telephony device
+ PhoneClient func(name, lang string) Option = newIdentity("client", "phone")
+
+ // Category: The "client" category consists of different types of clients, mostly for instant messaging.
+ // Type: A client that is not actually using an instant messaging client; however, messages sent to this contact will be delivered as Short Message Service (SMS) messages
+ SMSClient func(name, lang string) Option = newIdentity("client", "sms")
+
+ // Category: The "client" category consists of different types of clients, mostly for instant messaging.
+ // Type: A client operated from within a web browser
+ WebClient func(name, lang string) Option = newIdentity("client", "web")
+
+ // Category: The "collaboration" category consists of services that enable multiple individuals to work together in real time.
+ // Type: Multi-user whiteboarding service
+ WhiteboardCollaboration func(name, lang string) Option = newIdentity("collaboration", "whiteboard")
+
+ // Category: The "component" category consists of services that are internal to server implementations and not normally exposed outside a server.
+ // Type: A server component that archives traffic
+ ArchiveComponent func(name, lang string) Option = newIdentity("component", "archive")
+
+ // Category: The "component" category consists of services that are internal to server implementations and not normally exposed outside a server.
+ // Type: A server component that handles client connections
+ C2SComponent func(name, lang string) Option = newIdentity("component", "c2s")
+
+ // Category: The "component" category consists of services that are internal to server implementations and not normally exposed outside a server.
+ // Type: A server component other than one of the registered types
+ GenericComponent func(name, lang string) Option = newIdentity("component", "generic")
+
+ // Category: The "component" category consists of services that are internal to server implementations and not normally exposed outside a server.
+ // Type: A server component that handles load balancing
+ LoadComponent func(name, lang string) Option = newIdentity("component", "load")
+
+ // Category: The "component" category consists of services that are internal to server implementations and not normally exposed outside a server.
+ // Type: A server component that logs server information
+ LogComponent func(name, lang string) Option = newIdentity("component", "log")
+
+ // Category: The "component" category consists of services that are internal to server implementations and not normally exposed outside a server.
+ // Type: A server component that provides presence information
+ PresenceComponent func(name, lang string) Option = newIdentity("component", "presence")
+
+ // Category: The "component" category consists of services that are internal to server implementations and not normally exposed outside a server.
+ // Type: A server component that handles core routing logic
+ RouterComponent func(name, lang string) Option = newIdentity("component", "router")
+
+ // Category: The "component" category consists of services that are internal to server implementations and not normally exposed outside a server.
+ // Type: A server component that handles server connections
+ S2SComponent func(name, lang string) Option = newIdentity("component", "s2s")
+
+ // Category: The "component" category consists of services that are internal to server implementations and not normally exposed outside a server.
+ // Type: A server component that manages user sessions
+ SMComponent func(name, lang string) Option = newIdentity("component", "sm")
+
+ // Category: The "component" category consists of services that are internal to server implementations and not normally exposed outside a server.
+ // Type: A server component that provides server statistics
+ StatsComponent func(name, lang string) Option = newIdentity("component", "stats")
+
+ // Category: The "conference" category consists of online conference services such as multi-user chatroom services.
+ // Type: Internet Relay Chat service
+ IRCConference func(name, lang string) Option = newIdentity("conference", "irc")
+
+ // Category: The "conference" category consists of online conference services such as multi-user chatroom services.
+ // Type: Text conferencing service
+ TextConference func(name, lang string) Option = newIdentity("conference", "text")
+
+ // Category: The "directory" category consists of information retrieval services that enable users to search online directories or otherwise be informed about the existence of other XMPP entities.
+ // Type: A directory of chatrooms
+ ChatroomDirectory func(name, lang string) Option = newIdentity("directory", "chatroom")
+
+ // Category: The "directory" category consists of information retrieval services that enable users to search online directories or otherwise be informed about the existence of other XMPP entities.
+ // Type: A directory that provides shared roster groups
+ GroupDirectory func(name, lang string) Option = newIdentity("directory", "group")
+
+ // Category: The "directory" category consists of information retrieval services that enable users to search online directories or otherwise be informed about the existence of other XMPP entities.
+ // Type: A directory of end users (e.g., JUD)
+ UserDirectory func(name, lang string) Option = newIdentity("directory", "user")
+
+ // Category: The "directory" category consists of information retrieval services that enable users to search online directories or otherwise be informed about the existence of other XMPP entities.
+ // Type: A directory of waiting list entries
+ WaitinglistDirectory func(name, lang string) Option = newIdentity("directory", "waitinglist")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to AOL Instant Messenger
+ AIMGateway func(name, lang string) Option = newIdentity("gateway", "aim")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to the Facebook IM service
+ FacebookGateway func(name, lang string) Option = newIdentity("gateway", "facebook")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to the Gadu-Gadu IM service
+ GaduGaduGateway func(name, lang string) Option = newIdentity("gateway", "gadu-gadu")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway that provides HTTP Web Services access
+ HTTPWSGateway func(name, lang string) Option = newIdentity("gateway", "http-ws")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to ICQ
+ ICQGateway func(name, lang string) Option = newIdentity("gateway", "icq")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to IRC
+ IRCGateway func(name, lang string) Option = newIdentity("gateway", "irc")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to Microsoft Live Communications Server
+ LCSGateway func(name, lang string) Option = newIdentity("gateway", "lcs")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to the mail.ru IM service
+ MRIMGateway func(name, lang string) Option = newIdentity("gateway", "mrim")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to MSN Messenger
+ MSNGateway func(name, lang string) Option = newIdentity("gateway", "msn")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to the MySpace IM service
+ MyspaceimGateway func(name, lang string) Option = newIdentity("gateway", "myspaceim")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to Microsoft Office Communications Server
+ OCSGateway func(name, lang string) Option = newIdentity("gateway", "ocs")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to the QQ IM service
+ QQGateway func(name, lang string) Option = newIdentity("gateway", "qq")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to IBM Lotus Sametime
+ SametimeGateway func(name, lang string) Option = newIdentity("gateway", "sametime")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to SIP for Instant Messaging and Presence Leveraging Extensions (SIMPLE)
+ SimpleGateway func(name, lang string) Option = newIdentity("gateway", "simple")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to the Skype service
+ SkypeGateway func(name, lang string) Option = newIdentity("gateway", "skype")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to Short Message Service
+ SMSGateway func(name, lang string) Option = newIdentity("gateway", "sms")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to the SMTP (email) network
+ SMTPGateway func(name, lang string) Option = newIdentity("gateway", "smtp")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to the Tlen IM service
+ TlenGateway func(name, lang string) Option = newIdentity("gateway", "tlen")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to the Xfire gaming and IM service
+ XfireGateway func(name, lang string) Option = newIdentity("gateway", "xfire")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to another XMPP service (NOT via native server-to-server communication)
+ XMPPGateway func(name, lang string) Option = newIdentity("gateway", "xmpp")
+
+ // Category: The "gateway" category consists of translators between Jabber/XMPP services and non-XMPP services.
+ // Type: Gateway to Yahoo! Instant Messenger
+ YahooGateway func(name, lang string) Option = newIdentity("gateway", "yahoo")
+
+ // Category: The "headline" category consists of services that provide real-time news or information (often but not necessarily in a message of type "headline").
+ // Type: Service that notifies a user of new email messages.
+ NewmailHeadline func(name, lang string) Option = newIdentity("headline", "newmail")
+
+ // Category: The "headline" category consists of services that provide real-time news or information (often but not necessarily in a message of type "headline").
+ // Type: RSS notification service.
+ RSSHeadline func(name, lang string) Option = newIdentity("headline", "rss")
+
+ // Category: The "headline" category consists of services that provide real-time news or information (often but not necessarily in a message of type "headline").
+ // Type: Service that provides weather alerts.
+ WeatherHeadline func(name, lang string) Option = newIdentity("headline", "weather")
+
+ // Category: The "hierarchy" category is used to describe nodes within a hierarchy of nodes; the "branch" and "leaf" types are exhaustive.
+ // Type: A service discovery node that contains further nodes in the hierarchy.
+ BranchHierarchy func(name, lang string) Option = newIdentity("hierarchy", "branch")
+
+ // Category: The "hierarchy" category is used to describe nodes within a hierarchy of nodes; the "branch" and "leaf" types are exhaustive.
+ // Type: A service discovery node that does not contain further nodes in the hierarchy.
+ LeafHierarchy func(name, lang string) Option = newIdentity("hierarchy", "leaf")
+
+ // Category: The "proxy" category consists of servers or services that act as special-purpose proxies or intermediaries between two or more XMPP endpoints.
+ // Type: SOCKS5 bytestreams proxy service
+ BytestreamsProxy func(name, lang string) Option = newIdentity("proxy", "bytestreams")
+
+ // Category: Services and nodes that adhere to XEP-0060.
+ // Type: A pubsub node of the "collection" type.
+ CollectionPubsub func(name, lang string) Option = newIdentity("pubsub", "collection")
+
+ // Category: Services and nodes that adhere to XEP-0060.
+ // Type: A pubsub node of the "leaf" type.
+ LeafPubsub func(name, lang string) Option = newIdentity("pubsub", "leaf")
+
+ // Category: Services and nodes that adhere to XEP-0060.
+ // Type: A personal eventing service that supports the publish-subscribe subset defined in XEP-0163.
+ PEPPubsub func(name, lang string) Option = newIdentity("pubsub", "pep")
+
+ // Category: Services and nodes that adhere to XEP-0060.
+ // Type: A pubsub service that supports the functionality defined in XEP-0060.
+ ServicePubsub func(name, lang string) Option = newIdentity("pubsub", "service")
+
+ // Category: The "server" category consists of any Jabber/XMPP server.
+ // Type: Standard Jabber/XMPP server used for instant messaging and presence
+ IMServer func(name, lang string) Option = newIdentity("server", "im")
+
+ // Category: The "store" category consists of internal server components that provide data storage and retrieval services.
+ // Type: A server component that stores data in a Berkeley database
+ BerkeleyStore func(name, lang string) Option = newIdentity("store", "berkeley")
+
+ // Category: The "store" category consists of internal server components that provide data storage and retrieval services.
+ // Type: A server component that stores data on the file system
+ FileStore func(name, lang string) Option = newIdentity("store", "file")
+
+ // Category: The "store" category consists of internal server components that provide data storage and retrieval services.
+ // Type: A server data storage component other than one of the registered types
+ GenericStore func(name, lang string) Option = newIdentity("store", "generic")
+
+ // Category: The "store" category consists of internal server components that provide data storage and retrieval services.
+ // Type: A server component that stores data in an LDAP database
+ LDAPStore func(name, lang string) Option = newIdentity("store", "ldap")
+
+ // Category: The "store" category consists of internal server components that provide data storage and retrieval services.
+ // Type: A server component that stores data in a MySQL database
+ MysqlStore func(name, lang string) Option = newIdentity("store", "mysql")
+
+ // Category: The "store" category consists of internal server components that provide data storage and retrieval services.
+ // Type: A server component that stores data in an Oracle database
+ OracleStore func(name, lang string) Option = newIdentity("store", "oracle")
+
+ // Category: The "store" category consists of internal server components that provide data storage and retrieval services.
+ // Type: A server component that stores data in a PostgreSQL database
+ PostgresStore func(name, lang string) Option = newIdentity("store", "postgres")
+)
+
+func newIdentity(cat, typ string) func(string, string) Option {
+ return func(name, lang string) Option {
+ return Identity(cat, typ, name, lang)
+ }
+}
A disco/disco.go => disco/disco.go +107 -0
@@ 0,0 1,107 @@
+// Copyright 2017 The Mellium Contributors.
+// Use of this source code is governed by the BSD 2-clause
+// license that can be found in the LICENSE file.
+
+// Package disco implements XEP-0030: Service Discovery.
+package disco
+
+//go:generate go run gen.go
+
+import (
+ "encoding/xml"
+ "errors"
+
+ "mellium.im/xmlstream"
+ "mellium.im/xmpp/internal/ns"
+)
+
+// Namespaces used by this package.
+const (
+ NSInfo = `http://jabber.org/protocol/disco#info`
+ NSItems = `http://jabber.org/protocol/disco#items`
+)
+
+type identity struct {
+ Category string
+ Type string
+ XMLLang string
+}
+
+// A Registry is used to register features supported by a server.
+type Registry struct {
+ identities map[identity]string
+ features map[string]struct{}
+}
+
+// NewRegistry creates a new feature registry with the provided identities and
+// features.
+// If multiple identities are specified, the name of the registry will be used
+// for all of them.
+func NewRegistry(options ...Option) *Registry {
+ registry := &Registry{
+ features: map[string]struct{}{
+ NSInfo: struct{}{},
+ NSItems: struct{}{},
+ },
+ }
+ for _, o := range options {
+ o(registry)
+ }
+ return registry
+}
+
+// HandleXMPP handles disco info requests.
+func (r *Registry) HandleXMPP(t xmlstream.TokenReadWriter, start *xml.StartElement) error {
+ // TODO: Handle the IQ and IQ semantics, not the payload.
+ switch {
+ case r == nil:
+ return NewRegistry().HandleXMPP(t, start)
+ case start == nil || start.Name.Local != "query" || start.Name.Space != NSInfo:
+ return errors.New("disco: bad info query payload")
+ }
+
+ if err := xmlstream.Skip(t); err != nil {
+ return err
+ }
+
+ resp := xml.StartElement{
+ Name: xml.Name{Space: NSInfo, Local: "query"},
+ }
+ if err := t.EncodeToken(resp); err != nil {
+ return err
+ }
+
+ for feature := range r.features {
+ start := xml.StartElement{
+ Name: xml.Name{Local: "feature"},
+ Attr: []xml.Attr{
+ {Name: xml.Name{Local: "var"}, Value: feature},
+ },
+ }
+ if err := t.EncodeToken(start); err != nil {
+ return err
+ }
+ if err := t.EncodeToken(start.End()); err != nil {
+ return err
+ }
+ }
+ for ident, name := range r.identities {
+ start := xml.StartElement{
+ Name: xml.Name{Local: "identity"},
+ Attr: []xml.Attr{
+ {Name: xml.Name{Local: "category"}, Value: ident.Category},
+ {Name: xml.Name{Local: "type"}, Value: ident.Type},
+ {Name: xml.Name{Local: "name"}, Value: name},
+ {Name: xml.Name{Space: ns.XML, Local: "lang"}, Value: ident.XMLLang},
+ },
+ }
+ if err := t.EncodeToken(start); err != nil {
+ return err
+ }
+ if err := t.EncodeToken(start.End()); err != nil {
+ return err
+ }
+ }
+
+ return t.EncodeToken(resp.End())
+}
A disco/disco_test.go => disco/disco_test.go +136 -0
@@ 0,0 1,136 @@
+// Copyright 2017 The Mellium Contributors.
+// Use of this source code is governed by the BSD 2-clause
+// license that can be found in the LICENSE file.
+
+package disco_test
+
+import (
+ "bytes"
+ "encoding/xml"
+ "reflect"
+ "sort"
+ "strconv"
+ "strings"
+ "testing"
+
+ "mellium.im/xmlstream"
+ "mellium.im/xmpp/disco"
+)
+
+type Feature struct {
+ Var string `xml:"var,attr"`
+}
+
+type Ident struct {
+ Cat string `xml:"category,attr"`
+ Type string `xml:"type,attr"`
+ Name string `xml:"name,attr"`
+}
+
+type Query struct {
+ XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"`
+ Feature []Feature `xml:"feature"`
+ Identity []Ident `xml:"identity"`
+}
+
+var registryTests = [...]struct {
+ r *disco.Registry
+ q Query
+ err error
+}{
+ 0: {
+ q: Query{
+ Feature: []Feature{
+ {Var: disco.NSInfo},
+ {Var: disco.NSItems},
+ },
+ },
+ },
+ 1: {
+ r: disco.NewRegistry(),
+ q: Query{
+ Feature: []Feature{
+ {Var: disco.NSInfo},
+ {Var: disco.NSItems},
+ },
+ },
+ },
+ 2: {
+ r: disco.NewRegistry(disco.Feature(disco.NSInfo), disco.Feature("porticulus")),
+ q: Query{
+ Feature: []Feature{
+ {Var: disco.NSInfo},
+ {Var: disco.NSItems},
+ {Var: "porticulus"},
+ },
+ },
+ },
+ 3: {
+ r: disco.NewRegistry(disco.Feature(disco.NSItems), disco.AdminAccount("my service", "en"), disco.Feature("porticulus")),
+ q: Query{
+ Identity: []Ident{
+ {"account", "admin", "my service"},
+ },
+ Feature: []Feature{
+ {Var: disco.NSInfo},
+ {Var: disco.NSItems},
+ {Var: "porticulus"},
+ },
+ },
+ },
+}
+
+func TestDisco(t *testing.T) {
+ for i, tc := range registryTests {
+ t.Run(strconv.Itoa(i), func(t *testing.T) {
+ d := xml.NewDecoder(strings.NewReader(`<query xmlns='http://jabber.org/protocol/disco#info'/>`))
+ tok, _ := d.Token()
+ start := tok.(xml.StartElement)
+ buf := new(bytes.Buffer)
+ e := xml.NewEncoder(buf)
+
+ err := tc.r.HandleXMPP(struct {
+ xml.TokenReader
+ xmlstream.TokenWriter
+ }{
+ TokenReader: d,
+ TokenWriter: e,
+ }, &start)
+ if err != tc.err {
+ t.Fatalf("Unexpected error: want=`%v', got=`%v'", tc.err, err)
+ }
+ if err := e.Flush(); err != nil {
+ t.Fatalf("Unexpected error while flushing: `%v'", err)
+ }
+
+ q := Query{}
+ err = xml.Unmarshal(buf.Bytes(), &q)
+ if err != nil {
+ t.Fatalf("Unexpected error: `%v'", err)
+ }
+
+ // Clear the name so we don't have to set it in the test cases.
+ q.XMLName = xml.Name{}
+
+ sort.Slice(q.Feature, func(i, j int) bool {
+ return q.Feature[i].Var < q.Feature[j].Var
+ })
+ sort.Slice(tc.q.Feature, func(i, j int) bool {
+ return tc.q.Feature[i].Var < tc.q.Feature[j].Var
+ })
+ if !reflect.DeepEqual(q.Feature, tc.q.Feature) {
+ t.Errorf("Features list did not match: want=`%+v', got=`%+v'", tc.q, q)
+ }
+
+ sort.Slice(q.Identity, func(i, j int) bool {
+ return q.Identity[i].Cat+q.Identity[i].Type+q.Identity[i].Name < q.Identity[j].Cat+q.Identity[j].Type+q.Identity[j].Name
+ })
+ sort.Slice(tc.q.Identity, func(i, j int) bool {
+ return tc.q.Identity[i].Cat+tc.q.Identity[i].Type+tc.q.Identity[i].Name < tc.q.Identity[j].Cat+tc.q.Identity[j].Type+tc.q.Identity[j].Name
+ })
+ if !reflect.DeepEqual(q.Identity, tc.q.Identity) {
+ t.Errorf("Identity list did not match: want=`%+v', got=`%+v'", tc.q, q)
+ }
+ })
+ }
+}
A disco/gen.go => disco/gen.go +169 -0
@@ 0,0 1,169 @@
+// Copyright 2017 The Mellium Contributors.
+// Use of this source code is governed by the BSD 2-clause
+// license that can be found in the LICENSE file.
+
+// +build ignore
+
+// The gen command creates disco identity options from the disco-categories
+// registry located at https://xmpp.org/registrar/disco-categories.html.
+package main
+
+import (
+ "bytes"
+ "encoding/xml"
+ "flag"
+ "go/format"
+ "io"
+ "log"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+ "text/template"
+)
+
+type registry struct {
+ XMLName xml.Name `xml:"registry"`
+ Categories []struct {
+ Name string `xml:"name"`
+ Desc string `xml:"desc"`
+ Type []struct {
+ Name string `xml:"name"`
+ XMLLang string `xml:"http://www.w3.org/XML/1998/namespace lang"`
+ Desc string `xml:"desc"`
+ Doc string `xml:"doc"`
+ } `xml:"type"`
+ } `xml:"category"`
+}
+
+func main() {
+ var (
+ catURL = `https://xmpp.org/registrar/disco-categories.xml`
+ tmpDir = os.TempDir()
+ outPath = `./`
+ )
+ flag.StringVar(&catURL, "categories", catURL, "A link to the disco-categories registry")
+ flag.StringVar(&tmpDir, "tmp", tmpDir, "A temporary directory to downlaod files to")
+ flag.StringVar(&outPath, "out", outPath, "A directory to output Go files to")
+ flag.Parse()
+
+ ident := func(s string) string {
+ return strings.Map(func(r rune) rune {
+ switch r {
+ case ' ', '-', '_':
+ return rune(-1)
+ }
+ return r
+ }, s)
+ }
+
+ tmpl := template.Must(template.New("categories").Funcs(map[string]interface{}{
+ "ident": ident,
+ "export": func(s string) string {
+ s = strings.Title(s)
+ // Manual tweaks to make the output more idiomatic Go.
+ for _, cap := range []string{
+ "Rss", "Xmpp", "Smtp", "Sms", "Qq", "Msn", "Lcs", "Irc", "Icq", "Aim",
+ "S2s", "C2s", "Sm", "Pc", "Soap", "Pam", "Ntlm", "Ldap", "Http-Ws",
+ "Mrim", "Ocs", "Pep", "Im",
+ } {
+ if strings.HasPrefix(s, cap) {
+ s = strings.Replace(s, cap, strings.ToUpper(cap), 1)
+ break
+ }
+ }
+ return ident(s)
+ },
+ }).Parse(`// Code generated by running "go generate" in mellium.im/xmpp/disco. DO NOT EDIT.
+
+package disco
+
+// Predefined identities generated from the Service Discovery Identities
+// Registry as registered with the XMPP Registrar.
+var (
+{{- range $cat := .Categories}}{{range .Type}}
+ // Category: {{$cat.Desc}}
+ // Type: {{.Desc}}
+ {{export .Name}}{{export $cat.Name}} func(name, lang string) Option = newIdentity({{printf "%q" $cat.Name}}, {{printf "%q" .Name}})
+{{end}}{{end}}
+)
+
+func newIdentity(cat, typ string) func(string, string) Option {
+ return func(name, lang string) Option {
+ return Identity(cat, typ, name, lang)
+ }
+}
+`))
+
+ logger := log.New(os.Stderr, "", log.LstdFlags)
+ if err := genFile(catURL, tmpDir, filepath.Join(outPath, "categories.go"), tmpl); err != nil {
+ logger.Fatal(err)
+ }
+}
+
+func genFile(regURL, tmpDir, outFile string, tmpl *template.Template) error {
+ fd, err := openOrDownload(regURL, tmpDir)
+ if err != nil {
+ return err
+ }
+ defer fd.Close()
+
+ out, err := os.Create(outFile)
+ if err != nil {
+ return err
+ }
+ defer fd.Close()
+
+ reg := registry{}
+ d := xml.NewDecoder(fd)
+ if err = d.Decode(®); err != nil {
+ return err
+ }
+
+ buf := new(bytes.Buffer)
+ if err = tmpl.Execute(buf, reg); err != nil {
+ return err
+ }
+ b, err := format.Source(buf.Bytes())
+ if err != nil {
+ return err
+ }
+ _, err = io.Copy(out, bytes.NewReader(b))
+ return err
+}
+
+type writeCloser struct {
+ io.Writer
+ closer func() error
+}
+
+func (f writeCloser) Close() error {
+ return f.closer()
+}
+
+// opens the provided registry URL (downloading it if it doesn't exist).
+func openOrDownload(catURL, tmpDir string) (*os.File, error) {
+ registryXML := filepath.Join(tmpDir, filepath.Base(catURL))
+ fd, err := os.Open(registryXML)
+ if err != nil {
+ fd, err = os.Create(registryXML)
+ if err != nil {
+ return nil, err
+ }
+ // If we couldn't open it for reading, attempt to download it.
+ resp, err := http.Get(catURL)
+ if err != nil {
+ return nil, err
+ }
+ _, err = io.Copy(fd, resp.Body)
+ if err != nil {
+ return nil, err
+ }
+ resp.Body.Close()
+ _, err = fd.Seek(0, 0)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return fd, err
+}
A disco/option.go => disco/option.go +46 -0
@@ 0,0 1,46 @@
+// Copyright 2017 The Mellium Contributors.
+// Use of this source code is governed by the BSD 2-clause
+// license that can be found in the LICENSE file.
+
+package disco
+
+// An Option is used to configure new registries.
+type Option func(*Registry)
+
+// Identity adds an identity to the registry.
+//
+// Identities are described by XEP-0030:
+//
+// An entity's identity is broken down into its category (server, client,
+// gateway, directory, etc.) and its particular type within that category
+// (IM server, phone vs. handheld client, MSN gateway vs. AIM gateway, user
+// directory vs. chatroom directory, etc.). This information helps
+// requesting entities to determine the group or "bucket" of services into
+// which the entity is most appropriately placed (e.g., perhaps the entity
+// is shown in a GUI with an appropriate icon).
+func Identity(category, typ, name, lang string) Option {
+ return func(r *Registry) {
+ if r.identities == nil {
+ r.identities = make(map[identity]string)
+ }
+ r.identities[identity{
+ Category: category,
+ Type: typ,
+ XMLLang: lang,
+ }] = name
+ }
+}
+
+// Feature adds a feature to the registry.
+//
+// Features are described by XEP-0030:
+//
+// This information helps requesting entities determine what actions are
+// possible with regard to this entity (registration, search, join, etc.),
+// what protocols the entity supports, and specific feature types of
+// interest, if any (e.g., for the purpose of feature negotiation).
+func Feature(name string) Option {
+ return func(r *Registry) {
+ r.features[name] = struct{}{}
+ }
+}