~samwhited/xmpp

xmpp/ping/ping.go -rw-r--r-- 2.8 KiB
e9b0a2deSam Whited docs: do a quick editing pass over the docs a day ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// 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.

//go:generate go run ../internal/genfeature -receiver "h Handler"

// Package ping implements XEP-0199: XMPP Ping.
package ping // import "mellium.im/xmpp/ping"

import (
	"context"
	"encoding/xml"
	"errors"

	"mellium.im/xmlstream"
	"mellium.im/xmpp"
	"mellium.im/xmpp/jid"
	"mellium.im/xmpp/mux"
	"mellium.im/xmpp/stanza"
)

// NS is the XML namespace used by XMPP pings. It is provided as a convenience.
const NS = `urn:xmpp:ping`

// Handle returns an option that registers a Handler for ping requests.
func Handle() mux.Option {
	return mux.IQ(stanza.GetIQ, xml.Name{Local: "ping", Space: NS}, Handler{})
}

// Handler responds to ping requests.
type Handler struct{}

// HandleIQ implements mux.IQHandler.
func (h Handler) HandleIQ(iq stanza.IQ, t xmlstream.TokenReadEncoder, start *xml.StartElement) error {
	if iq.Type != stanza.GetIQ || start.Name.Local != "ping" || start.Name.Space != NS {
		return nil
	}

	_, err := xmlstream.Copy(t, iq.Result(nil))
	return err
}

// Send sends a ping to the provided JID and blocks until a response is
// received.
// Pings sent to other clients should use the full JID, otherwise they will be
// handled by the server.
//
// If the remote JID reports that the ping service is unavailable, no error is
// returned because we were able to receive the error response (the remote
// resource exists and could be pinged, it just doesn't support this particular
// protocol for doing so).
func Send(ctx context.Context, s *xmpp.Session, to jid.JID) error {
	err := s.UnmarshalIQ(ctx, IQ{IQ: stanza.IQ{
		Type: stanza.GetIQ,
		To:   to,
	}}.TokenReader(), nil)

	if stanzaErr := (stanza.Error{}); errors.As(err, &stanzaErr) {
		// If the ping namespace isn't supported and we get back
		// service-unavailable, treat this as if the ping succeeded (because the
		// client was obviously able to send us the error that ping isn't
		// supported).
		if stanzaErr.Condition == stanza.ServiceUnavailable {
			return nil
		}
	}
	return err
}

// IQ is encoded as a ping request.
type IQ struct {
	stanza.IQ

	Ping struct{} `xml:"urn:xmpp:ping ping"`
}

// WriteXML satisfies the xmlstream.WriterTo interface. It is like MarshalXML
// except it writes tokens to w.
func (iq IQ) WriteXML(w xmlstream.TokenWriter) (int, error) {
	return xmlstream.Copy(w, iq.TokenReader())
}

// MarshalXML implements xml.Marshaler.
func (iq IQ) MarshalXML(e *xml.Encoder, _ xml.StartElement) error {
	_, err := iq.WriteXML(e)
	return err
}

// TokenReader satisfies the xmlstream.Marshaler interface.
func (iq IQ) TokenReader() xml.TokenReader {
	start := xml.StartElement{Name: xml.Name{Local: "ping", Space: NS}}
	return iq.Wrap(xmlstream.Wrap(nil, start))
}