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

Side by Side Diff: webrtc/p2p/base/turnserver.cc

Issue 2620303003: Replace ASSERT by RTC_DCHECK in all non-test code. (Closed)
Patch Set: Address final nits. Created 3 years, 11 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
« no previous file with comments | « webrtc/p2p/base/turnport.cc ('k') | webrtc/p2p/client/basicportallocator.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /* 1 /*
2 * Copyright 2012 The WebRTC Project Authors. All rights reserved. 2 * Copyright 2012 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 #include "webrtc/p2p/base/turnserver.h" 11 #include "webrtc/p2p/base/turnserver.h"
12 12
13 #include <tuple> // for std::tie 13 #include <tuple> // for std::tie
14 14
15 #include "webrtc/p2p/base/asyncstuntcpsocket.h" 15 #include "webrtc/p2p/base/asyncstuntcpsocket.h"
16 #include "webrtc/p2p/base/common.h" 16 #include "webrtc/p2p/base/common.h"
17 #include "webrtc/p2p/base/packetsocketfactory.h" 17 #include "webrtc/p2p/base/packetsocketfactory.h"
18 #include "webrtc/p2p/base/stun.h" 18 #include "webrtc/p2p/base/stun.h"
19 #include "webrtc/base/bind.h" 19 #include "webrtc/base/bind.h"
20 #include "webrtc/base/bytebuffer.h" 20 #include "webrtc/base/bytebuffer.h"
21 #include "webrtc/base/checks.h"
21 #include "webrtc/base/helpers.h" 22 #include "webrtc/base/helpers.h"
22 #include "webrtc/base/logging.h" 23 #include "webrtc/base/logging.h"
23 #include "webrtc/base/messagedigest.h" 24 #include "webrtc/base/messagedigest.h"
24 #include "webrtc/base/socketadapters.h" 25 #include "webrtc/base/socketadapters.h"
25 #include "webrtc/base/stringencode.h" 26 #include "webrtc/base/stringencode.h"
26 #include "webrtc/base/thread.h" 27 #include "webrtc/base/thread.h"
27 28
28 namespace cricket { 29 namespace cricket {
29 30
30 // TODO(juberti): Move this all to a future turnmessage.h 31 // TODO(juberti): Move this all to a future turnmessage.h
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
135 136
136 for (ServerSocketMap::iterator it = server_listen_sockets_.begin(); 137 for (ServerSocketMap::iterator it = server_listen_sockets_.begin();
137 it != server_listen_sockets_.end(); ++it) { 138 it != server_listen_sockets_.end(); ++it) {
138 rtc::AsyncSocket* socket = it->first; 139 rtc::AsyncSocket* socket = it->first;
139 delete socket; 140 delete socket;
140 } 141 }
141 } 142 }
142 143
143 void TurnServer::AddInternalSocket(rtc::AsyncPacketSocket* socket, 144 void TurnServer::AddInternalSocket(rtc::AsyncPacketSocket* socket,
144 ProtocolType proto) { 145 ProtocolType proto) {
145 ASSERT(server_sockets_.end() == server_sockets_.find(socket)); 146 RTC_DCHECK(server_sockets_.end() == server_sockets_.find(socket));
146 server_sockets_[socket] = proto; 147 server_sockets_[socket] = proto;
147 socket->SignalReadPacket.connect(this, &TurnServer::OnInternalPacket); 148 socket->SignalReadPacket.connect(this, &TurnServer::OnInternalPacket);
148 } 149 }
149 150
150 void TurnServer::AddInternalServerSocket(rtc::AsyncSocket* socket, 151 void TurnServer::AddInternalServerSocket(rtc::AsyncSocket* socket,
151 ProtocolType proto) { 152 ProtocolType proto) {
152 ASSERT(server_listen_sockets_.end() == 153 RTC_DCHECK(server_listen_sockets_.end() ==
153 server_listen_sockets_.find(socket)); 154 server_listen_sockets_.find(socket));
154 server_listen_sockets_[socket] = proto; 155 server_listen_sockets_[socket] = proto;
155 socket->SignalReadEvent.connect(this, &TurnServer::OnNewInternalConnection); 156 socket->SignalReadEvent.connect(this, &TurnServer::OnNewInternalConnection);
156 } 157 }
157 158
158 void TurnServer::SetExternalSocketFactory( 159 void TurnServer::SetExternalSocketFactory(
159 rtc::PacketSocketFactory* factory, 160 rtc::PacketSocketFactory* factory,
160 const rtc::SocketAddress& external_addr) { 161 const rtc::SocketAddress& external_addr) {
161 external_socket_factory_.reset(factory); 162 external_socket_factory_.reset(factory);
162 external_addr_ = external_addr; 163 external_addr_ = external_addr;
163 } 164 }
164 165
165 void TurnServer::OnNewInternalConnection(rtc::AsyncSocket* socket) { 166 void TurnServer::OnNewInternalConnection(rtc::AsyncSocket* socket) {
166 ASSERT(server_listen_sockets_.find(socket) != server_listen_sockets_.end()); 167 RTC_DCHECK(server_listen_sockets_.find(socket) !=
168 server_listen_sockets_.end());
167 AcceptConnection(socket); 169 AcceptConnection(socket);
168 } 170 }
169 171
170 void TurnServer::AcceptConnection(rtc::AsyncSocket* server_socket) { 172 void TurnServer::AcceptConnection(rtc::AsyncSocket* server_socket) {
171 // Check if someone is trying to connect to us. 173 // Check if someone is trying to connect to us.
172 rtc::SocketAddress accept_addr; 174 rtc::SocketAddress accept_addr;
173 rtc::AsyncSocket* accepted_socket = server_socket->Accept(&accept_addr); 175 rtc::AsyncSocket* accepted_socket = server_socket->Accept(&accept_addr);
174 if (accepted_socket != NULL) { 176 if (accepted_socket != NULL) {
175 ProtocolType proto = server_listen_sockets_[server_socket]; 177 ProtocolType proto = server_listen_sockets_[server_socket];
176 cricket::AsyncStunTCPSocket* tcp_socket = 178 cricket::AsyncStunTCPSocket* tcp_socket =
(...skipping 12 matching lines...) Expand all
189 191
190 void TurnServer::OnInternalPacket(rtc::AsyncPacketSocket* socket, 192 void TurnServer::OnInternalPacket(rtc::AsyncPacketSocket* socket,
191 const char* data, size_t size, 193 const char* data, size_t size,
192 const rtc::SocketAddress& addr, 194 const rtc::SocketAddress& addr,
193 const rtc::PacketTime& packet_time) { 195 const rtc::PacketTime& packet_time) {
194 // Fail if the packet is too small to even contain a channel header. 196 // Fail if the packet is too small to even contain a channel header.
195 if (size < TURN_CHANNEL_HEADER_SIZE) { 197 if (size < TURN_CHANNEL_HEADER_SIZE) {
196 return; 198 return;
197 } 199 }
198 InternalSocketMap::iterator iter = server_sockets_.find(socket); 200 InternalSocketMap::iterator iter = server_sockets_.find(socket);
199 ASSERT(iter != server_sockets_.end()); 201 RTC_DCHECK(iter != server_sockets_.end());
200 TurnServerConnection conn(addr, iter->second, socket); 202 TurnServerConnection conn(addr, iter->second, socket);
201 uint16_t msg_type = rtc::GetBE16(data); 203 uint16_t msg_type = rtc::GetBE16(data);
202 if (!IsTurnChannelData(msg_type)) { 204 if (!IsTurnChannelData(msg_type)) {
203 // This is a STUN message. 205 // This is a STUN message.
204 HandleStunMessage(&conn, data, size); 206 HandleStunMessage(&conn, data, size);
205 } else { 207 } else {
206 // This is a channel message; let the allocation handle it. 208 // This is a channel message; let the allocation handle it.
207 TurnServerAllocation* allocation = FindAllocation(&conn); 209 TurnServerAllocation* allocation = FindAllocation(&conn);
208 if (allocation) { 210 if (allocation) {
209 allocation->HandleChannelData(data, size); 211 allocation->HandleChannelData(data, size);
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
283 285
284 std::string username = username_attr->GetString(); 286 std::string username = username_attr->GetString();
285 return (auth_hook_ != NULL && auth_hook_->GetKey(username, realm_, key)); 287 return (auth_hook_ != NULL && auth_hook_->GetKey(username, realm_, key));
286 } 288 }
287 289
288 bool TurnServer::CheckAuthorization(TurnServerConnection* conn, 290 bool TurnServer::CheckAuthorization(TurnServerConnection* conn,
289 const StunMessage* msg, 291 const StunMessage* msg,
290 const char* data, size_t size, 292 const char* data, size_t size,
291 const std::string& key) { 293 const std::string& key) {
292 // RFC 5389, 10.2.2. 294 // RFC 5389, 10.2.2.
293 ASSERT(IsStunRequestType(msg->type())); 295 RTC_DCHECK(IsStunRequestType(msg->type()));
294 const StunByteStringAttribute* mi_attr = 296 const StunByteStringAttribute* mi_attr =
295 msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY); 297 msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY);
296 const StunByteStringAttribute* username_attr = 298 const StunByteStringAttribute* username_attr =
297 msg->GetByteString(STUN_ATTR_USERNAME); 299 msg->GetByteString(STUN_ATTR_USERNAME);
298 const StunByteStringAttribute* realm_attr = 300 const StunByteStringAttribute* realm_attr =
299 msg->GetByteString(STUN_ATTR_REALM); 301 msg->GetByteString(STUN_ATTR_REALM);
300 const StunByteStringAttribute* nonce_attr = 302 const StunByteStringAttribute* nonce_attr =
301 msg->GetByteString(STUN_ATTR_NONCE); 303 msg->GetByteString(STUN_ATTR_NONCE);
302 304
303 // Fail if no M-I. 305 // Fail if no M-I.
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after
388 SendErrorResponse(conn, msg, STUN_ERROR_SERVER_ERROR, 390 SendErrorResponse(conn, msg, STUN_ERROR_SERVER_ERROR,
389 "Failed to allocate socket"); 391 "Failed to allocate socket");
390 } 392 }
391 } 393 }
392 394
393 std::string TurnServer::GenerateNonce(int64_t now) const { 395 std::string TurnServer::GenerateNonce(int64_t now) const {
394 // Generate a nonce of the form hex(now + HMAC-MD5(nonce_key_, now)) 396 // Generate a nonce of the form hex(now + HMAC-MD5(nonce_key_, now))
395 std::string input(reinterpret_cast<const char*>(&now), sizeof(now)); 397 std::string input(reinterpret_cast<const char*>(&now), sizeof(now));
396 std::string nonce = rtc::hex_encode(input.c_str(), input.size()); 398 std::string nonce = rtc::hex_encode(input.c_str(), input.size());
397 nonce += rtc::ComputeHmac(rtc::DIGEST_MD5, nonce_key_, input); 399 nonce += rtc::ComputeHmac(rtc::DIGEST_MD5, nonce_key_, input);
398 ASSERT(nonce.size() == kNonceSize); 400 RTC_DCHECK(nonce.size() == kNonceSize);
399 401
400 return nonce; 402 return nonce;
401 } 403 }
402 404
403 bool TurnServer::ValidateNonce(const std::string& nonce) const { 405 bool TurnServer::ValidateNonce(const std::string& nonce) const {
404 // Check the size. 406 // Check the size.
405 if (nonce.size() != kNonceSize) { 407 if (nonce.size() != kNonceSize) {
406 return false; 408 return false;
407 } 409 }
408 410
(...skipping 185 matching lines...) Expand 10 before | Expand all | Expand 10 after
594 LOG_J(LS_INFO, this) << "Allocation destroyed"; 596 LOG_J(LS_INFO, this) << "Allocation destroyed";
595 } 597 }
596 598
597 std::string TurnServerAllocation::ToString() const { 599 std::string TurnServerAllocation::ToString() const {
598 std::ostringstream ost; 600 std::ostringstream ost;
599 ost << "Alloc[" << conn_.ToString() << "]"; 601 ost << "Alloc[" << conn_.ToString() << "]";
600 return ost.str(); 602 return ost.str();
601 } 603 }
602 604
603 void TurnServerAllocation::HandleTurnMessage(const TurnMessage* msg) { 605 void TurnServerAllocation::HandleTurnMessage(const TurnMessage* msg) {
604 ASSERT(msg != NULL); 606 RTC_DCHECK(msg != NULL);
605 switch (msg->type()) { 607 switch (msg->type()) {
606 case STUN_ALLOCATE_REQUEST: 608 case STUN_ALLOCATE_REQUEST:
607 HandleAllocateRequest(msg); 609 HandleAllocateRequest(msg);
608 break; 610 break;
609 case TURN_REFRESH_REQUEST: 611 case TURN_REFRESH_REQUEST:
610 HandleRefreshRequest(msg); 612 HandleRefreshRequest(msg);
611 break; 613 break;
612 case TURN_SEND_INDICATION: 614 case TURN_SEND_INDICATION:
613 HandleSendIndication(msg); 615 HandleSendIndication(msg);
614 break; 616 break;
615 case TURN_CREATE_PERMISSION_REQUEST: 617 case TURN_CREATE_PERMISSION_REQUEST:
616 HandleCreatePermissionRequest(msg); 618 HandleCreatePermissionRequest(msg);
617 break; 619 break;
618 case TURN_CHANNEL_BIND_REQUEST: 620 case TURN_CHANNEL_BIND_REQUEST:
619 HandleChannelBindRequest(msg); 621 HandleChannelBindRequest(msg);
620 break; 622 break;
621 default: 623 default:
622 // Not sure what to do with this, just eat it. 624 // Not sure what to do with this, just eat it.
623 LOG_J(LS_WARNING, this) << "Invalid TURN message type received: " 625 LOG_J(LS_WARNING, this) << "Invalid TURN message type received: "
624 << msg->type(); 626 << msg->type();
625 } 627 }
626 } 628 }
627 629
628 void TurnServerAllocation::HandleAllocateRequest(const TurnMessage* msg) { 630 void TurnServerAllocation::HandleAllocateRequest(const TurnMessage* msg) {
629 // Copy the important info from the allocate request. 631 // Copy the important info from the allocate request.
630 transaction_id_ = msg->transaction_id(); 632 transaction_id_ = msg->transaction_id();
631 const StunByteStringAttribute* username_attr = 633 const StunByteStringAttribute* username_attr =
632 msg->GetByteString(STUN_ATTR_USERNAME); 634 msg->GetByteString(STUN_ATTR_USERNAME);
633 ASSERT(username_attr != NULL); 635 RTC_DCHECK(username_attr != NULL);
634 username_ = username_attr->GetString(); 636 username_ = username_attr->GetString();
635 const StunByteStringAttribute* origin_attr = 637 const StunByteStringAttribute* origin_attr =
636 msg->GetByteString(STUN_ATTR_ORIGIN); 638 msg->GetByteString(STUN_ATTR_ORIGIN);
637 if (origin_attr) { 639 if (origin_attr) {
638 origin_ = origin_attr->GetString(); 640 origin_ = origin_attr->GetString();
639 } 641 }
640 642
641 // Figure out the lifetime and start the allocation timer. 643 // Figure out the lifetime and start the allocation timer.
642 int lifetime_secs = ComputeLifetime(msg); 644 int lifetime_secs = ComputeLifetime(msg);
643 thread_->PostDelayed(RTC_FROM_HERE, lifetime_secs * 1000, this, 645 thread_->PostDelayed(RTC_FROM_HERE, lifetime_secs * 1000, this,
(...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after
794 LOG_J(LS_WARNING, this) << "Received channel data for invalid channel, id=" 796 LOG_J(LS_WARNING, this) << "Received channel data for invalid channel, id="
795 << channel_id; 797 << channel_id;
796 } 798 }
797 } 799 }
798 800
799 void TurnServerAllocation::OnExternalPacket( 801 void TurnServerAllocation::OnExternalPacket(
800 rtc::AsyncPacketSocket* socket, 802 rtc::AsyncPacketSocket* socket,
801 const char* data, size_t size, 803 const char* data, size_t size,
802 const rtc::SocketAddress& addr, 804 const rtc::SocketAddress& addr,
803 const rtc::PacketTime& packet_time) { 805 const rtc::PacketTime& packet_time) {
804 ASSERT(external_socket_.get() == socket); 806 RTC_DCHECK(external_socket_.get() == socket);
805 Channel* channel = FindChannel(addr); 807 Channel* channel = FindChannel(addr);
806 if (channel) { 808 if (channel) {
807 // There is a channel bound to this address. Send as a channel message. 809 // There is a channel bound to this address. Send as a channel message.
808 rtc::ByteBufferWriter buf; 810 rtc::ByteBufferWriter buf;
809 buf.WriteUInt16(channel->id()); 811 buf.WriteUInt16(channel->id());
810 buf.WriteUInt16(static_cast<uint16_t>(size)); 812 buf.WriteUInt16(static_cast<uint16_t>(size));
811 buf.WriteBytes(data, size); 813 buf.WriteBytes(data, size);
812 server_->Send(&conn_, buf); 814 server_->Send(&conn_, buf);
813 } else if (!server_->enable_permission_checks_ || 815 } else if (!server_->enable_permission_checks_ ||
814 HasPermission(addr.ipaddr())) { 816 HasPermission(addr.ipaddr())) {
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after
899 server_->SendErrorResponse(&conn_, req, code, reason); 901 server_->SendErrorResponse(&conn_, req, code, reason);
900 } 902 }
901 903
902 void TurnServerAllocation::SendExternal(const void* data, size_t size, 904 void TurnServerAllocation::SendExternal(const void* data, size_t size,
903 const rtc::SocketAddress& peer) { 905 const rtc::SocketAddress& peer) {
904 rtc::PacketOptions options; 906 rtc::PacketOptions options;
905 external_socket_->SendTo(data, size, peer, options); 907 external_socket_->SendTo(data, size, peer, options);
906 } 908 }
907 909
908 void TurnServerAllocation::OnMessage(rtc::Message* msg) { 910 void TurnServerAllocation::OnMessage(rtc::Message* msg) {
909 ASSERT(msg->message_id == MSG_ALLOCATION_TIMEOUT); 911 RTC_DCHECK(msg->message_id == MSG_ALLOCATION_TIMEOUT);
910 SignalDestroyed(this); 912 SignalDestroyed(this);
911 delete this; 913 delete this;
912 } 914 }
913 915
914 void TurnServerAllocation::OnPermissionDestroyed(Permission* perm) { 916 void TurnServerAllocation::OnPermissionDestroyed(Permission* perm) {
915 PermissionList::iterator it = std::find(perms_.begin(), perms_.end(), perm); 917 PermissionList::iterator it = std::find(perms_.begin(), perms_.end(), perm);
916 ASSERT(it != perms_.end()); 918 RTC_DCHECK(it != perms_.end());
917 perms_.erase(it); 919 perms_.erase(it);
918 } 920 }
919 921
920 void TurnServerAllocation::OnChannelDestroyed(Channel* channel) { 922 void TurnServerAllocation::OnChannelDestroyed(Channel* channel) {
921 ChannelList::iterator it = 923 ChannelList::iterator it =
922 std::find(channels_.begin(), channels_.end(), channel); 924 std::find(channels_.begin(), channels_.end(), channel);
923 ASSERT(it != channels_.end()); 925 RTC_DCHECK(it != channels_.end());
924 channels_.erase(it); 926 channels_.erase(it);
925 } 927 }
926 928
927 TurnServerAllocation::Permission::Permission(rtc::Thread* thread, 929 TurnServerAllocation::Permission::Permission(rtc::Thread* thread,
928 const rtc::IPAddress& peer) 930 const rtc::IPAddress& peer)
929 : thread_(thread), peer_(peer) { 931 : thread_(thread), peer_(peer) {
930 Refresh(); 932 Refresh();
931 } 933 }
932 934
933 TurnServerAllocation::Permission::~Permission() { 935 TurnServerAllocation::Permission::~Permission() {
934 thread_->Clear(this, MSG_ALLOCATION_TIMEOUT); 936 thread_->Clear(this, MSG_ALLOCATION_TIMEOUT);
935 } 937 }
936 938
937 void TurnServerAllocation::Permission::Refresh() { 939 void TurnServerAllocation::Permission::Refresh() {
938 thread_->Clear(this, MSG_ALLOCATION_TIMEOUT); 940 thread_->Clear(this, MSG_ALLOCATION_TIMEOUT);
939 thread_->PostDelayed(RTC_FROM_HERE, kPermissionTimeout, this, 941 thread_->PostDelayed(RTC_FROM_HERE, kPermissionTimeout, this,
940 MSG_ALLOCATION_TIMEOUT); 942 MSG_ALLOCATION_TIMEOUT);
941 } 943 }
942 944
943 void TurnServerAllocation::Permission::OnMessage(rtc::Message* msg) { 945 void TurnServerAllocation::Permission::OnMessage(rtc::Message* msg) {
944 ASSERT(msg->message_id == MSG_ALLOCATION_TIMEOUT); 946 RTC_DCHECK(msg->message_id == MSG_ALLOCATION_TIMEOUT);
945 SignalDestroyed(this); 947 SignalDestroyed(this);
946 delete this; 948 delete this;
947 } 949 }
948 950
949 TurnServerAllocation::Channel::Channel(rtc::Thread* thread, int id, 951 TurnServerAllocation::Channel::Channel(rtc::Thread* thread, int id,
950 const rtc::SocketAddress& peer) 952 const rtc::SocketAddress& peer)
951 : thread_(thread), id_(id), peer_(peer) { 953 : thread_(thread), id_(id), peer_(peer) {
952 Refresh(); 954 Refresh();
953 } 955 }
954 956
955 TurnServerAllocation::Channel::~Channel() { 957 TurnServerAllocation::Channel::~Channel() {
956 thread_->Clear(this, MSG_ALLOCATION_TIMEOUT); 958 thread_->Clear(this, MSG_ALLOCATION_TIMEOUT);
957 } 959 }
958 960
959 void TurnServerAllocation::Channel::Refresh() { 961 void TurnServerAllocation::Channel::Refresh() {
960 thread_->Clear(this, MSG_ALLOCATION_TIMEOUT); 962 thread_->Clear(this, MSG_ALLOCATION_TIMEOUT);
961 thread_->PostDelayed(RTC_FROM_HERE, kChannelTimeout, this, 963 thread_->PostDelayed(RTC_FROM_HERE, kChannelTimeout, this,
962 MSG_ALLOCATION_TIMEOUT); 964 MSG_ALLOCATION_TIMEOUT);
963 } 965 }
964 966
965 void TurnServerAllocation::Channel::OnMessage(rtc::Message* msg) { 967 void TurnServerAllocation::Channel::OnMessage(rtc::Message* msg) {
966 ASSERT(msg->message_id == MSG_ALLOCATION_TIMEOUT); 968 RTC_DCHECK(msg->message_id == MSG_ALLOCATION_TIMEOUT);
967 SignalDestroyed(this); 969 SignalDestroyed(this);
968 delete this; 970 delete this;
969 } 971 }
970 972
971 } // namespace cricket 973 } // namespace cricket
OLDNEW
« no previous file with comments | « webrtc/p2p/base/turnport.cc ('k') | webrtc/p2p/client/basicportallocator.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698