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

Side by Side Diff: remoting/host/chromoting_host_unittest.cc

Issue 8725016: Refactor IT2Me-specific functions into a HostObserver subclass. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix other nits. Created 9 years 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 (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 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 "base/bind.h" 5 #include "base/bind.h"
6 #include "base/memory/scoped_ptr.h" 6 #include "base/memory/scoped_ptr.h"
7 #include "base/message_loop_proxy.h" 7 #include "base/message_loop_proxy.h"
8 #include "base/task.h" 8 #include "base/task.h"
9 #include "remoting/host/capturer_fake.h" 9 #include "remoting/host/capturer_fake.h"
10 #include "remoting/host/chromoting_host.h" 10 #include "remoting/host/chromoting_host.h"
11 #include "remoting/host/chromoting_host_context.h" 11 #include "remoting/host/chromoting_host_context.h"
12 #include "remoting/host/host_mock_objects.h" 12 #include "remoting/host/host_mock_objects.h"
13 #include "remoting/host/in_memory_host_config.h" 13 #include "remoting/host/in_memory_host_config.h"
14 #include "remoting/host/it2me_host_user_interface.h"
14 #include "remoting/proto/video.pb.h" 15 #include "remoting/proto/video.pb.h"
15 #include "remoting/protocol/protocol_mock_objects.h" 16 #include "remoting/protocol/protocol_mock_objects.h"
16 #include "remoting/protocol/session_config.h" 17 #include "remoting/protocol/session_config.h"
17 #include "testing/gmock_mutant.h" 18 #include "testing/gmock_mutant.h"
18 #include "testing/gmock/include/gmock/gmock.h" 19 #include "testing/gmock/include/gmock/gmock.h"
19 #include "testing/gtest/include/gtest/gtest.h" 20 #include "testing/gtest/include/gtest/gtest.h"
20 21
21 using ::remoting::protocol::MockClientStub; 22 using ::remoting::protocol::MockClientStub;
22 using ::remoting::protocol::MockConnectionToClient; 23 using ::remoting::protocol::MockConnectionToClient;
23 using ::remoting::protocol::MockConnectionToClientEventHandler; 24 using ::remoting::protocol::MockConnectionToClientEventHandler;
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
82 .Times(AnyNumber()); 83 .Times(AnyNumber());
83 EXPECT_CALL(context_, encode_message_loop()) 84 EXPECT_CALL(context_, encode_message_loop())
84 .Times(AnyNumber()); 85 .Times(AnyNumber());
85 EXPECT_CALL(context_, network_message_loop()) 86 EXPECT_CALL(context_, network_message_loop())
86 .Times(AnyNumber()); 87 .Times(AnyNumber());
87 EXPECT_CALL(context_, ui_message_loop()) 88 EXPECT_CALL(context_, ui_message_loop())
88 .Times(AnyNumber()); 89 .Times(AnyNumber());
89 90
90 Capturer* capturer = new CapturerFake(); 91 Capturer* capturer = new CapturerFake();
91 event_executor_ = new MockEventExecutor(); 92 event_executor_ = new MockEventExecutor();
92 curtain_ = new MockCurtain();
93 disconnect_window_ = new MockDisconnectWindow();
94 continue_window_ = new MockContinueWindow();
95 local_input_monitor_ = new MockLocalInputMonitor();
96 desktop_environment_.reset( 93 desktop_environment_.reset(
97 new DesktopEnvironment(&context_, capturer, event_executor_, curtain_, 94 new DesktopEnvironment(&context_, capturer, event_executor_));
98 disconnect_window_, continue_window_,
99 local_input_monitor_));
100 MockAccessVerifier* access_verifier = new MockAccessVerifier(); 95 MockAccessVerifier* access_verifier = new MockAccessVerifier();
101 96
102 host_ = ChromotingHost::Create(&context_, config_, 97 host_ = ChromotingHost::Create(&context_, config_,
103 desktop_environment_.get(), 98 desktop_environment_.get(),
104 access_verifier, false); 99 access_verifier, false);
100
101 disconnect_window_ = new MockDisconnectWindow();
102 continue_window_ = new MockContinueWindow();
103 local_input_monitor_ = new MockLocalInputMonitor();
104 it2me_host_user_interface_.reset(new It2MeHostUserInterface(host_,
105 &context_));
106 it2me_host_user_interface_->InitFrom(disconnect_window_, continue_window_,
107 local_input_monitor_);
108 host_->AddStatusObserver(it2me_host_user_interface_.get());
109
105 session_ = new MockSession(); 110 session_ = new MockSession();
106 session2_ = new MockSession(); 111 session2_ = new MockSession();
107 session_config_ = SessionConfig::GetDefault(); 112 session_config_ = SessionConfig::GetDefault();
108 session_jid_ = "user@domain/rest-of-jid"; 113 session_jid_ = "user@domain/rest-of-jid";
109 session_config2_ = SessionConfig::GetDefault(); 114 session_config2_ = SessionConfig::GetDefault();
110 session2_jid_ = "user2@domain/rest-of-jid"; 115 session2_jid_ = "user2@domain/rest-of-jid";
111 EXPECT_CALL(*session_, jid()) 116 EXPECT_CALL(*session_, jid())
112 .WillRepeatedly(ReturnRef(session_jid_)); 117 .WillRepeatedly(ReturnRef(session_jid_));
113 EXPECT_CALL(*session2_, jid()) 118 EXPECT_CALL(*session2_, jid())
114 .WillRepeatedly(ReturnRef(session2_jid_)); 119 .WillRepeatedly(ReturnRef(session2_jid_));
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after
210 message_loop_.PostTask( 215 message_loop_.PostTask(
211 FROM_HERE, base::Bind(&ChromotingHost::Shutdown, host_, 216 FROM_HERE, base::Bind(&ChromotingHost::Shutdown, host_,
212 base::Bind(&PostQuitTask, &message_loop_))); 217 base::Bind(&PostQuitTask, &message_loop_)));
213 } 218 }
214 219
215 protected: 220 protected:
216 MessageLoop message_loop_; 221 MessageLoop message_loop_;
217 scoped_refptr<base::MessageLoopProxy> message_loop_proxy_; 222 scoped_refptr<base::MessageLoopProxy> message_loop_proxy_;
218 MockConnectionToClientEventHandler handler_; 223 MockConnectionToClientEventHandler handler_;
219 scoped_ptr<DesktopEnvironment> desktop_environment_; 224 scoped_ptr<DesktopEnvironment> desktop_environment_;
225 scoped_ptr<It2MeHostUserInterface> it2me_host_user_interface_;
220 scoped_refptr<ChromotingHost> host_; 226 scoped_refptr<ChromotingHost> host_;
221 scoped_refptr<InMemoryHostConfig> config_; 227 scoped_refptr<InMemoryHostConfig> config_;
222 MockChromotingHostContext context_; 228 MockChromotingHostContext context_;
223 MockConnectionToClient* connection_; 229 MockConnectionToClient* connection_;
224 scoped_ptr<MockConnectionToClient> owned_connection_; 230 scoped_ptr<MockConnectionToClient> owned_connection_;
225 ClientSession* client_; 231 ClientSession* client_;
226 std::string session_jid_; 232 std::string session_jid_;
227 MockSession* session_; // Owned by |connection_|. 233 MockSession* session_; // Owned by |connection_|.
228 SessionConfig session_config_; 234 SessionConfig session_config_;
229 MockVideoStub video_stub_; 235 MockVideoStub video_stub_;
230 MockClientStub client_stub_; 236 MockClientStub client_stub_;
231 MockHostStub host_stub_; 237 MockHostStub host_stub_;
232 MockConnectionToClient* connection2_; 238 MockConnectionToClient* connection2_;
233 scoped_ptr<MockConnectionToClient> owned_connection2_; 239 scoped_ptr<MockConnectionToClient> owned_connection2_;
234 ClientSession* client2_; 240 ClientSession* client2_;
235 std::string session2_jid_; 241 std::string session2_jid_;
236 MockSession* session2_; // Owned by |connection2_|. 242 MockSession* session2_; // Owned by |connection2_|.
237 SessionConfig session_config2_; 243 SessionConfig session_config2_;
238 MockVideoStub video_stub2_; 244 MockVideoStub video_stub2_;
239 MockClientStub client_stub2_; 245 MockClientStub client_stub2_;
240 MockHostStub host_stub2_; 246 MockHostStub host_stub2_;
241 MockEventExecutor event_executor2_; 247 MockEventExecutor event_executor2_;
242 248
243 // Owned by |host_|. 249 // Owned by |host_|.
244 MockEventExecutor* event_executor_; 250 MockEventExecutor* event_executor_;
245 MockCurtain* curtain_;
246 MockDisconnectWindow* disconnect_window_; 251 MockDisconnectWindow* disconnect_window_;
247 MockContinueWindow* continue_window_; 252 MockContinueWindow* continue_window_;
248 MockLocalInputMonitor* local_input_monitor_; 253 MockLocalInputMonitor* local_input_monitor_;
249 }; 254 };
250 255
251 TEST_F(ChromotingHostTest, DISABLED_StartAndShutdown) { 256 TEST_F(ChromotingHostTest, DISABLED_StartAndShutdown) {
252 host_->Start(); 257 host_->Start();
253 258
254 message_loop_.PostTask( 259 message_loop_.PostTask(
255 FROM_HERE, base::Bind( 260 FROM_HERE, base::Bind(
256 &ChromotingHost::Shutdown, host_.get(), 261 &ChromotingHost::Shutdown, host_.get(),
257 base::Bind(&PostQuitTask, &message_loop_))); 262 base::Bind(&PostQuitTask, &message_loop_)));
258 message_loop_.Run(); 263 message_loop_.Run();
259 } 264 }
260 265
261 TEST_F(ChromotingHostTest, DISABLED_Connect) { 266 TEST_F(ChromotingHostTest, DISABLED_Connect) {
262 host_->Start(); 267 host_->Start();
263 268
264 // When the video packet is received we first shutdown ChromotingHost 269 // When the video packet is received we first shutdown ChromotingHost
265 // then execute the done task. 270 // then execute the done task.
266 { 271 {
267 InSequence s; 272 InSequence s;
268 EXPECT_CALL(*curtain_, EnableCurtainMode(true))
269 .Times(1);
270 EXPECT_CALL(*disconnect_window_, Show(_, _)) 273 EXPECT_CALL(*disconnect_window_, Show(_, _))
271 .Times(0); 274 .Times(0);
272 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) 275 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _))
273 .WillOnce(DoAll( 276 .WillOnce(DoAll(
274 InvokeWithoutArgs(this, &ChromotingHostTest::ShutdownHost), 277 InvokeWithoutArgs(this, &ChromotingHostTest::ShutdownHost),
275 RunDoneTask())) 278 RunDoneTask()))
276 .RetiresOnSaturation(); 279 .RetiresOnSaturation();
277 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) 280 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _))
278 .Times(AnyNumber()); 281 .Times(AnyNumber());
279 EXPECT_CALL(*connection_, Disconnect()) 282 EXPECT_CALL(*connection_, Disconnect())
280 .RetiresOnSaturation(); 283 .RetiresOnSaturation();
281 } 284 }
282 SimulateClientConnection(0, true); 285 SimulateClientConnection(0, true);
283 message_loop_.Run(); 286 message_loop_.Run();
284 } 287 }
285 288
286 TEST_F(ChromotingHostTest, DISABLED_Reconnect) { 289 TEST_F(ChromotingHostTest, DISABLED_Reconnect) {
287 host_->Start(); 290 host_->Start();
288 291
289 // When the video packet is received we first disconnect the mock 292 // When the video packet is received we first disconnect the mock
290 // connection. 293 // connection.
291 { 294 {
292 InSequence s; 295 InSequence s;
293 // Ensure that curtain mode is activated before the first video packet.
294 EXPECT_CALL(*curtain_, EnableCurtainMode(true))
295 .Times(1);
296 EXPECT_CALL(*disconnect_window_, Show(_, _)) 296 EXPECT_CALL(*disconnect_window_, Show(_, _))
297 .Times(0); 297 .Times(0);
298 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) 298 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _))
299 .WillOnce(DoAll( 299 .WillOnce(DoAll(
300 InvokeWithoutArgs(this, &ChromotingHostTest::RemoveClientSession), 300 InvokeWithoutArgs(this, &ChromotingHostTest::RemoveClientSession),
301 RunDoneTask())) 301 RunDoneTask()))
302 .RetiresOnSaturation(); 302 .RetiresOnSaturation();
303 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) 303 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _))
304 .Times(AnyNumber()); 304 .Times(AnyNumber());
305 EXPECT_CALL(*curtain_, EnableCurtainMode(false))
306 .Times(1);
307 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) 305 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _))
308 .Times(AnyNumber()); 306 .Times(AnyNumber());
309 } 307 }
310 308
311 // If Disconnect() is called we can break the main message loop. 309 // If Disconnect() is called we can break the main message loop.
312 EXPECT_CALL(*connection_, Disconnect()) 310 EXPECT_CALL(*connection_, Disconnect())
313 .WillOnce(QuitMainMessageLoop(&message_loop_)) 311 .WillOnce(QuitMainMessageLoop(&message_loop_))
314 .RetiresOnSaturation(); 312 .RetiresOnSaturation();
315 313
316 SimulateClientConnection(0, true); 314 SimulateClientConnection(0, true);
317 message_loop_.Run(); 315 message_loop_.Run();
318 316
319 // Connect the client again. 317 // Connect the client again.
320 { 318 {
321 InSequence s; 319 InSequence s;
322 EXPECT_CALL(*curtain_, EnableCurtainMode(true))
323 .Times(1);
324 EXPECT_CALL(*disconnect_window_, Show(_, _)) 320 EXPECT_CALL(*disconnect_window_, Show(_, _))
325 .Times(0); 321 .Times(0);
326 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) 322 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _))
327 .WillOnce(DoAll( 323 .WillOnce(DoAll(
328 InvokeWithoutArgs(this, &ChromotingHostTest::ShutdownHost), 324 InvokeWithoutArgs(this, &ChromotingHostTest::ShutdownHost),
329 RunDoneTask())) 325 RunDoneTask()))
330 .RetiresOnSaturation(); 326 .RetiresOnSaturation();
331 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) 327 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _))
332 .Times(AnyNumber()); 328 .Times(AnyNumber());
333 } 329 }
334 330
335 EXPECT_CALL(*connection_, Disconnect()) 331 EXPECT_CALL(*connection_, Disconnect())
336 .RetiresOnSaturation(); 332 .RetiresOnSaturation();
337 333
338 SimulateClientConnection(0, true); 334 SimulateClientConnection(0, true);
339 message_loop_.Run(); 335 message_loop_.Run();
340 } 336 }
341 337
342 TEST_F(ChromotingHostTest, DISABLED_ConnectTwice) { 338 TEST_F(ChromotingHostTest, DISABLED_ConnectTwice) {
343 host_->Start(); 339 host_->Start();
344 340
345 // When a video packet is received we connect the second mock 341 // When a video packet is received we connect the second mock
346 // connection. 342 // connection.
347 { 343 {
348 InSequence s; 344 InSequence s;
349 EXPECT_CALL(*curtain_, EnableCurtainMode(true))
350 .Times(1)
351 .WillOnce(QuitMainMessageLoop(&message_loop_));
352 EXPECT_CALL(*disconnect_window_, Show(_, _)) 345 EXPECT_CALL(*disconnect_window_, Show(_, _))
353 .Times(0); 346 .Times(0);
354 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) 347 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _))
355 .WillOnce(DoAll( 348 .WillOnce(DoAll(
356 InvokeWithoutArgs( 349 InvokeWithoutArgs(
357 CreateFunctor( 350 CreateFunctor(
358 this, 351 this,
359 &ChromotingHostTest::SimulateClientConnection, 1, true)), 352 &ChromotingHostTest::SimulateClientConnection, 1, true)),
360 RunDoneTask())) 353 RunDoneTask()))
361 .RetiresOnSaturation(); 354 .RetiresOnSaturation();
362 // Check that the second connection does not affect curtain mode.
363 EXPECT_CALL(*curtain_, EnableCurtainMode(_))
364 .Times(0);
365 EXPECT_CALL(*disconnect_window_, Show(_, _)) 355 EXPECT_CALL(*disconnect_window_, Show(_, _))
366 .Times(0); 356 .Times(0);
367 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) 357 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _))
368 .Times(AnyNumber()); 358 .Times(AnyNumber());
369 EXPECT_CALL(video_stub2_, ProcessVideoPacket(_, _)) 359 EXPECT_CALL(video_stub2_, ProcessVideoPacket(_, _))
370 .WillOnce(DoAll( 360 .WillOnce(DoAll(
371 InvokeWithoutArgs(this, &ChromotingHostTest::ShutdownHost), 361 InvokeWithoutArgs(this, &ChromotingHostTest::ShutdownHost),
372 RunDoneTask())) 362 RunDoneTask()))
373 .RetiresOnSaturation(); 363 .RetiresOnSaturation();
374 EXPECT_CALL(video_stub2_, ProcessVideoPacket(_, _)) 364 EXPECT_CALL(video_stub2_, ProcessVideoPacket(_, _))
375 .Times(AnyNumber()); 365 .Times(AnyNumber());
376 } 366 }
377 367
378 EXPECT_CALL(*connection_, Disconnect()) 368 EXPECT_CALL(*connection_, Disconnect())
379 .RetiresOnSaturation(); 369 .RetiresOnSaturation();
380 EXPECT_CALL(*connection2_, Disconnect()) 370 EXPECT_CALL(*connection2_, Disconnect())
381 .RetiresOnSaturation(); 371 .RetiresOnSaturation();
382 372
383 SimulateClientConnection(0, true); 373 SimulateClientConnection(0, true);
384 message_loop_.Run(); 374 message_loop_.Run();
385 } 375 }
386 376
387 TEST_F(ChromotingHostTest, CurtainModeFail) {
388 host_->Start();
389
390 // Ensure that curtain mode is not activated if a connection does not
391 // authenticate.
392 EXPECT_CALL(*curtain_, EnableCurtainMode(_))
393 .Times(0);
394 EXPECT_CALL(*disconnect_window_, Show(_, _))
395 .Times(0);
396 EXPECT_CALL(*continue_window_, Hide())
397 .Times(AnyNumber());
398 EXPECT_CALL(*disconnect_window_, Hide())
399 .Times(AnyNumber());
400 SimulateClientConnection(0, false);
401 context_.network_message_loop()->PostTask(
402 FROM_HERE, base::Bind(&ChromotingHostTest::RemoveClientSession,
403 base::Unretained(this)));
404 PostQuitTask(&message_loop_);
405 message_loop_.Run();
406 }
407
408 TEST_F(ChromotingHostTest, CurtainModeFailSecond) {
409 host_->Start();
410
411 // When a video packet is received we connect the second mock
412 // connection.
413 {
414 InSequence s;
415 EXPECT_CALL(*curtain_, EnableCurtainMode(true))
416 .WillOnce(QuitMainMessageLoop(&message_loop_));
417 EXPECT_CALL(*local_input_monitor_, Start(_))
418 .Times(1);
419 EXPECT_CALL(*disconnect_window_, Show(_, "user@domain"))
420 .Times(1);
421 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _))
422 .WillOnce(DoAll(
423 InvokeWithoutArgs(
424 CreateFunctor(
425 this,
426 &ChromotingHostTest::SimulateClientConnection, 1, false)),
427 RunDoneTask()))
428 .RetiresOnSaturation();
429 // Check that the second connection does not affect curtain mode.
430 EXPECT_CALL(*curtain_, EnableCurtainMode(_))
431 .Times(0);
432 EXPECT_CALL(*disconnect_window_, Show(_, _))
433 .Times(0);
434 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _))
435 .Times(AnyNumber());
436 EXPECT_CALL(video_stub2_, ProcessVideoPacket(_, _))
437 .Times(0);
438 }
439
440 SimulateClientConnection(0, true);
441 message_loop_.Run();
442
443 // Curtain is removed when there are no clients connected.
444 EXPECT_CALL(*curtain_, EnableCurtainMode(false))
445 .Times(AtLeast(1));
446 EXPECT_CALL(*continue_window_, Hide())
447 .Times(AtLeast(1));
448 EXPECT_CALL(*disconnect_window_, Hide())
449 .Times(AtLeast(1));
450 EXPECT_CALL(*local_input_monitor_, Stop())
451 .Times(AtLeast(1));
452
453 // Close connections before destroying the host.
454 client_->OnConnectionClosed(connection_);
455 client2_->OnConnectionClosed(connection2_);
456 }
457
458 ACTION_P(SetBool, var) { *var = true; }
459
460 TEST_F(ChromotingHostTest, CurtainModeIT2Me) {
461 host_->Start();
462 host_->set_it2me(true);
463
464 // When the video packet is received we first shutdown ChromotingHost
465 // then execute the done task.
466 bool curtain_activated = false;
467 {
468 Sequence s1, s2;
469 // Can't just expect Times(0) because if it fails then the host will
470 // not be shut down and the message loop will never exit.
471 EXPECT_CALL(*curtain_, EnableCurtainMode(_))
472 .Times(AnyNumber())
473 .WillRepeatedly(SetBool(&curtain_activated));
474 EXPECT_CALL(*disconnect_window_, Show(_, "user@domain"))
475 .Times(1)
476 .InSequence(s1);
477 EXPECT_CALL(*local_input_monitor_, Start(_))
478 .Times(1)
479 .InSequence(s2);
480 EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _))
481 .InSequence(s1, s2)
482 .WillOnce(DoAll(
483 InvokeWithoutArgs(this, &ChromotingHostTest::ShutdownHost),
484 RunDoneTask()))
485 .RetiresOnSaturation();
486 EXPECT_CALL(*connection_, Disconnect())
487 .InSequence(s1, s2)
488 .WillOnce(InvokeWithoutArgs(
489 this, &ChromotingHostTest::RemoveClientSession))
490 .RetiresOnSaturation();
491 EXPECT_CALL(*local_input_monitor_, Stop())
492 .Times(1)
493 .InSequence(s1, s2);
494 EXPECT_CALL(*continue_window_, Hide())
495 .Times(1)
496 .InSequence(s1);
497 EXPECT_CALL(*disconnect_window_, Hide())
498 .Times(1)
499 .InSequence(s2);
500 }
501 SimulateClientConnection(0, true);
502 message_loop_.Run();
503 host_->set_it2me(false);
504 EXPECT_THAT(curtain_activated, false);
505 }
506
507 } // namespace remoting 377 } // namespace remoting
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698