XMPP Service Discovery (XEP-0030)
1 Example
1.1 Nested hierarchy
1.2 JSON data
2 Reference
2.1 disco
xmpp-provide-disco
add-xmpp-disco!
xmpp-disco->jsexpr
xmpp-disco-provider
2.2 disco#info
xmpp-provide-disco-info
add-xmpp-disco-info-entity!
add-xmpp-disco-info-identity!
add-xmpp-disco-info-feature!
make-xmpp-disco-info
xmpp-disco-info-provider
xmpp-disco-info
xmpp-disco-info-identity
xmpp-disco-info-feature
2.3 disco#items
xmpp-provide-disco-items
add-xmpp-disco-items-entity!
add-xmpp-disco-item!
make-xmpp-disco-items
xmpp-disco-items-provider
xmpp-disco-items
xmpp-disco-item
8.12

XMPP Service Discovery (XEP-0030)🔗ℹ

Navlost <racket at navlost dot eu>

 (require xmpp/xep-0030) package: XMPP

This module implements XMPP Service Discovery (XMPP-0030), an XMPP protocol extension for discovering information about other XMPP entities.

1 Example🔗ℹ

Example:

> ; We will define a component with two entities, of the form:
  ; 
  ; my-component.example.net
  ; ├ node0@my-component.example.net
  ; └ node1@my-component.example.net
  ; 
  ; With the following configuration:
  ; 
  ; my-component.example.net:
  ; Name:     My component
  ; Category: component
  ; Type:     generic
  ; 
  ; node0@my-component.example.net:
  ; Name:     First node
  ; Category: account
  ; Type:     registered
  ; 
  ; node1@my-component.example.net:
  ; Name:     Second node
  ; Category: account
  ; Type:     registered
  ; 
  
  (require xmpp)
  ; 
  ; Let us connect to the server as a component.
  ; 
  (define-values (host port domain secret)
  (values "example.net" 5255 "my-component.example.net" "COMPONENT!SECRET"))
  (define conn (xmpp-connect-component host port domain secret))
  (xmpp-set-handler conn)
  
  ; 
  ; First we set up disco#info
  ; 
  
  ; We start the disco#info service handler on this connection
  (define disco-info (xmpp-provide-disco-info conn))
  
  ; Let us create a disco#info object for the component itself
  (define disco-info-root (make-xmpp-disco-info (xmpp-connection-jid conn) ""))
  
  ; Let us add an <identity/> value
  (add-xmpp-disco-info-identity!
  (xmpp-disco-info-identity "My component" "component" "generic")
  disco-info-root)
  
  ; And advertise the disco#info feature
  (add-xmpp-disco-info-feature!
   (xmpp-disco-info-feature "http://jabber.org/protocol/disco#info" "")
   disco-info-root)
  
  ; We add the disco-info object to the disco#info handler.
  ; After this, the component will start replying to any
  ; incoming disco#info queries.
  ; 
  ; NOTE: we could have also added it first and then configured it.
  
  (add-xmpp-disco-info-entity! disco-info-root disco-info)
  
  ; 
  ; Now we set up disco#items
  ; 
  
  ; We start the disco#items service handler
  (define disco-items (xmpp-provide-disco-items conn))
  
  ; We create a disco#items object holding the component's child items
  (define disco-items-root (xmpp-disco-items (xmpp-connection-jid conn) "" '()))
  
  ; We add it to the handler (we can do this before or after configuring
  ; the object's items).
  (add-xmpp-disco-items-entity! disco-items-root disco-items)
  
  ; We tell the root's disco#info to also advertise disco#items capability
  (add-xmpp-disco-info-feature!
   (xmpp-disco-info-feature "http://jabber.org/protocol/disco#items" "")
   disco-info-root)
  
  ; We define the first node
  (define di0 (xmpp-disco-item (string->jid "node0@my-component.example.net") "" ""))
  
  ; We define the second node
  (define di1 (xmpp-disco-item (string->jid "node1@my-component.example.net") "" ""))
  
  ; We add them to the disco#items object
  (add-xmpp-disco-item! di0 disco-items-root)
  (add-xmpp-disco-item! di1 disco-items-root)
  
  ; 
  ; And now we add the disco#info for each of the two nodes
  ; 
  
  ; We can also chain the different calls:
  
  (add-xmpp-disco-info-entity!
   (add-xmpp-disco-info-feature!
    (xmpp-disco-info-feature "http://jabber.org/protocol/disco#info" "")
    (add-xmpp-disco-info-identity!
     (xmpp-disco-info-identity "First node" "registered" "account")
     (xmpp-disco-info (xmpp-disco-item-jid di0) (xmpp-disco-item-node di0) '() '() '())))
  
   (add-xmpp-disco-info-entity!
    (add-xmpp-disco-info-feature!
     (xmpp-disco-info-feature "http://jabber.org/protocol/disco#info" "")
     (add-xmpp-disco-info-identity!
      (xmpp-disco-info-identity "Second node" "registered" "account")
      (xmpp-disco-info (xmpp-disco-item-jid di1) (xmpp-disco-item-node di1) '() '() '())))
  
    disco-info))

When the above component is queried, the conversation will be something like this:

<iq type=’get’ id=’a1197a’ to=’my-component.example.net’> <query xmlns=’http://jabber.org/protocol/disco#info’/> </iq>

<iq type=’get’ id=’a1198a’ to=’my-component.example.net’> <query xmlns=’http://jabber.org/protocol/disco#items’/> </iq>

<iq type=’result’ id=’a1197a’ from=’my-component.example.net’ to=’user@example.com/laptop-129’> <query xmlns=’http://jabber.org/protocol/disco#info’> <identity name=’My component’ type=’generic’ category=’component’/> <feature var=’http://jabber.org/protocol/disco#items’/> <feature var=’http://jabber.org/protocol/disco#info’/> </query> </iq>

<iq type=’result’ id=’a1198a’ from=’my-component.example.net’ to=’user@example.com/laptop-129’> <query xmlns=’http://jabber.org/protocol/disco#items’> <item jid=’node1@my-component.example.net’/> <item jid=’node0@my-component.example.net’/> </query> </iq>

<iq type=’get’ id=’a1199a’ to=’node1@my-component.example.net’> <query xmlns=’http://jabber.org/protocol/disco#info’/> </iq>

<iq type=’get’ id=’a119aa’ to=’node0@my-component.example.net’> <query xmlns=’http://jabber.org/protocol/disco#info’/> </iq>

<iq type=’result’ id=’a1199a’ from=’node1@my-component.example.net’ to=’user@example.com/laptop-129’> <query xmlns=’http://jabber.org/protocol/disco#info’> <identity name=’Second node’ type=’account’ category=’registered’/> <feature var=’http://jabber.org/protocol/disco#info’/> </query> </iq>

<iq type=’result’ id=’a119aa’ from=’node0@my-component.example.net’ to=’user@example.com/laptop-129’> <query xmlns=’http://jabber.org/protocol/disco#info’> <identity name=’First node’ type=’account’ category=’registered’/> <feature var=’http://jabber.org/protocol/disco#info’/> </query> </iq>

1.1 Nested hierarchy🔗ℹ

Continuing from the previous example, we illustrate how to add items deeper in the hierarchy.

In this case, we will add two nodes under node0@my-component.example.net, named “config” and “user”:

> (add-xmpp-disco-items-entity!
  
   ; This is one item
   (add-xmpp-disco-item!
    (xmpp-disco-item (xmpp-disco-item-jid di0) "user" "User management")
  
    ; This is another
    (add-xmpp-disco-item!
     (xmpp-disco-item (xmpp-disco-item-jid di0) "config" "Configuration")
  
     ; This is the container for the items. The root node for these items is
     ; di0 (defined in the previous example).
     (xmpp-disco-items (xmpp-disco-item-jid di0) (xmpp-disco-item-node di0))))
  
   ; This is the disco#items handler
   disco-items)
  
  ; We indicate that node0@my-component.example.net now also supports
  ; disco#items.
  (add-xmpp-disco-info-entity!
   (add-xmpp-disco-info-feature!
    "http://jabber.org/protocol/disco#items"
    (xmpp-disco-info (xmpp-disco-item-jid di0) (xmpp-disco-item-node di0) '() '() '()))
   disco-info)

1.2 JSON data🔗ℹ

Alternatively, it might be more convenient to load the configuration from a JSON object (see Racket’s JSON module).

For instance, the above configuration can be represented as:

{ "info": { "features": [ { "node": null, "var": "http://jabber.org/protocol/disco#items" }, { "node": null, "var": "http://jabber.org/protocol/disco#info" } ], "identity": [ { "category": "My component", "name": "generic", "type": "component" } ] }, "items": [ { "info": { "features": [ { "node": null, "var": "http://jabber.org/protocol/disco#info" } ], "identity": [ { "category": "Second node", "name": "account", "type": "registered" } ] }, "items": [], "jid": "node1@my-component.example.net", "name": null, "node": null }, { "info": { "features": [ { "node": null, "var": "http://jabber.org/protocol/disco#info" } ], "identity": [ { "category": "First node", "name": "account", "type": "registered" } ] }, "items": [], "jid": "node0@my-component.example.net", "name": null, "node": null } ], "jid": "my-component.example.net", "name": null, "node": null }

We could then load the configuration as:

> (require xmpp json)
  ; 
  ; Let us connect to the server as a component.
  ; 
  (define-values (host port domain secret)
  (values "example.net" 5255 "my-component.example.net" "COMPONENT!SECRET"))
  (define conn (xmpp-connect-component host port domain secret))
  (xmpp-set-handler conn)
  
  ; 
  ; First we set up disco#info
  ; 
  
  ; We start both disco#info and disco#items service
  ; handlers on this connection
  (define disco (xmpp-provide-disco conn))
  
  (define config
   (string-append
    "{\"info\":{\"features\":[{\"node\":null,\"var\":\"http://jabb"
    "er.org/protocol/disco#items\"},{\"node\":null,\"var\":\"http:/"
    "/jabber.org/protocol/disco#info\"}],\"identity\":[{\"category\"\n  "
     ":\"My component\",\"name\":\"generic\",\"type\":\"component\""
    "}]},\"items\":[{\"info\":{\"features\":[{\"node\":null,\"var\""
    ":\"http://jabber.org/protocol/disco#info\"}],\"identity\":[{\""
    "category\":\"Second node\",\"name\":\"account\",\"type\":\"reg"
    "istered\"}]},\"items\":[],\"jid\":\"node1@my-component.example"
    ".net\",\"name\":null,\"node\":null},{\"info\":{\"features\":[{"
    "\"node\":null,\"var\":\"http://jabber.org/protocol/disco#info\"\n  "
     "}],\"identity\":[{\"category\":\"First node\",\"name\":\"acco"
    "unt\",\"type\":\"registered\"}]},\"items\":[],\"jid\":\"node0@"
    "my-component.example.net\",\"name\":null,\"node\":null}],\"jid"
    "\":\"my-component.example.net\",\"name\":null,\"node\":null}"))
  
  (add-xmpp-disco! (string->jsexpr config) (invalid-jid) "" disco)

2 Reference🔗ℹ

2.1 disco🔗ℹ

procedure

(xmpp-provide-disco conn)  (xmpp-disco-provider?)

  conn : xmpp-connection?
Installs both disco#info and disco#items handlers in one go.

procedure

(add-xmpp-disco! json jid node disco)  (xmpp-disco-provider?)

  json : jsexpr?
  jid : jid?
  node : string?
  disco : xmpp-disco-provider?
Read the configuration from a JSON object and insert it as a child of jid+node. If jid+node does not already exist, the configuration will be inserted at the top level of the discovery tree.

Example:

> ; Use (invalid-jid) "" to load the configuration
  ; at the root of the tree.
  (add-xmpp-disco! json-data (invalid-jid) "" disco)

procedure

(xmpp-disco->jsexpr disco jid [node])  (jsexpr?)

  disco : xmpp-disco-provider?
  jid : jid?
  node : string? = ""
Export the discovery configuration for jid+node as a JSON object.

Example:

> ; Export the entire configuration tree
  (xmpp-disco->jsexpr disco (xmpp-connection-jid conn) "")

  '#hasheq(
  
       (info . #hasheq(
  
                (features . (#hasheq((node . null)
                                     (var . "http://jabber.org/protocol/disco#info"))
                      #hasheq((node . null)
                              (var . "http://jabber.org/protocol/disco#items"))))
  
  
                (identity . (#hasheq((category . "My component")
                                     (name . "generic")
                                     (type . "component"))))))
  
  
       (items . (#hasheq(
  
                 (info . #hasheq(
  
                          (features . (#hasheq((node . null)
  
  
                                    (var . "http://jabber.org/protocol/disco#info"))))
  
  
                          (identity . (#hasheq((category . "First node")
                                               (name . "account")
                                               (type . "registered"))))))
                     (items)
                     (jid . "node0@my-component.example.net")
                     (name . null)
                     (node . null))
             #hasheq(
  
                 (info . #hasheq(
  
                          (features . (#hasheq((node . null)
  
  
                                    (var . "http://jabber.org/protocol/disco#info"))))
  
  
                          (identity . (#hasheq((category . "Second node")
                                               (name . "account")
                                               (type . "registered"))))))
                     (items)
                     (jid . "node1@my-component.example.net")
                     (name . null)
                     (node . null))))
           (jid . "my-component.example.net")
           (name . null)
           (node . null))

