1// Copyright 2013 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 "google_apis/gcm/base/mcs_util.h"
6
7#include "base/format_macros.h"
8#include "base/logging.h"
9#include "base/strings/string_number_conversions.h"
10#include "base/strings/stringprintf.h"
11#include "base/time/clock.h"
12#include "base/time/time.h"
13
14namespace gcm {
15
16namespace {
17
18// Type names corresponding to MCSProtoTags. Useful for identifying what type
19// of MCS protobuf is contained within a google::protobuf::MessageLite object.
20// WARNING: must match the order in MCSProtoTag.
21const char* kProtoNames[] = {
22  "mcs_proto.HeartbeatPing",
23  "mcs_proto.HeartbeatAck",
24  "mcs_proto.LoginRequest",
25  "mcs_proto.LoginResponse",
26  "mcs_proto.Close",
27  "mcs_proto.MessageStanza",
28  "mcs_proto.PresenceStanza",
29  "mcs_proto.IqStanza",
30  "mcs_proto.DataMessageStanza",
31  "mcs_proto.BatchPresenceStanza",
32  "mcs_proto.StreamErrorStanza",
33  "mcs_proto.HttpRequest",
34  "mcs_proto.HttpResponse",
35  "mcs_proto.BindAccountRequest",
36  "mcs_proto.BindAccountResponse",
37  "mcs_proto.TalkMetadata"
38};
39COMPILE_ASSERT(arraysize(kProtoNames) == kNumProtoTypes,
40               ProtoNamesMustIncludeAllTags);
41
42const char kLoginId[] = "chrome-";
43const char kLoginDomain[] = "mcs.android.com";
44const char kLoginDeviceIdPrefix[] = "android-";
45const char kLoginSettingDefaultName[] = "new_vc";
46const char kLoginSettingDefaultValue[] = "1";
47
48// Maximum amount of time to save an unsent outgoing message for.
49const int kMaxTTLSeconds = 24 * 60 * 60;  // 1 day.
50
51}  // namespace
52
53scoped_ptr<mcs_proto::LoginRequest> BuildLoginRequest(
54    uint64 auth_id,
55    uint64 auth_token,
56    const std::string& version_string) {
57  // Create a hex encoded auth id for the device id field.
58  std::string auth_id_hex;
59  auth_id_hex = base::StringPrintf("%" PRIx64, auth_id);
60
61  std::string auth_id_str = base::Uint64ToString(auth_id);
62  std::string auth_token_str = base::Uint64ToString(auth_token);
63
64  scoped_ptr<mcs_proto::LoginRequest> login_request(
65      new mcs_proto::LoginRequest());
66
67  login_request->set_adaptive_heartbeat(false);
68  login_request->set_auth_service(mcs_proto::LoginRequest::ANDROID_ID);
69  login_request->set_auth_token(auth_token_str);
70  login_request->set_id(kLoginId + version_string);
71  login_request->set_domain(kLoginDomain);
72  login_request->set_device_id(kLoginDeviceIdPrefix + auth_id_hex);
73  login_request->set_network_type(1);
74  login_request->set_resource(auth_id_str);
75  login_request->set_user(auth_id_str);
76  login_request->set_use_rmq2(true);
77
78  login_request->add_setting();
79  login_request->mutable_setting(0)->set_name(kLoginSettingDefaultName);
80  login_request->mutable_setting(0)->set_value(kLoginSettingDefaultValue);
81  return login_request.Pass();
82}
83
84scoped_ptr<mcs_proto::IqStanza> BuildStreamAck() {
85  scoped_ptr<mcs_proto::IqStanza> stream_ack_iq(new mcs_proto::IqStanza());
86  stream_ack_iq->set_type(mcs_proto::IqStanza::SET);
87  stream_ack_iq->set_id("");
88  stream_ack_iq->mutable_extension()->set_id(kStreamAck);
89  stream_ack_iq->mutable_extension()->set_data("");
90  return stream_ack_iq.Pass();
91}
92
93scoped_ptr<mcs_proto::IqStanza> BuildSelectiveAck(
94    const std::vector<std::string>& acked_ids) {
95  scoped_ptr<mcs_proto::IqStanza> selective_ack_iq(new mcs_proto::IqStanza());
96  selective_ack_iq->set_type(mcs_proto::IqStanza::SET);
97  selective_ack_iq->set_id("");
98  selective_ack_iq->mutable_extension()->set_id(kSelectiveAck);
99  mcs_proto::SelectiveAck selective_ack;
100  for (size_t i = 0; i < acked_ids.size(); ++i)
101    selective_ack.add_id(acked_ids[i]);
102  selective_ack_iq->mutable_extension()->set_data(
103      selective_ack.SerializeAsString());
104  return selective_ack_iq.Pass();
105}
106
107// Utility method to build a google::protobuf::MessageLite object from a MCS
108// tag.
109scoped_ptr<google::protobuf::MessageLite> BuildProtobufFromTag(uint8 tag) {
110  switch(tag) {
111    case kHeartbeatPingTag:
112      return scoped_ptr<google::protobuf::MessageLite>(
113          new mcs_proto::HeartbeatPing());
114    case kHeartbeatAckTag:
115      return scoped_ptr<google::protobuf::MessageLite>(
116          new mcs_proto::HeartbeatAck());
117    case kLoginRequestTag:
118      return scoped_ptr<google::protobuf::MessageLite>(
119          new mcs_proto::LoginRequest());
120    case kLoginResponseTag:
121      return scoped_ptr<google::protobuf::MessageLite>(
122          new mcs_proto::LoginResponse());
123    case kCloseTag:
124      return scoped_ptr<google::protobuf::MessageLite>(
125          new mcs_proto::Close());
126    case kIqStanzaTag:
127      return scoped_ptr<google::protobuf::MessageLite>(
128          new mcs_proto::IqStanza());
129    case kDataMessageStanzaTag:
130      return scoped_ptr<google::protobuf::MessageLite>(
131          new mcs_proto::DataMessageStanza());
132    case kStreamErrorStanzaTag:
133      return scoped_ptr<google::protobuf::MessageLite>(
134          new mcs_proto::StreamErrorStanza());
135    default:
136      return scoped_ptr<google::protobuf::MessageLite>();
137  }
138}
139
140// Utility method to extract a MCS tag from a google::protobuf::MessageLite
141// object.
142int GetMCSProtoTag(const google::protobuf::MessageLite& message) {
143  const std::string& type_name = message.GetTypeName();
144  if (type_name == kProtoNames[kHeartbeatPingTag]) {
145    return kHeartbeatPingTag;
146  } else if (type_name == kProtoNames[kHeartbeatAckTag]) {
147    return kHeartbeatAckTag;
148  } else if (type_name == kProtoNames[kLoginRequestTag]) {
149    return kLoginRequestTag;
150  } else if (type_name == kProtoNames[kLoginResponseTag]) {
151    return kLoginResponseTag;
152  } else if (type_name == kProtoNames[kCloseTag]) {
153    return kCloseTag;
154  } else if (type_name == kProtoNames[kIqStanzaTag]) {
155    return kIqStanzaTag;
156  } else if (type_name == kProtoNames[kDataMessageStanzaTag]) {
157    return kDataMessageStanzaTag;
158  } else if (type_name == kProtoNames[kStreamErrorStanzaTag]) {
159    return kStreamErrorStanzaTag;
160  }
161  return -1;
162}
163
164std::string GetPersistentId(const google::protobuf::MessageLite& protobuf) {
165  if (protobuf.GetTypeName() == kProtoNames[kIqStanzaTag]) {
166    return reinterpret_cast<const mcs_proto::IqStanza*>(&protobuf)->
167        persistent_id();
168  } else if (protobuf.GetTypeName() == kProtoNames[kDataMessageStanzaTag]) {
169    return reinterpret_cast<const mcs_proto::DataMessageStanza*>(&protobuf)->
170        persistent_id();
171  }
172  // Not all message types have persistent ids. Just return empty string;
173  return "";
174}
175
176void SetPersistentId(const std::string& persistent_id,
177                     google::protobuf::MessageLite* protobuf) {
178  if (protobuf->GetTypeName() == kProtoNames[kIqStanzaTag]) {
179    reinterpret_cast<mcs_proto::IqStanza*>(protobuf)->
180        set_persistent_id(persistent_id);
181    return;
182  } else if (protobuf->GetTypeName() == kProtoNames[kDataMessageStanzaTag]) {
183    reinterpret_cast<mcs_proto::DataMessageStanza*>(protobuf)->
184        set_persistent_id(persistent_id);
185    return;
186  }
187  NOTREACHED();
188}
189
190uint32 GetLastStreamIdReceived(const google::protobuf::MessageLite& protobuf) {
191  if (protobuf.GetTypeName() == kProtoNames[kIqStanzaTag]) {
192    return reinterpret_cast<const mcs_proto::IqStanza*>(&protobuf)->
193        last_stream_id_received();
194  } else if (protobuf.GetTypeName() == kProtoNames[kDataMessageStanzaTag]) {
195    return reinterpret_cast<const mcs_proto::DataMessageStanza*>(&protobuf)->
196        last_stream_id_received();
197  } else if (protobuf.GetTypeName() == kProtoNames[kHeartbeatPingTag]) {
198    return reinterpret_cast<const mcs_proto::HeartbeatPing*>(&protobuf)->
199        last_stream_id_received();
200  } else if (protobuf.GetTypeName() == kProtoNames[kHeartbeatAckTag]) {
201    return reinterpret_cast<const mcs_proto::HeartbeatAck*>(&protobuf)->
202        last_stream_id_received();
203  } else if (protobuf.GetTypeName() == kProtoNames[kLoginResponseTag]) {
204    return reinterpret_cast<const mcs_proto::LoginResponse*>(&protobuf)->
205        last_stream_id_received();
206  }
207  // Not all message types have last stream ids. Just return 0.
208  return 0;
209}
210
211void SetLastStreamIdReceived(uint32 val,
212                             google::protobuf::MessageLite* protobuf) {
213  if (protobuf->GetTypeName() == kProtoNames[kIqStanzaTag]) {
214    reinterpret_cast<mcs_proto::IqStanza*>(protobuf)->
215        set_last_stream_id_received(val);
216    return;
217  } else if (protobuf->GetTypeName() == kProtoNames[kHeartbeatPingTag]) {
218    reinterpret_cast<mcs_proto::HeartbeatPing*>(protobuf)->
219        set_last_stream_id_received(val);
220    return;
221  } else if (protobuf->GetTypeName() == kProtoNames[kHeartbeatAckTag]) {
222    reinterpret_cast<mcs_proto::HeartbeatAck*>(protobuf)->
223        set_last_stream_id_received(val);
224    return;
225  } else if (protobuf->GetTypeName() == kProtoNames[kDataMessageStanzaTag]) {
226    reinterpret_cast<mcs_proto::DataMessageStanza*>(protobuf)->
227        set_last_stream_id_received(val);
228    return;
229  } else if (protobuf->GetTypeName() == kProtoNames[kLoginResponseTag]) {
230    reinterpret_cast<mcs_proto::LoginResponse*>(protobuf)->
231        set_last_stream_id_received(val);
232    return;
233  }
234  NOTREACHED();
235}
236
237bool HasTTLExpired(const google::protobuf::MessageLite& protobuf,
238                   base::Clock* clock) {
239  if (protobuf.GetTypeName() != kProtoNames[kDataMessageStanzaTag])
240    return false;
241  uint64 ttl = GetTTL(protobuf);
242  uint64 sent =
243      reinterpret_cast<const mcs_proto::DataMessageStanza*>(&protobuf)->sent();
244  DCHECK(sent);
245  return ttl > 0 &&
246      clock->Now() >
247          base::Time::FromInternalValue(
248              (sent + ttl) * base::Time::kMicrosecondsPerSecond);
249}
250
251int GetTTL(const google::protobuf::MessageLite& protobuf) {
252  if (protobuf.GetTypeName() != kProtoNames[kDataMessageStanzaTag])
253    return 0;
254  const mcs_proto::DataMessageStanza* data_message =
255      reinterpret_cast<const mcs_proto::DataMessageStanza*>(&protobuf);
256  if (!data_message->has_ttl())
257    return kMaxTTLSeconds;
258  DCHECK_LE(data_message->ttl(), kMaxTTLSeconds);
259  return data_message->ttl();
260}
261
262}  // namespace gcm
263