Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(227)

Side by Side Diff: remoting/protocol/pairing_registry.cc

Issue 21128006: Refactored PairingRegistry::Delegate such that it can retrieve/modify for a single client. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: - Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "remoting/protocol/pairing_registry.h" 5 #include "remoting/protocol/pairing_registry.h"
6 6
7 #include "base/base64.h" 7 #include "base/base64.h"
8 #include "base/bind.h" 8 #include "base/bind.h"
9 #include "base/guid.h" 9 #include "base/guid.h"
10 #include "base/json/json_string_value_serializer.h" 10 #include "base/json/json_string_value_serializer.h"
11 #include "base/location.h"
12 #include "base/single_thread_task_runner.h"
11 #include "base/strings/string_number_conversions.h" 13 #include "base/strings/string_number_conversions.h"
14 #include "base/thread_task_runner_handle.h"
12 #include "base/values.h" 15 #include "base/values.h"
13 #include "crypto/random.h" 16 #include "crypto/random.h"
14 17
15 namespace remoting { 18 namespace remoting {
16 namespace protocol { 19 namespace protocol {
17 20
18 // How many bytes of random data to use for the shared secret. 21 // How many bytes of random data to use for the shared secret.
19 const int kKeySize = 16; 22 const int kKeySize = 16;
20 23
21 const char PairingRegistry::kCreatedTimeKey[] = "createdTime"; 24 const char PairingRegistry::kCreatedTimeKey[] = "createdTime";
22 const char PairingRegistry::kClientIdKey[] = "clientId"; 25 const char PairingRegistry::kClientIdKey[] = "clientId";
23 const char PairingRegistry::kClientNameKey[] = "clientName"; 26 const char PairingRegistry::kClientNameKey[] = "clientName";
24 const char PairingRegistry::kSharedSecretKey[] = "sharedSecret"; 27 const char PairingRegistry::kSharedSecretKey[] = "sharedSecret";
25 28
26 PairingRegistry::Pairing::Pairing() { 29 PairingRegistry::Pairing::Pairing() {
27 } 30 }
28 31
29 PairingRegistry::Pairing::Pairing(const base::Time& created_time, 32 PairingRegistry::Pairing::Pairing(const base::Time& created_time,
30 const std::string& client_name, 33 const std::string& client_name,
31 const std::string& client_id, 34 const std::string& client_id,
32 const std::string& shared_secret) 35 const std::string& shared_secret)
33 : created_time_(created_time), 36 : created_time_(created_time),
34 client_name_(client_name), 37 client_name_(client_name),
35 client_id_(client_id), 38 client_id_(client_id),
36 shared_secret_(shared_secret) { 39 shared_secret_(shared_secret) {
37 } 40 }
38 41
42 PairingRegistry::Pairing::~Pairing() {
43 }
44
39 PairingRegistry::Pairing PairingRegistry::Pairing::Create( 45 PairingRegistry::Pairing PairingRegistry::Pairing::Create(
40 const std::string& client_name) { 46 const std::string& client_name) {
41 base::Time created_time = base::Time::Now(); 47 base::Time created_time = base::Time::Now();
42 std::string client_id = base::GenerateGUID(); 48 std::string client_id = base::GenerateGUID();
43 std::string shared_secret; 49 std::string shared_secret;
44 char buffer[kKeySize]; 50 char buffer[kKeySize];
45 crypto::RandBytes(buffer, arraysize(buffer)); 51 crypto::RandBytes(buffer, arraysize(buffer));
46 if (!base::Base64Encode(base::StringPiece(buffer, arraysize(buffer)), 52 if (!base::Base64Encode(base::StringPiece(buffer, arraysize(buffer)),
47 &shared_secret)) { 53 &shared_secret)) {
48 LOG(FATAL) << "Base64Encode failed."; 54 LOG(FATAL) << "Base64Encode failed.";
49 } 55 }
50 return Pairing(created_time, client_name, client_id, shared_secret); 56 return Pairing(created_time, client_name, client_id, shared_secret);
51 } 57 }
52 58
53 PairingRegistry::Pairing::~Pairing() { 59 PairingRegistry::Pairing PairingRegistry::Pairing::CreateFromValue(
60 const base::Value& pairing_json) {
61 const base::DictionaryValue* pairing = NULL;
62 if (!pairing_json.GetAsDictionary(&pairing)) {
63 LOG(ERROR) << "Failed to load pairing information: not a dictionary.";
64 return Pairing();
65 }
66
67 std::string client_name, client_id, shared_secret;
68 double created_time_value;
69 if (pairing->GetDouble(kCreatedTimeKey, &created_time_value) &&
70 pairing->GetString(kClientNameKey, &client_name) &&
71 pairing->GetString(kClientIdKey, &client_id) &&
72 pairing->GetString(kSharedSecretKey, &shared_secret)) {
73 base::Time created_time = base::Time::FromJsTime(created_time_value);
74 return Pairing(created_time, client_name, client_id, shared_secret);
75 }
76
77 LOG(ERROR) << "Failed to load pairing information: unexpected format.";
78 return Pairing();
79 }
80
81 scoped_ptr<base::Value> PairingRegistry::Pairing::ToValue() const {
82 scoped_ptr<base::DictionaryValue> pairing(new base::DictionaryValue());
83 pairing->SetDouble(kCreatedTimeKey, created_time().ToJsTime());
84 pairing->SetString(kClientNameKey, client_name());
85 pairing->SetString(kClientIdKey, client_id());
86 if (!shared_secret().empty())
87 pairing->SetString(kSharedSecretKey, shared_secret());
88 return pairing.PassAs<base::Value>();
54 } 89 }
55 90
56 bool PairingRegistry::Pairing::operator==(const Pairing& other) const { 91 bool PairingRegistry::Pairing::operator==(const Pairing& other) const {
57 return created_time_ == other.created_time_ && 92 return created_time_ == other.created_time_ &&
58 client_id_ == other.client_id_ && 93 client_id_ == other.client_id_ &&
59 client_name_ == other.client_name_ && 94 client_name_ == other.client_name_ &&
60 shared_secret_ == other.shared_secret_; 95 shared_secret_ == other.shared_secret_;
61 } 96 }
62 97
63 bool PairingRegistry::Pairing::is_valid() const { 98 bool PairingRegistry::Pairing::is_valid() const {
64 return !client_id_.empty() && !shared_secret_.empty(); 99 return !client_id_.empty() && !shared_secret_.empty();
65 } 100 }
66 101
67 PairingRegistry::PairingRegistry(scoped_ptr<Delegate> delegate) 102 PairingRegistry::PairingRegistry(
68 : delegate_(delegate.Pass()) { 103 scoped_refptr<base::SingleThreadTaskRunner> delegate_task_runner,
104 scoped_ptr<Delegate> delegate)
105 : caller_task_runner_(base::ThreadTaskRunnerHandle::Get()),
106 delegate_task_runner_(delegate_task_runner),
107 delegate_(delegate.Pass()) {
69 DCHECK(delegate_); 108 DCHECK(delegate_);
70 } 109 }
71 110
72 PairingRegistry::~PairingRegistry() {
73 }
74
75 PairingRegistry::Pairing PairingRegistry::CreatePairing( 111 PairingRegistry::Pairing PairingRegistry::CreatePairing(
76 const std::string& client_name) { 112 const std::string& client_name) {
77 DCHECK(CalledOnValidThread()); 113 DCHECK(caller_task_runner_->BelongsToCurrentThread());
114
78 Pairing result = Pairing::Create(client_name); 115 Pairing result = Pairing::Create(client_name);
79 AddPairing(result); 116 AddPairing(result);
80 return result; 117 return result;
81 } 118 }
82 119
83 void PairingRegistry::GetPairing(const std::string& client_id, 120 void PairingRegistry::GetPairing(const std::string& client_id,
84 const GetPairingCallback& callback) { 121 const GetPairingCallback& callback) {
85 DCHECK(CalledOnValidThread()); 122 DCHECK(caller_task_runner_->BelongsToCurrentThread());
123
86 GetPairingCallback wrapped_callback = base::Bind( 124 GetPairingCallback wrapped_callback = base::Bind(
87 &PairingRegistry::InvokeGetPairingCallbackAndScheduleNext, 125 &PairingRegistry::InvokeGetPairingCallbackAndScheduleNext,
88 this, callback); 126 this, callback);
89 LoadCallback load_callback = base::Bind(
90 &PairingRegistry::DoGetPairing, this, client_id, wrapped_callback);
91 // |Unretained| and |get| are both safe here because the delegate is owned
92 // by the pairing registry and so is guaranteed to exist when the request
93 // is serviced.
94 base::Closure request = base::Bind( 127 base::Closure request = base::Bind(
95 &PairingRegistry::Delegate::Load, 128 &PairingRegistry::DoLoad, this, client_id, wrapped_callback);
96 base::Unretained(delegate_.get()), load_callback);
97 ServiceOrQueueRequest(request); 129 ServiceOrQueueRequest(request);
98 } 130 }
99 131
100 void PairingRegistry::GetAllPairings( 132 void PairingRegistry::GetAllPairings(
101 const GetAllPairingsCallback& callback) { 133 const GetAllPairingsCallback& callback) {
102 DCHECK(CalledOnValidThread()); 134 DCHECK(caller_task_runner_->BelongsToCurrentThread());
135
103 GetAllPairingsCallback wrapped_callback = base::Bind( 136 GetAllPairingsCallback wrapped_callback = base::Bind(
104 &PairingRegistry::InvokeGetAllPairingsCallbackAndScheduleNext, 137 &PairingRegistry::InvokeGetAllPairingsCallbackAndScheduleNext,
105 this, callback); 138 this, callback);
106 LoadCallback load_callback = base::Bind( 139 GetAllPairingsCallback sanitize_callback = base::Bind(
107 &PairingRegistry::SanitizePairings, this, wrapped_callback); 140 &PairingRegistry::SanitizePairings,
141 this, wrapped_callback);
108 base::Closure request = base::Bind( 142 base::Closure request = base::Bind(
109 &PairingRegistry::Delegate::Load, 143 &PairingRegistry::DoLoadAll, this, sanitize_callback);
110 base::Unretained(delegate_.get()), load_callback);
111 ServiceOrQueueRequest(request); 144 ServiceOrQueueRequest(request);
112 } 145 }
113 146
114 void PairingRegistry::DeletePairing( 147 void PairingRegistry::DeletePairing(
115 const std::string& client_id, const SaveCallback& callback) { 148 const std::string& client_id, const DoneCallback& callback) {
116 DCHECK(CalledOnValidThread()); 149 DCHECK(caller_task_runner_->BelongsToCurrentThread());
117 SaveCallback wrapped_callback = base::Bind( 150
118 &PairingRegistry::InvokeSaveCallbackAndScheduleNext, 151 DoneCallback wrapped_callback = base::Bind(
152 &PairingRegistry::InvokeDoneCallbackAndScheduleNext,
119 this, callback); 153 this, callback);
120 LoadCallback load_callback = base::Bind(
121 &PairingRegistry::DoDeletePairing, this, client_id, wrapped_callback);
122 base::Closure request = base::Bind( 154 base::Closure request = base::Bind(
123 &PairingRegistry::Delegate::Load, 155 &PairingRegistry::DoDelete, this, client_id, wrapped_callback);
124 base::Unretained(delegate_.get()), load_callback);
125 ServiceOrQueueRequest(request); 156 ServiceOrQueueRequest(request);
126 } 157 }
127 158
128 void PairingRegistry::ClearAllPairings( 159 void PairingRegistry::ClearAllPairings(
129 const SaveCallback& callback) { 160 const DoneCallback& callback) {
130 DCHECK(CalledOnValidThread()); 161 DCHECK(caller_task_runner_->BelongsToCurrentThread());
131 SaveCallback wrapped_callback = base::Bind( 162
132 &PairingRegistry::InvokeSaveCallbackAndScheduleNext, 163 DoneCallback wrapped_callback = base::Bind(
164 &PairingRegistry::InvokeDoneCallbackAndScheduleNext,
133 this, callback); 165 this, callback);
134 base::Closure request = base::Bind( 166 base::Closure request = base::Bind(
135 &PairingRegistry::Delegate::Save, 167 &PairingRegistry::DoDeleteAll, this, wrapped_callback);
136 base::Unretained(delegate_.get()),
137 EncodeJson(PairedClients()),
138 wrapped_callback);
139 ServiceOrQueueRequest(request); 168 ServiceOrQueueRequest(request);
140 } 169 }
141 170
171 PairingRegistry::~PairingRegistry() {
172 }
173
174 void PairingRegistry::PostTask(
175 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
176 const tracked_objects::Location& from_here,
177 const base::Closure& task) {
178 task_runner->PostTask(from_here, task);
179 }
180
142 void PairingRegistry::AddPairing(const Pairing& pairing) { 181 void PairingRegistry::AddPairing(const Pairing& pairing) {
143 SaveCallback callback = base::Bind( 182 DoneCallback wrapped_callback = base::Bind(
144 &PairingRegistry::InvokeSaveCallbackAndScheduleNext, 183 &PairingRegistry::InvokeDoneCallbackAndScheduleNext,
145 this, SaveCallback()); 184 this, DoneCallback());
146 LoadCallback load_callback = base::Bind(
147 &PairingRegistry::MergePairingAndSave, this, pairing, callback);
148 base::Closure request = base::Bind( 185 base::Closure request = base::Bind(
149 &PairingRegistry::Delegate::Load, 186 &PairingRegistry::DoSave, this, pairing, wrapped_callback);
150 base::Unretained(delegate_.get()), load_callback);
151 ServiceOrQueueRequest(request); 187 ServiceOrQueueRequest(request);
152 } 188 }
153 189
154 void PairingRegistry::MergePairingAndSave(const Pairing& pairing, 190 void PairingRegistry::DoLoadAll(
155 const SaveCallback& callback, 191 const protocol::PairingRegistry::GetAllPairingsCallback& callback) {
156 const std::string& pairings_json) { 192 DCHECK(delegate_task_runner_->BelongsToCurrentThread());
157 DCHECK(CalledOnValidThread()); 193
158 PairedClients clients = DecodeJson(pairings_json); 194 scoped_ptr<base::ListValue> pairings = delegate_->LoadAll();
159 clients[pairing.client_id()] = pairing; 195 PostTask(caller_task_runner_, FROM_HERE, base::Bind(callback,
160 std::string new_pairings_json = EncodeJson(clients); 196 base::Passed(&pairings)));
161 delegate_->Save(new_pairings_json, callback);
162 } 197 }
163 198
164 void PairingRegistry::DoGetPairing(const std::string& client_id, 199 void PairingRegistry::DoDeleteAll(
165 const GetPairingCallback& callback, 200 const protocol::PairingRegistry::DoneCallback& callback) {
166 const std::string& pairings_json) { 201 DCHECK(delegate_task_runner_->BelongsToCurrentThread());
167 PairedClients clients = DecodeJson(pairings_json); 202
168 Pairing result = clients[client_id]; 203 bool success = delegate_->DeleteAll();
169 callback.Run(result); 204 PostTask(caller_task_runner_, FROM_HERE, base::Bind(callback, success));
170 } 205 }
171 206
172 void PairingRegistry::SanitizePairings(const GetAllPairingsCallback& callback, 207 void PairingRegistry::DoLoad(
173 const std::string& pairings_json) { 208 const std::string& client_id,
174 PairedClients clients = DecodeJson(pairings_json); 209 const protocol::PairingRegistry::GetPairingCallback& callback) {
175 callback.Run(ConvertToListValue(clients, false)); 210 DCHECK(delegate_task_runner_->BelongsToCurrentThread());
211
212 Pairing pairing = delegate_->Load(client_id);
213 PostTask(caller_task_runner_, FROM_HERE, base::Bind(callback, pairing));
176 } 214 }
177 215
178 void PairingRegistry::DoDeletePairing(const std::string& client_id, 216 void PairingRegistry::DoSave(
179 const SaveCallback& callback, 217 const protocol::PairingRegistry::Pairing& pairing,
180 const std::string& pairings_json) { 218 const protocol::PairingRegistry::DoneCallback& callback) {
181 PairedClients clients = DecodeJson(pairings_json); 219 DCHECK(delegate_task_runner_->BelongsToCurrentThread());
182 clients.erase(client_id); 220
183 std::string new_pairings_json = EncodeJson(clients); 221 bool success = delegate_->Save(pairing);
184 delegate_->Save(new_pairings_json, callback); 222 PostTask(caller_task_runner_, FROM_HERE, base::Bind(callback, success));
185 } 223 }
186 224
187 void PairingRegistry::InvokeLoadCallbackAndScheduleNext( 225 void PairingRegistry::DoDelete(
188 const LoadCallback& callback, const std::string& pairings_json) { 226 const std::string& client_id,
189 callback.Run(pairings_json); 227 const protocol::PairingRegistry::DoneCallback& callback) {
228 DCHECK(delegate_task_runner_->BelongsToCurrentThread());
229
230 bool success = delegate_->Delete(client_id);
231 PostTask(caller_task_runner_, FROM_HERE, base::Bind(callback, success));
232 }
233
234 void PairingRegistry::InvokeDoneCallbackAndScheduleNext(
235 const DoneCallback& callback, bool success) {
236 // CreatePairing doesn't have a callback, so the callback can be null.
237 if (!callback.is_null())
238 callback.Run(success);
239
190 pending_requests_.pop(); 240 pending_requests_.pop();
191 ServiceNextRequest(); 241 ServiceNextRequest();
192 } 242 }
193
194 void PairingRegistry::InvokeSaveCallbackAndScheduleNext(
195 const SaveCallback& callback, bool success) {
196 // CreatePairing doesn't have a callback, so the callback can be null.
197 if (!callback.is_null()) {
198 callback.Run(success);
199 }
200 pending_requests_.pop();
201 ServiceNextRequest();
202 }
203 243
204 void PairingRegistry::InvokeGetPairingCallbackAndScheduleNext( 244 void PairingRegistry::InvokeGetPairingCallbackAndScheduleNext(
205 const GetPairingCallback& callback, Pairing pairing) { 245 const GetPairingCallback& callback, Pairing pairing) {
206 callback.Run(pairing); 246 callback.Run(pairing);
207 pending_requests_.pop(); 247 pending_requests_.pop();
208 ServiceNextRequest(); 248 ServiceNextRequest();
209 } 249 }
210 250
211 void PairingRegistry::InvokeGetAllPairingsCallbackAndScheduleNext( 251 void PairingRegistry::InvokeGetAllPairingsCallbackAndScheduleNext(
212 const GetAllPairingsCallback& callback, 252 const GetAllPairingsCallback& callback,
213 scoped_ptr<base::ListValue> pairings) { 253 scoped_ptr<base::ListValue> pairings) {
214 callback.Run(pairings.Pass()); 254 callback.Run(pairings.Pass());
215 pending_requests_.pop(); 255 pending_requests_.pop();
216 ServiceNextRequest(); 256 ServiceNextRequest();
217 } 257 }
218 258
219 // static 259 void PairingRegistry::SanitizePairings(const GetAllPairingsCallback& callback,
220 PairingRegistry::PairedClients PairingRegistry::DecodeJson( 260 scoped_ptr<base::ListValue> pairings) {
221 const std::string& pairings_json) { 261 DCHECK(caller_task_runner_->BelongsToCurrentThread());
222 PairedClients result;
223 262
224 if (pairings_json.empty()) { 263 scoped_ptr<base::ListValue> sanitized_pairings(new base::ListValue());
225 return result; 264 for (size_t i = 0; i < pairings->GetSize(); ++i) {
265 DictionaryValue* pairing_json;
266 if (!pairings->GetDictionary(i, &pairing_json)) {
267 LOG(WARNING) << "A pairing entry is not a disctionary.";
Jamie 2013/08/01 22:44:04 s/disctionary/dictionary/
alexeypa (please no reviews) 2013/08/01 23:07:42 Fixed by patch #4
268 continue;
269 }
270
271 // Parse the pairing data.
272 protocol::PairingRegistry::Pairing pairing =
273 protocol::PairingRegistry::Pairing::CreateFromValue(*pairing_json);
274 if (!pairing.is_valid()) {
275 LOG(WARNING) << "Could not parse a pairing entry.";
276 continue;
277 }
278
279 // Clear the shared secrect and append the pairing data to the list.
280 protocol::PairingRegistry::Pairing sanitized_pairing(
281 pairing.created_time(),
282 pairing.client_name(),
283 pairing.client_id(),
284 "");
285 sanitized_pairings->Append(sanitized_pairing.ToValue().release());
226 } 286 }
227 287
228 JSONStringValueSerializer registry(pairings_json); 288 callback.Run(sanitized_pairings.Pass());
229 int error_code;
230 std::string error_message;
231 scoped_ptr<base::Value> root(
232 registry.Deserialize(&error_code, &error_message));
233 if (!root) {
234 LOG(ERROR) << "Failed to load paired clients: " << error_message
235 << " (" << error_code << ").";
236 return result;
237 }
238
239 base::ListValue* root_list = NULL;
240 if (!root->GetAsList(&root_list)) {
241 LOG(ERROR) << "Failed to load paired clients: root node is not a list.";
242 return result;
243 }
244
245 for (size_t i = 0; i < root_list->GetSize(); ++i) {
246 base::DictionaryValue* pairing = NULL;
247 std::string client_name, client_id, shared_secret;
248 double created_time_value;
249 if (root_list->GetDictionary(i, &pairing) &&
250 pairing->GetDouble(kCreatedTimeKey, &created_time_value) &&
251 pairing->GetString(kClientNameKey, &client_name) &&
252 pairing->GetString(kClientIdKey, &client_id) &&
253 pairing->GetString(kSharedSecretKey, &shared_secret)) {
254 base::Time created_time = base::Time::FromJsTime(created_time_value);
255 result[client_id] = Pairing(
256 created_time, client_name, client_id, shared_secret);
257 } else {
258 LOG(ERROR) << "Paired client " << i << " has unexpected format.";
259 }
260 }
261
262 return result;
263 } 289 }
264 290
265 void PairingRegistry::ServiceOrQueueRequest(const base::Closure& request) { 291 void PairingRegistry::ServiceOrQueueRequest(const base::Closure& request) {
266 bool servicing_request = !pending_requests_.empty(); 292 bool servicing_request = !pending_requests_.empty();
267 pending_requests_.push(request); 293 pending_requests_.push(request);
268 if (!servicing_request) { 294 if (!servicing_request) {
269 ServiceNextRequest(); 295 ServiceNextRequest();
270 } 296 }
271 } 297 }
272 298
273 void PairingRegistry::ServiceNextRequest() { 299 void PairingRegistry::ServiceNextRequest() {
274 if (pending_requests_.empty()) { 300 if (pending_requests_.empty())
275 return; 301 return;
276 }
277 base::Closure request = pending_requests_.front();
278 request.Run();
279 }
280 302
281 // static 303 PostTask(delegate_task_runner_, FROM_HERE, pending_requests_.front());
282 std::string PairingRegistry::EncodeJson(const PairedClients& clients) {
283 scoped_ptr<base::ListValue> root = ConvertToListValue(clients, true);
284 std::string result;
285 JSONStringValueSerializer serializer(&result);
286 serializer.Serialize(*root);
287
288 return result;
289 }
290
291 // static
292 scoped_ptr<base::ListValue> PairingRegistry::ConvertToListValue(
293 const PairedClients& clients,
294 bool include_shared_secrets) {
295 scoped_ptr<base::ListValue> root(new base::ListValue());
296 for (PairedClients::const_iterator i = clients.begin();
297 i != clients.end(); ++i) {
298 base::DictionaryValue* pairing = new base::DictionaryValue();
299 pairing->SetDouble(kCreatedTimeKey, i->second.created_time().ToJsTime());
300 pairing->SetString(kClientNameKey, i->second.client_name());
301 pairing->SetString(kClientIdKey, i->second.client_id());
302 if (include_shared_secrets) {
303 pairing->SetString(kSharedSecretKey, i->second.shared_secret());
304 }
305 root->Append(pairing);
306 }
307 return root.Pass();
308 } 304 }
309 305
310 } // namespace protocol 306 } // namespace protocol
311 } // namespace remoting 307 } // namespace remoting
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698