aboutsummaryrefslogtreecommitdiff
path: root/libraries/katabasis/src/PollServer.cpp
blob: 1083c599472ac643c3560954662bccb912f2d132 (plain)
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <QNetworkAccessManager>
#include <QNetworkReply>

#include "katabasis/PollServer.h"
#include "JsonResponse.h"

namespace {
QMap<QString, QString> toVerificationParams(const QVariantMap &map)
{
    QMap<QString, QString> params;
    for (QVariantMap::const_iterator i = map.constBegin();
         i != map.constEnd(); ++i)
    {
        params[i.key()] = i.value().toString();
    }
    return params;
}
}

namespace Katabasis {

PollServer::PollServer(QNetworkAccessManager *manager, const QNetworkRequest &request, const QByteArray &payload, int expiresIn, QObject *parent)
    : QObject(parent)
    , manager_(manager)
    , request_(request)
    , payload_(payload)
    , expiresIn_(expiresIn)
{
    expirationTimer.setTimerType(Qt::VeryCoarseTimer);
    expirationTimer.setInterval(expiresIn * 1000);
    expirationTimer.setSingleShot(true);
    connect(&expirationTimer, SIGNAL(timeout()), this, SLOT(onExpiration()));
    expirationTimer.start();

    pollTimer.setTimerType(Qt::VeryCoarseTimer);
    pollTimer.setInterval(5 * 1000);
    pollTimer.setSingleShot(true);
    connect(&pollTimer, SIGNAL(timeout()), this, SLOT(onPollTimeout()));
}

int PollServer::interval() const
{
    return pollTimer.interval() / 1000;
}

void PollServer::setInterval(int interval)
{
    pollTimer.setInterval(interval * 1000);
}

void PollServer::startPolling()
{
    if (expirationTimer.isActive()) {
        pollTimer.start();
    }
}

void PollServer::onPollTimeout()
{
    qDebug() << "PollServer::onPollTimeout: retrying";
    QNetworkReply * reply = manager_->post(request_, payload_);
    connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished()));
}

void PollServer::onExpiration()
{
    pollTimer.stop();
    emit serverClosed(false);
}

void PollServer::onReplyFinished()
{
    QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());

    if (!reply) {
        qDebug() << "PollServer::onReplyFinished: reply is null";
        return;
    }

    QByteArray replyData = reply->readAll();
    QMap<QString, QString> params = toVerificationParams(parseJsonResponse(replyData));

    // Dump replyData
    // SENSITIVE DATA in RelWithDebInfo or Debug builds
    // qDebug() << "PollServer::onReplyFinished: replyData\n";
    // qDebug() << QString( replyData );

    if (reply->error() == QNetworkReply::TimeoutError) {
        // rfc8628#section-3.2
        // "On encountering a connection timeout, clients MUST unilaterally
        // reduce their polling frequency before retrying.  The use of an
        // exponential backoff algorithm to achieve this, such as doubling the
        // polling interval on each such connection timeout, is RECOMMENDED."
        setInterval(interval() * 2);
        pollTimer.start();
    }
    else {
        QString error = params.value("error");
        if (error == "slow_down") {
            // rfc8628#section-3.2
            // "A variant of 'authorization_pending', the authorization request is
            // still pending and polling should continue, but the interval MUST
            // be increased by 5 seconds for this and all subsequent requests."
            setInterval(interval() + 5);
            pollTimer.start();
        }
        else if (error == "authorization_pending") {
            // keep trying - rfc8628#section-3.2
            // "The authorization request is still pending as the end user hasn't
            // yet completed the user-interaction steps (Section 3.3)."
            pollTimer.start();
        }
        else {
            expirationTimer.stop();
            emit serverClosed(true);
            // let O2 handle the other cases
            emit verificationReceived(params);
        }
    }
    reply->deleteLater();
}

}