1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "remoting/signaling/iq_sender.h"
6
7#include "base/bind.h"
8#include "base/location.h"
9#include "base/logging.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/single_thread_task_runner.h"
12#include "base/strings/string_number_conversions.h"
13#include "base/thread_task_runner_handle.h"
14#include "base/time/time.h"
15#include "remoting/signaling/signal_strategy.h"
16#include "third_party/libjingle/source/talk/xmpp/constants.h"
17#include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
18
19namespace remoting {
20
21// static
22scoped_ptr<buzz::XmlElement> IqSender::MakeIqStanza(
23    const std::string& type,
24    const std::string& addressee,
25    scoped_ptr<buzz::XmlElement> iq_body) {
26  scoped_ptr<buzz::XmlElement> stanza(new buzz::XmlElement(buzz::QN_IQ));
27  stanza->AddAttr(buzz::QN_TYPE, type);
28  if (!addressee.empty())
29    stanza->AddAttr(buzz::QN_TO, addressee);
30  stanza->AddElement(iq_body.release());
31  return stanza.Pass();
32}
33
34IqSender::IqSender(SignalStrategy* signal_strategy)
35    : signal_strategy_(signal_strategy) {
36  signal_strategy_->AddListener(this);
37}
38
39IqSender::~IqSender() {
40  signal_strategy_->RemoveListener(this);
41}
42
43scoped_ptr<IqRequest> IqSender::SendIq(scoped_ptr<buzz::XmlElement> stanza,
44                                       const ReplyCallback& callback) {
45  std::string addressee = stanza->Attr(buzz::QN_TO);
46  std::string id = signal_strategy_->GetNextId();
47  stanza->AddAttr(buzz::QN_ID, id);
48  if (!signal_strategy_->SendStanza(stanza.Pass())) {
49    return scoped_ptr<IqRequest>();
50  }
51  DCHECK(requests_.find(id) == requests_.end());
52  scoped_ptr<IqRequest> request(new IqRequest(this, callback, addressee));
53  if (!callback.is_null())
54    requests_[id] = request.get();
55  return request.Pass();
56}
57
58scoped_ptr<IqRequest> IqSender::SendIq(const std::string& type,
59                                       const std::string& addressee,
60                                       scoped_ptr<buzz::XmlElement> iq_body,
61                                       const ReplyCallback& callback) {
62  return SendIq(MakeIqStanza(type, addressee, iq_body.Pass()), callback);
63}
64
65void IqSender::RemoveRequest(IqRequest* request) {
66  IqRequestMap::iterator it = requests_.begin();
67  while (it != requests_.end()) {
68    IqRequestMap::iterator cur = it;
69    ++it;
70    if (cur->second == request) {
71      requests_.erase(cur);
72      break;
73    }
74  }
75}
76
77void IqSender::OnSignalStrategyStateChange(SignalStrategy::State state) {
78}
79
80bool IqSender::OnSignalStrategyIncomingStanza(const buzz::XmlElement* stanza) {
81  if (stanza->Name() != buzz::QN_IQ) {
82    LOG(WARNING) << "Received unexpected non-IQ packet " << stanza->Str();
83    return false;
84  }
85
86  const std::string& type = stanza->Attr(buzz::QN_TYPE);
87  if (type.empty()) {
88    LOG(WARNING) << "IQ packet missing type " << stanza->Str();
89    return false;
90  }
91
92  if (type != "result" && type != "error") {
93    return false;
94  }
95
96  const std::string& id = stanza->Attr(buzz::QN_ID);
97  if (id.empty()) {
98    LOG(WARNING) << "IQ packet missing id " << stanza->Str();
99    return false;
100  }
101
102  std::string from = stanza->Attr(buzz::QN_FROM);
103
104  IqRequestMap::iterator it = requests_.find(id);
105  if (it == requests_.end()) {
106    return false;
107  }
108
109  IqRequest* request = it->second;
110
111  if (request->addressee_ != from) {
112    LOG(ERROR) << "Received IQ response from from a invalid JID. Ignoring it."
113               << " Message received from: " << from
114               << " Original JID: " << request->addressee_;
115    return false;
116  }
117
118  requests_.erase(it);
119  request->OnResponse(stanza);
120
121  return true;
122}
123
124IqRequest::IqRequest(IqSender* sender, const IqSender::ReplyCallback& callback,
125                     const std::string& addressee)
126    : sender_(sender),
127      callback_(callback),
128      addressee_(addressee) {
129}
130
131IqRequest::~IqRequest() {
132  sender_->RemoveRequest(this);
133}
134
135void IqRequest::SetTimeout(base::TimeDelta timeout) {
136  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
137      FROM_HERE, base::Bind(&IqRequest::OnTimeout, AsWeakPtr()), timeout);
138}
139
140void IqRequest::CallCallback(const buzz::XmlElement* stanza) {
141  if (!callback_.is_null()) {
142    IqSender::ReplyCallback callback(callback_);
143    callback_.Reset();
144    callback.Run(this, stanza);
145  }
146}
147
148void IqRequest::OnTimeout() {
149  CallCallback(NULL);
150}
151
152void IqRequest::OnResponse(const buzz::XmlElement* stanza) {
153  // It's unsafe to delete signal strategy here, and the callback may
154  // want to do that, so we post task to invoke the callback later.
155  scoped_ptr<buzz::XmlElement> stanza_copy(new buzz::XmlElement(*stanza));
156  base::ThreadTaskRunnerHandle::Get()->PostTask(
157      FROM_HERE, base::Bind(&IqRequest::DeliverResponse, AsWeakPtr(),
158                            base::Passed(&stanza_copy)));
159}
160
161void IqRequest::DeliverResponse(scoped_ptr<buzz::XmlElement> stanza) {
162  CallCallback(stanza.get());
163}
164
165}  // namespace remoting
166