1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package xmpp implements the XMPP IM protocol, as specified in RFC 6120 and
6// 6121.
7// Ping implements the XMPP extension Ping, as specified in xep-0199
8package xmpp
9
10import (
11	"errors"
12	"log"
13	"time"
14
15	"github.com/coyim/coyim/xmpp/data"
16)
17
18// SendPing sends a Ping request.
19func (c *conn) SendPing() (reply <-chan data.Stanza, cookie data.Cookie, err error) {
20	//TODO: should not this be set when we send any message? Why would I send a ping
21	//just after sending a message?
22	c.lastPingRequest = time.Now() //TODO: this seems should not belong to Conn
23	return c.SendIQ("", "get", data.PingRequest{})
24}
25
26// SendPingReply sends a reply to a Ping request.
27func (c *conn) SendPingReply(id string) error {
28	return c.SendIQReply("", "result", id, data.EmptyReply{})
29}
30
31// ReceivePong update the timestamp for lastPongResponse,
32func (c *conn) ReceivePong() {
33	c.lastPongResponse = time.Now() //TODO: this seems should not belong to Conn
34}
35
36// ParsePong parse a reply of a Pong response.
37func ParsePong(reply data.Stanza) error {
38	iq, ok := reply.Value.(*data.ClientIQ)
39	if !ok {
40		return errors.New("xmpp: ping request resulted in tag of type " + reply.Name.Local)
41	}
42	switch iq.Type {
43	case "result":
44		return nil
45	case "error":
46		return errors.New("xmpp: ping request resulted in a error: " + iq.Error.Text)
47	default:
48		return errors.New("xmpp: ping request resulted in a unexpected type")
49	}
50}
51
52var (
53	pingIterval     = 10 * time.Second //should be 5 minutes at least, per spec
54	pingTimeout     = 30 * time.Second
55	maxPingFailures = 2
56)
57
58func (c *conn) watchPings() {
59	tick := time.NewTicker(pingIterval)
60	defer tick.Stop()
61	failures := 0
62
63	for _ = range tick.C {
64		if c.closed {
65			return
66		}
67
68		pongReply, _, err := c.SendPing()
69		if err != nil {
70			return
71		}
72
73		select {
74		case <-time.After(pingTimeout):
75			// ping timed out
76			failures = failures + 1
77		case pongStanza, ok := <-pongReply:
78			if !ok {
79				// pong cancelled
80				continue
81			}
82
83			failures = 0
84			iq, ok := pongStanza.Value.(*data.ClientIQ)
85			if !ok {
86				//not the expected IQ
87				return
88			}
89
90			//TODO: check for <service-unavailable/>
91			if iq.Type == "error" {
92				//server does not support Ping
93				return
94			}
95		}
96
97		if failures < maxPingFailures {
98			continue
99		}
100
101		log.Println("xmpp: ping failures reached threshold of", maxPingFailures)
102		go c.sendStreamError(data.StreamError{
103			DefinedCondition: data.ConnectionTimeout,
104		})
105
106		return
107	}
108}
109