struct

(struct xmpp-disco-provider (disco-info disco-items))

  disco-info : xmpp-disco-info-provider?
  disco-items : xmpp-disco-items-provider?
Structure that holds a node’s information for both disco#info and disco#items.

2.2 disco#info🔗ℹ

Install the disco#info handler on an xmpp-connection. The connection itself has to be managed via (xmpp-set-handler conn).

The returned xmpp-disco-info-provider mutable object is used to configure the actual disco#info data.

procedure

(add-xmpp-disco-info-entity! entity 
  provider) 
  (xmpp-disco-info-provider?)
  entity : xmpp-disco-info?
  provider : xmpp-disco-info-provider?
Add an entity, either a JID or a node, to the disco#info database, so that disco#info identities and features can be associated to it.

Example:

> (require xmpp)
  
  ; We start the disco#info service handler on this connection
  (define disco-info (xmpp-provide-disco-info conn))
  
  ; Let us create a disco#info object for the component itself
  ; (assume the component's name is my-component.example.net).
  (define disco-info-root
   (xmpp-disco-info (string->jid "my-component.example.net") ""))
  
  ; Now we can add disco#info data to my-component.example.net …
  (add-xmpp-disco-info-identity!
   (xmpp-disco-info-identity "Racket XMPP" "component" "generic")
   disco-info-root)
  
  (add-xmpp-disco-info-feature!
   ; Features are plain-text strings
   "http://jabber.org/protocol/disco#info"
   disco-info-root)

(add-xmpp-disco-info-entity! disco-info-root disco-info)

procedure

(add-xmpp-disco-info-identity! identity 
  entity) 
  (xmpp-disco-info-entity?)
  identity : xmpp-disco-info-identity?
  entity : xmpp-disco-info-entity?
Add an identity entry (in the sense of this service discovery protocol) to the target entity.

procedure

(add-xmpp-disco-info-feature! feature 
  entity) 
  (xmpp-disco-info-entity?)
  feature : string?
  entity : xmpp-disco-info-entity?
Add a feature entry (in the sense of this service discovery protocol) to the target entity.

procedure

(make-xmpp-disco-info jid node)  (xmpp-disco-info?)

  jid : jid?
  node : string?
A convenience function equivalent to (xmpp-disco-info jid node '() '() '()).

struct

(struct xmpp-disco-info-provider (entities))

  entities : (listof (xmpp-disco-info?))
Structure that holds a list of xmpp-disco-info entities, each representing a JID or a JID+node, which in turn hold disco#info data.

struct

(struct xmpp-disco-info (jid node identities features form))

  jid : jid?
  node : string?
  identities : (listof xmpp-disco-info-identity?)
  features : (listof non-empty-string?)
  form : (listof (list/c non-empty-string? (listof string?)))
Structure that holds a node’s disco#info information.

struct

(struct xmpp-disco-info-identity (name category type))

  name : string?
  category : non-empty-string?
  type : non-empty-string?
Structure that represents a disco#info <identity/> entity.

struct

(struct xmpp-disco-info-feature (var node))

  var : string?
  node : string?
Structure that represents a disco#info <feature/> entity.

2.3 disco#items🔗ℹ

Install the disco#items handler on an xmpp-connection. The connection itself has to be managed via (xmpp-set-handler conn).

The returned xmpp-disco-items-provider mutable object is used to configure the actual disco#items data.

procedure

(add-xmpp-disco-items-entity! entity 
  provider) 
  (xmpp-disco-items-provider?)
  entity : xmpp-disco-items?
  provider : xmpp-disco-items-provider?
Add an entity, either a JID or a JID+node, to the disco#items database, so that disco#items nodes can be associated to it.

Example:

> (require xmpp)
  
  ; We start the disco#items service handler on this connection
  (define disco-items (xmpp-provide-disco-items conn))
  
  ; Let us create a disco#items object for the component itself
  ; (assume the component's name is my-component.example.net).
  (define disco-items-root
   (xmpp-disco-items (string->jid "my-component.example.net") ""))
  
  ; Now we can add disco#items data to my-component.example.net …
  (add-xmpp-disco-item!
   (xmpp-disco-item (string->jid "node0@" "my-component.example.net") "" "First node")
   disco-items-root)
  
  (add-xmpp-disco-items-entity!
   disco-items-root
   disco-items)

procedure

(add-xmpp-disco-item! item entity)  (xmpp-disco-items?)

  item : xmpp-disco-item?
  entity : xmpp-disco-items?
Add an item to entity.

procedure

(make-xmpp-disco-items jid node)  (xmpp-disco-items?)

  jid : jid?
  node : string?
A convenience function equivalent to (xmpp-disco-items jid node '()).

struct

(struct xmpp-disco-items-provider (entities))

  entities : (listof (xmpp-disco-items?))
Structure that holds a list of xmpp-disco-items entities, each representing a JID or a JID+node, which in turn hold disco#items data.

struct

(struct xmpp-disco-items (jid node items))

  jid : jid?
  node : string?
  items : (listof xmpp-disco-item?)
Structure representing a JID or JID+node. Its items member holds its children items.

struct

(struct xmpp-disco-item (jid node name))

  jid : jid?
  node : string?
  name : string?
A disco#items <item/> entity.