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

Side by Side Diff: webrtc/p2p/base/faketransportcontroller.h

Issue 1246913005: TransportController refactoring (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: minor cleanup Created 5 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
OLDNEW
1 /* 1 /*
2 * Copyright 2009 The WebRTC Project Authors. All rights reserved. 2 * Copyright 2009 The WebRTC Project Authors. All rights reserved.
3 * 3 *
4 * Use of this source code is governed by a BSD-style license 4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source 5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found 6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may 7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree. 8 * be found in the AUTHORS file in the root of the source tree.
9 */ 9 */
10 10
11 #ifndef WEBRTC_P2P_BASE_FAKESESSION_H_ 11 #ifndef WEBRTC_P2P_BASE_FAKETRANSPORTCONTROLLER_H_
12 #define WEBRTC_P2P_BASE_FAKESESSION_H_ 12 #define WEBRTC_P2P_BASE_FAKETRANSPORTCONTROLLER_H_
13 13
14 #include <map> 14 #include <map>
15 #include <string> 15 #include <string>
16 #include <vector> 16 #include <vector>
17 17
18 #include "webrtc/p2p/base/session.h"
19 #include "webrtc/p2p/base/transport.h" 18 #include "webrtc/p2p/base/transport.h"
20 #include "webrtc/p2p/base/transportchannel.h" 19 #include "webrtc/p2p/base/transportchannel.h"
20 #include "webrtc/p2p/base/transportcontroller.h"
21 #include "webrtc/p2p/base/transportchannelimpl.h" 21 #include "webrtc/p2p/base/transportchannelimpl.h"
22 #include "webrtc/base/bind.h"
22 #include "webrtc/base/buffer.h" 23 #include "webrtc/base/buffer.h"
23 #include "webrtc/base/fakesslidentity.h" 24 #include "webrtc/base/fakesslidentity.h"
24 #include "webrtc/base/messagequeue.h" 25 #include "webrtc/base/messagequeue.h"
25 #include "webrtc/base/sigslot.h" 26 #include "webrtc/base/sigslot.h"
26 #include "webrtc/base/sslfingerprint.h" 27 #include "webrtc/base/sslfingerprint.h"
28 #include "webrtc/base/thread.h"
27 29
28 namespace cricket { 30 namespace cricket {
29 31
30 class FakeTransport; 32 class FakeTransport;
31 33
32 struct PacketMessageData : public rtc::MessageData { 34 struct PacketMessageData : public rtc::MessageData {
33 PacketMessageData(const char* data, size_t len) : packet(data, len) { 35 PacketMessageData(const char* data, size_t len) : packet(data, len) {
34 } 36 }
35 rtc::Buffer packet; 37 rtc::Buffer packet;
36 }; 38 };
37 39
38 // Fake transport channel class, which can be passed to anything that needs a 40 // Fake transport channel class, which can be passed to anything that needs a
39 // transport channel. Can be informed of another FakeTransportChannel via 41 // transport channel. Can be informed of another FakeTransportChannel via
40 // SetDestination. 42 // SetDestination.
41 class FakeTransportChannel : public TransportChannelImpl, 43 class FakeTransportChannel : public TransportChannelImpl,
42 public rtc::MessageHandler { 44 public rtc::MessageHandler {
43 public: 45 public:
44 explicit FakeTransportChannel(Transport* transport, 46 explicit FakeTransportChannel(Transport* transport,
45 const std::string& content_name, 47 const std::string& content_name,
46 int component) 48 int component)
47 : TransportChannelImpl(content_name, component), 49 : TransportChannelImpl(content_name, component),
48 transport_(transport), 50 transport_(transport),
49 dest_(NULL), 51 dest_(nullptr),
50 state_(STATE_INIT), 52 state_(STATE_INIT),
51 async_(false), 53 async_(false),
52 identity_(NULL), 54 identity_(nullptr),
53 do_dtls_(false), 55 do_dtls_(false),
54 role_(ICEROLE_UNKNOWN), 56 role_(ICEROLE_UNKNOWN),
55 tiebreaker_(0), 57 tiebreaker_(0),
56 ice_proto_(ICEPROTO_HYBRID), 58 ice_proto_(ICEPROTO_HYBRID),
57 remote_ice_mode_(ICEMODE_FULL), 59 remote_ice_mode_(ICEMODE_FULL),
58 dtls_fingerprint_("", NULL, 0), 60 dtls_fingerprint_("", nullptr, 0),
59 ssl_role_(rtc::SSL_CLIENT), 61 ssl_role_(rtc::SSL_CLIENT),
60 connection_count_(0) { 62 connection_count_(0),
63 candidate_gathering_state_(Transport::kGatheringNew) {
61 } 64 }
62 ~FakeTransportChannel() { 65 ~FakeTransportChannel() {
63 Reset(); 66 Reset();
64 } 67 }
65 68
66 uint64 IceTiebreaker() const { return tiebreaker_; } 69 uint64 IceTiebreaker() const { return tiebreaker_; }
67 TransportProtocol protocol() const { return ice_proto_; } 70 TransportProtocol protocol() const { return ice_proto_; }
68 IceMode remote_ice_mode() const { return remote_ice_mode_; } 71 IceMode remote_ice_mode() const { return remote_ice_mode_; }
69 const std::string& ice_ufrag() const { return ice_ufrag_; } 72 const std::string& ice_ufrag() const { return ice_ufrag_; }
70 const std::string& ice_pwd() const { return ice_pwd_; } 73 const std::string& ice_pwd() const { return ice_pwd_; }
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
126 virtual bool GetSslRole(rtc::SSLRole* role) const { 129 virtual bool GetSslRole(rtc::SSLRole* role) const {
127 *role = ssl_role_; 130 *role = ssl_role_;
128 return true; 131 return true;
129 } 132 }
130 133
131 virtual void Connect() { 134 virtual void Connect() {
132 if (state_ == STATE_INIT) { 135 if (state_ == STATE_INIT) {
133 state_ = STATE_CONNECTING; 136 state_ = STATE_CONNECTING;
134 } 137 }
135 } 138 }
139
140 virtual Transport::CandidateGatheringState candidate_gathering_state() const {
141 return candidate_gathering_state_;
142 }
143
136 virtual void Reset() { 144 virtual void Reset() {
137 if (state_ != STATE_INIT) { 145 if (state_ != STATE_INIT) {
138 state_ = STATE_INIT; 146 state_ = STATE_INIT;
139 if (dest_) { 147 if (dest_) {
140 dest_->state_ = STATE_INIT; 148 dest_->state_ = STATE_INIT;
141 dest_->dest_ = NULL; 149 dest_->dest_ = nullptr;
142 dest_ = NULL; 150 dest_ = nullptr;
143 } 151 }
144 } 152 }
145 } 153 }
146 154
147 void SetWritable(bool writable) { 155 void SetWritable(bool writable) {
148 set_writable(writable); 156 set_writable(writable);
149 } 157 }
150 158
151 void SetDestination(FakeTransportChannel* dest) { 159 void SetDestination(FakeTransportChannel* dest) {
152 if (state_ == STATE_CONNECTING && dest) { 160 if (state_ == STATE_CONNECTING && dest) {
153 // This simulates the delivery of candidates. 161 // This simulates the delivery of candidates.
154 dest_ = dest; 162 dest_ = dest;
155 dest_->dest_ = this; 163 dest_->dest_ = this;
156 if (identity_ && dest_->identity_) { 164 if (identity_ && dest_->identity_) {
157 do_dtls_ = true; 165 do_dtls_ = true;
158 dest_->do_dtls_ = true; 166 dest_->do_dtls_ = true;
159 NegotiateSrtpCiphers(); 167 NegotiateSrtpCiphers();
160 } 168 }
161 state_ = STATE_CONNECTED; 169 state_ = STATE_CONNECTED;
162 dest_->state_ = STATE_CONNECTED; 170 dest_->state_ = STATE_CONNECTED;
163 set_writable(true); 171 set_writable(true);
164 dest_->set_writable(true); 172 dest_->set_writable(true);
165 } else if (state_ == STATE_CONNECTED && !dest) { 173 } else if (state_ == STATE_CONNECTED && !dest) {
166 // Simulates loss of connectivity, by asymmetrically forgetting dest_. 174 // Simulates loss of connectivity, by asymmetrically forgetting dest_.
167 dest_ = NULL; 175 dest_ = nullptr;
168 state_ = STATE_CONNECTING; 176 state_ = STATE_CONNECTING;
169 set_writable(false); 177 set_writable(false);
170 } 178 }
171 } 179 }
172 180
173 void SetConnectionCount(size_t connection_count) { 181 void SetConnectionCount(size_t connection_count) {
174 size_t old_connection_count = connection_count_; 182 size_t old_connection_count = connection_count_;
175 connection_count_ = connection_count; 183 connection_count_ = connection_count;
176 if (connection_count_ < old_connection_count) 184 if (connection_count_ < old_connection_count)
177 SignalConnectionRemoved(this); 185 SignalConnectionRemoved(this);
178 } 186 }
179 187
188 void SetCandidatesAllocationDone() {
189 if (candidate_gathering_state_ != Transport::kGatheringDone) {
190 candidate_gathering_state_ = Transport::kGatheringDone;
191 SignalCandidatesAllocationDone(this);
192 }
193 }
194
180 void SetReceiving(bool receiving) { 195 void SetReceiving(bool receiving) {
181 set_receiving(receiving); 196 set_receiving(receiving);
182 } 197 }
183 198
184 void SetReceivingTimeout(int timeout) override {} 199 void SetReceivingTimeout(int timeout) override {}
185 200
186 virtual int SendPacket(const char* data, size_t len, 201 virtual int SendPacket(const char* data, size_t len,
187 const rtc::PacketOptions& options, int flags) { 202 const rtc::PacketOptions& options, int flags) {
188 if (state_ != STATE_CONNECTED) { 203 if (state_ != STATE_CONNECTED) {
189 return -1; 204 return -1;
(...skipping 14 matching lines...) Expand all
204 virtual int SetOption(rtc::Socket::Option opt, int value) { 219 virtual int SetOption(rtc::Socket::Option opt, int value) {
205 return true; 220 return true;
206 } 221 }
207 virtual bool GetOption(rtc::Socket::Option opt, int* value) { 222 virtual bool GetOption(rtc::Socket::Option opt, int* value) {
208 return true; 223 return true;
209 } 224 }
210 virtual int GetError() { 225 virtual int GetError() {
211 return 0; 226 return 0;
212 } 227 }
213 228
214 virtual void OnSignalingReady() {
215 }
216 virtual void OnCandidate(const Candidate& candidate) { 229 virtual void OnCandidate(const Candidate& candidate) {
217 } 230 }
218 231
219 virtual void OnMessage(rtc::Message* msg) { 232 virtual void OnMessage(rtc::Message* msg) {
220 PacketMessageData* data = static_cast<PacketMessageData*>( 233 PacketMessageData* data = static_cast<PacketMessageData*>(
221 msg->pdata); 234 msg->pdata);
222 dest_->SignalReadPacket(dest_, data->packet.data<char>(), 235 dest_->SignalReadPacket(dest_, data->packet.data<char>(),
223 data->packet.size(), rtc::CreatePacketTime(0), 0); 236 data->packet.size(), rtc::CreatePacketTime(0), 0);
224 delete data; 237 delete data;
225 } 238 }
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
322 uint64 tiebreaker_; 335 uint64 tiebreaker_;
323 IceProtocolType ice_proto_; 336 IceProtocolType ice_proto_;
324 std::string ice_ufrag_; 337 std::string ice_ufrag_;
325 std::string ice_pwd_; 338 std::string ice_pwd_;
326 std::string remote_ice_ufrag_; 339 std::string remote_ice_ufrag_;
327 std::string remote_ice_pwd_; 340 std::string remote_ice_pwd_;
328 IceMode remote_ice_mode_; 341 IceMode remote_ice_mode_;
329 rtc::SSLFingerprint dtls_fingerprint_; 342 rtc::SSLFingerprint dtls_fingerprint_;
330 rtc::SSLRole ssl_role_; 343 rtc::SSLRole ssl_role_;
331 size_t connection_count_; 344 size_t connection_count_;
345 Transport::CandidateGatheringState candidate_gathering_state_;
332 }; 346 };
333 347
334 // Fake transport class, which can be passed to anything that needs a Transport. 348 // Fake transport class, which can be passed to anything that needs a Transport.
335 // Can be informed of another FakeTransport via SetDestination (low-tech way 349 // Can be informed of another FakeTransport via SetDestination (low-tech way
336 // of doing candidates) 350 // of doing candidates)
337 class FakeTransport : public Transport { 351 class FakeTransport : public Transport {
338 public: 352 public:
339 typedef std::map<int, FakeTransportChannel*> ChannelMap; 353 typedef std::map<int, FakeTransportChannel*> ChannelMap;
340 FakeTransport(rtc::Thread* signaling_thread, 354 FakeTransport(const std::string& content_name,
341 rtc::Thread* worker_thread, 355 PortAllocator* alllocator = nullptr)
342 const std::string& content_name, 356 : Transport(content_name, "test_type", nullptr),
343 PortAllocator* alllocator = NULL) 357 dest_(nullptr),
344 : Transport(signaling_thread, worker_thread,
345 content_name, "test_type", NULL),
346 dest_(NULL),
347 async_(false), 358 async_(false),
348 identity_(NULL) { 359 identity_(nullptr) {
349 } 360 }
361
350 ~FakeTransport() { 362 ~FakeTransport() {
351 DestroyAllChannels(); 363 DestroyAllChannels();
352 } 364 }
353 365
354 const ChannelMap& channels() const { return channels_; } 366 const ChannelMap& channels() const { return channels_; }
355 367
356 void SetAsync(bool async) { async_ = async; } 368 void SetAsync(bool async) { async_ = async; }
357 void SetDestination(FakeTransport* dest) { 369 void SetDestination(FakeTransport* dest) {
358 dest_ = dest; 370 dest_ = dest;
359 for (ChannelMap::iterator it = channels_.begin(); it != channels_.end(); 371 for (const auto& kv : channels_) {
360 ++it) { 372 kv.second->SetLocalIdentity(identity_);
361 it->second->SetLocalIdentity(identity_); 373 SetChannelDestination(kv.first, kv.second);
362 SetChannelDestination(it->first, it->second);
363 } 374 }
364 } 375 }
365 376
366 void SetWritable(bool writable) { 377 void SetWritable(bool writable) {
367 for (ChannelMap::iterator it = channels_.begin(); it != channels_.end(); 378 for (const auto& kv : channels_) {
368 ++it) { 379 kv.second->SetWritable(writable);
369 it->second->SetWritable(writable);
370 } 380 }
371 } 381 }
372 382
373 void set_identity(rtc::SSLIdentity* identity) { 383 virtual void SetIdentity(rtc::SSLIdentity* identity) {
374 identity_ = identity; 384 identity_ = identity;
375 } 385 }
386 virtual bool GetIdentity(rtc::SSLIdentity** identity) {
387 if (!identity_)
388 return false;
389
390 *identity = identity_->GetReference();
391 return true;
392 }
376 393
377 using Transport::local_description; 394 using Transport::local_description;
378 using Transport::remote_description; 395 using Transport::remote_description;
379 396
380 protected: 397 protected:
381 virtual TransportChannelImpl* CreateTransportChannel(int component) { 398 virtual TransportChannelImpl* CreateTransportChannel(int component) {
382 if (channels_.find(component) != channels_.end()) { 399 if (channels_.find(component) != channels_.end()) {
383 return NULL; 400 return nullptr;
384 } 401 }
385 FakeTransportChannel* channel = 402 FakeTransportChannel* channel =
386 new FakeTransportChannel(this, content_name(), component); 403 new FakeTransportChannel(this, content_name(), component);
387 channel->SetAsync(async_); 404 channel->SetAsync(async_);
388 SetChannelDestination(component, channel); 405 SetChannelDestination(component, channel);
389 channels_[component] = channel; 406 channels_[component] = channel;
390 return channel; 407 return channel;
391 } 408 }
409
392 virtual void DestroyTransportChannel(TransportChannelImpl* channel) { 410 virtual void DestroyTransportChannel(TransportChannelImpl* channel) {
393 channels_.erase(channel->component()); 411 channels_.erase(channel->component());
394 delete channel; 412 delete channel;
395 } 413 }
396 virtual void SetIdentity_w(rtc::SSLIdentity* identity) {
397 identity_ = identity;
398 }
399 virtual bool GetIdentity_w(rtc::SSLIdentity** identity) {
400 if (!identity_)
401 return false;
402
403 *identity = identity_->GetReference();
404 return true;
405 }
406 414
407 private: 415 private:
408 FakeTransportChannel* GetFakeChannel(int component) { 416 FakeTransportChannel* GetFakeChannel(int component) {
409 ChannelMap::iterator it = channels_.find(component); 417 auto it = channels_.find(component);
410 return (it != channels_.end()) ? it->second : NULL; 418 return (it != channels_.end()) ? it->second : nullptr;
411 } 419 }
420
412 void SetChannelDestination(int component, 421 void SetChannelDestination(int component,
413 FakeTransportChannel* channel) { 422 FakeTransportChannel* channel) {
414 FakeTransportChannel* dest_channel = NULL; 423 FakeTransportChannel* dest_channel = nullptr;
415 if (dest_) { 424 if (dest_) {
416 dest_channel = dest_->GetFakeChannel(component); 425 dest_channel = dest_->GetFakeChannel(component);
417 if (dest_channel) { 426 if (dest_channel) {
418 dest_channel->SetLocalIdentity(dest_->identity_); 427 dest_channel->SetLocalIdentity(dest_->identity_);
419 } 428 }
420 } 429 }
421 channel->SetDestination(dest_channel); 430 channel->SetDestination(dest_channel);
422 } 431 }
423 432
424 // Note, this is distinct from the Channel map owned by Transport. 433 // Note, this is distinct from the Channel map owned by Transport.
425 // This map just tracks the FakeTransportChannels created by this class. 434 // This map just tracks the FakeTransportChannels created by this class.
435 // It's mainly needed so that we can access a FakeTransportChannel directly,
436 // even if wrapped by a DtlsTransportChannelWrapper.
426 ChannelMap channels_; 437 ChannelMap channels_;
427 FakeTransport* dest_; 438 FakeTransport* dest_;
428 bool async_; 439 bool async_;
429 rtc::SSLIdentity* identity_; 440 rtc::SSLIdentity* identity_;
430 }; 441 };
431 442
432 // Fake session class, which can be passed into a BaseChannel object for 443 // Fake TransportController class, which can be passed into a BaseChannel object
433 // test purposes. Can be connected to other FakeSessions via Connect(). 444 // for test purposes. Can be connected to other FakeTransportControllers via
434 class FakeSession : public BaseSession { 445 // Connect().
446 class FakeTransportController : public TransportController {
435 public: 447 public:
436 explicit FakeSession() 448 FakeTransportController()
437 : BaseSession(rtc::Thread::Current(), 449 : TransportController(rtc::Thread::Current(),
438 rtc::Thread::Current(), 450 rtc::Thread::Current(),
439 NULL, "", "", true), 451 nullptr),
440 fail_create_channel_(false) { 452 fail_create_channel_(false) {
441 }
442 explicit FakeSession(bool initiator)
443 : BaseSession(rtc::Thread::Current(),
444 rtc::Thread::Current(),
445 NULL, "", "", initiator),
446 fail_create_channel_(false) {
447 }
448 FakeSession(rtc::Thread* worker_thread, bool initiator)
449 : BaseSession(rtc::Thread::Current(),
450 worker_thread,
451 NULL, "", "", initiator),
452 fail_create_channel_(false) {
453 } 453 }
454 454
455 FakeTransport* GetTransport(const std::string& content_name) { 455 explicit FakeTransportController(bool initiator)
456 return static_cast<FakeTransport*>( 456 : TransportController(rtc::Thread::Current(),
457 BaseSession::GetTransport(content_name)); 457 rtc::Thread::Current(),
458 nullptr),
459 fail_create_channel_(false) {
460 SetIceRole(initiator ? ICEROLE_CONTROLLING : ICEROLE_CONTROLLED);
458 } 461 }
459 462
460 void Connect(FakeSession* dest) { 463 FakeTransportController(rtc::Thread* worker_thread, bool initiator)
461 // Simulate the exchange of candidates. 464 : TransportController(rtc::Thread::Current(),
462 CompleteNegotiation(); 465 worker_thread,
463 dest->CompleteNegotiation(); 466 nullptr),
464 for (TransportMap::const_iterator it = transport_proxies().begin(); 467 fail_create_channel_(false) {
465 it != transport_proxies().end(); ++it) { 468 SetIceRole(initiator ? ICEROLE_CONTROLLING : ICEROLE_CONTROLLED);
466 static_cast<FakeTransport*>(it->second->impl())->SetDestination(
467 dest->GetTransport(it->first));
468 }
469 } 469 }
470 470
471 virtual TransportChannel* CreateChannel( 471 FakeTransport* GetTransport_w(const std::string& transport_name) {
472 const std::string& content_name, 472 return static_cast<FakeTransport*>(
473 TransportController::GetTransport_w(transport_name));
474 }
475
476 void Connect(FakeTransportController* dest) {
477 worker_thread()->Invoke<void>(rtc::Bind(
478 &FakeTransportController::Connect_w, this, dest));
479 }
480
481 virtual TransportChannel* CreateTransportChannel_w(
482 const std::string& transport_name,
473 int component) { 483 int component) {
474 if (fail_create_channel_) { 484 if (fail_create_channel_) {
475 return NULL; 485 return nullptr;
476 } 486 }
477 return BaseSession::CreateChannel(content_name, component); 487 return TransportController::CreateTransportChannel_w(transport_name,
488 component);
478 } 489 }
479 490
480 void set_fail_channel_creation(bool fail_channel_creation) { 491 void set_fail_channel_creation(bool fail_channel_creation) {
481 fail_create_channel_ = fail_channel_creation; 492 fail_create_channel_ = fail_channel_creation;
482 } 493 }
483 494
484 // TODO: Hoist this into Session when we re-work the Session code. 495 protected:
485 void set_ssl_identity(rtc::SSLIdentity* identity) { 496 virtual Transport* CreateTransport_w(const std::string& transport_name) {
486 for (TransportMap::const_iterator it = transport_proxies().begin(); 497 return new FakeTransport(transport_name);
487 it != transport_proxies().end(); ++it) { 498 }
488 // We know that we have a FakeTransport*
489 499
490 static_cast<FakeTransport*>(it->second->impl())->set_identity 500 void Connect_w(FakeTransportController* dest) {
491 (identity); 501 // Simulate the exchange of candidates.
502 ConnectChannels_w();
503 dest->ConnectChannels_w();
504 for (auto& kv : transports()) {
505 FakeTransport* transport = static_cast<FakeTransport*>(kv.second);
506 transport->SetDestination(dest->GetTransport_w(kv.first));
492 } 507 }
493 } 508 }
494 509
495 protected: 510 void ConnectChannels_w() {
496 virtual Transport* CreateTransport(const std::string& content_name) { 511 for (auto& kv : transports()) {
497 return new FakeTransport(signaling_thread(), worker_thread(), content_name); 512 FakeTransport* transport = static_cast<FakeTransport*>(kv.second);
498 } 513 transport->ConnectChannels();
499
500 void CompleteNegotiation() {
501 for (TransportMap::const_iterator it = transport_proxies().begin();
502 it != transport_proxies().end(); ++it) {
503 it->second->CompleteNegotiation();
504 it->second->ConnectChannels();
505 } 514 }
506 } 515 }
507 516
508 private: 517 private:
509 bool fail_create_channel_; 518 bool fail_create_channel_;
510 }; 519 };
511 520
512 } // namespace cricket 521 } // namespace cricket
513 522
514 #endif // WEBRTC_P2P_BASE_FAKESESSION_H_ 523 #endif // WEBRTC_P2P_BASE_FAKETRANSPORTCONTROLLER_H_
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698