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

Side by Side Diff: net/base/default_server_bound_cert_store_unittest.cc

Issue 12680003: net: split net/ssl out of net/base (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 9 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
(Empty)
1 // Copyright (c) 2012 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 "net/base/default_server_bound_cert_store.h"
6
7 #include <map>
8 #include <string>
9 #include <vector>
10
11 #include "base/bind.h"
12 #include "base/compiler_specific.h"
13 #include "base/logging.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/message_loop.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17
18 namespace net {
19
20 namespace {
21
22 void CallCounter(int* counter) {
23 (*counter)++;
24 }
25
26 void NotCalled() {
27 ADD_FAILURE() << "Unexpected callback execution.";
28 }
29
30 void GetCertCallbackNotCalled(const std::string& server_identifier,
31 SSLClientCertType type,
32 base::Time expiration_time,
33 const std::string& private_key_result,
34 const std::string& cert_result) {
35 ADD_FAILURE() << "Unexpected callback execution.";
36 }
37
38 class AsyncGetCertHelper {
39 public:
40 AsyncGetCertHelper() : called_(false) {}
41
42 void Callback(const std::string& server_identifier,
43 SSLClientCertType type,
44 base::Time expiration_time,
45 const std::string& private_key_result,
46 const std::string& cert_result) {
47 server_identifier_ = server_identifier;
48 type_ = type;
49 expiration_time_ = expiration_time;
50 private_key_ = private_key_result;
51 cert_ = cert_result;
52 called_ = true;
53 }
54
55 std::string server_identifier_;
56 SSLClientCertType type_;
57 base::Time expiration_time_;
58 std::string private_key_;
59 std::string cert_;
60 bool called_;
61 };
62
63 void GetAllCallback(
64 ServerBoundCertStore::ServerBoundCertList* dest,
65 const ServerBoundCertStore::ServerBoundCertList& result) {
66 *dest = result;
67 }
68
69 class MockPersistentStore
70 : public DefaultServerBoundCertStore::PersistentStore {
71 public:
72 MockPersistentStore();
73
74 // DefaultServerBoundCertStore::PersistentStore implementation.
75 virtual void Load(const LoadedCallback& loaded_callback) OVERRIDE;
76 virtual void AddServerBoundCert(
77 const DefaultServerBoundCertStore::ServerBoundCert& cert) OVERRIDE;
78 virtual void DeleteServerBoundCert(
79 const DefaultServerBoundCertStore::ServerBoundCert& cert) OVERRIDE;
80 virtual void SetForceKeepSessionState() OVERRIDE;
81 virtual void Flush(const base::Closure& completion_task) OVERRIDE;
82
83 protected:
84 virtual ~MockPersistentStore();
85
86 private:
87 typedef std::map<std::string, DefaultServerBoundCertStore::ServerBoundCert>
88 ServerBoundCertMap;
89
90 ServerBoundCertMap origin_certs_;
91 };
92
93 MockPersistentStore::MockPersistentStore() {}
94
95 void MockPersistentStore::Load(const LoadedCallback& loaded_callback) {
96 scoped_ptr<ScopedVector<DefaultServerBoundCertStore::ServerBoundCert> >
97 certs(new ScopedVector<DefaultServerBoundCertStore::ServerBoundCert>());
98 ServerBoundCertMap::iterator it;
99
100 for (it = origin_certs_.begin(); it != origin_certs_.end(); ++it) {
101 certs->push_back(
102 new DefaultServerBoundCertStore::ServerBoundCert(it->second));
103 }
104
105 MessageLoop::current()->PostTask(
106 FROM_HERE, base::Bind(loaded_callback, base::Passed(&certs)));
107 }
108
109 void MockPersistentStore::AddServerBoundCert(
110 const DefaultServerBoundCertStore::ServerBoundCert& cert) {
111 origin_certs_[cert.server_identifier()] = cert;
112 }
113
114 void MockPersistentStore::DeleteServerBoundCert(
115 const DefaultServerBoundCertStore::ServerBoundCert& cert) {
116 origin_certs_.erase(cert.server_identifier());
117 }
118
119 void MockPersistentStore::SetForceKeepSessionState() {}
120
121 void MockPersistentStore::Flush(const base::Closure& completion_task) {
122 NOTREACHED();
123 }
124
125 MockPersistentStore::~MockPersistentStore() {}
126
127 } // namespace
128
129 TEST(DefaultServerBoundCertStoreTest, TestLoading) {
130 scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
131
132 persistent_store->AddServerBoundCert(
133 DefaultServerBoundCertStore::ServerBoundCert(
134 "google.com",
135 CLIENT_CERT_RSA_SIGN,
136 base::Time(),
137 base::Time(),
138 "a", "b"));
139 persistent_store->AddServerBoundCert(
140 DefaultServerBoundCertStore::ServerBoundCert(
141 "verisign.com",
142 CLIENT_CERT_ECDSA_SIGN,
143 base::Time(),
144 base::Time(),
145 "c", "d"));
146
147 // Make sure certs load properly.
148 DefaultServerBoundCertStore store(persistent_store.get());
149 // Load has not occurred yet.
150 EXPECT_EQ(0, store.GetCertCount());
151 store.SetServerBoundCert(
152 "verisign.com",
153 CLIENT_CERT_RSA_SIGN,
154 base::Time(),
155 base::Time(),
156 "e", "f");
157 // Wait for load & queued set task.
158 MessageLoop::current()->RunUntilIdle();
159 EXPECT_EQ(2, store.GetCertCount());
160 store.SetServerBoundCert(
161 "twitter.com",
162 CLIENT_CERT_RSA_SIGN,
163 base::Time(),
164 base::Time(),
165 "g", "h");
166 // Set should be synchronous now that load is done.
167 EXPECT_EQ(3, store.GetCertCount());
168 }
169
170 //TODO(mattm): add more tests of without a persistent store?
171 TEST(DefaultServerBoundCertStoreTest, TestSettingAndGetting) {
172 // No persistent store, all calls will be synchronous.
173 DefaultServerBoundCertStore store(NULL);
174 SSLClientCertType type;
175 base::Time expiration_time;
176 std::string private_key, cert;
177 EXPECT_EQ(0, store.GetCertCount());
178 EXPECT_TRUE(store.GetServerBoundCert("verisign.com",
179 &type,
180 &expiration_time,
181 &private_key,
182 &cert,
183 base::Bind(&GetCertCallbackNotCalled)));
184 EXPECT_EQ(CLIENT_CERT_INVALID_TYPE, type);
185 EXPECT_TRUE(private_key.empty());
186 EXPECT_TRUE(cert.empty());
187 store.SetServerBoundCert(
188 "verisign.com",
189 CLIENT_CERT_RSA_SIGN,
190 base::Time::FromInternalValue(123),
191 base::Time::FromInternalValue(456),
192 "i", "j");
193 EXPECT_TRUE(store.GetServerBoundCert("verisign.com",
194 &type,
195 &expiration_time,
196 &private_key,
197 &cert,
198 base::Bind(&GetCertCallbackNotCalled)));
199 EXPECT_EQ(CLIENT_CERT_RSA_SIGN, type);
200 EXPECT_EQ(456, expiration_time.ToInternalValue());
201 EXPECT_EQ("i", private_key);
202 EXPECT_EQ("j", cert);
203 }
204
205 TEST(DefaultServerBoundCertStoreTest, TestDuplicateCerts) {
206 scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
207 DefaultServerBoundCertStore store(persistent_store.get());
208
209 SSLClientCertType type;
210 base::Time expiration_time;
211 std::string private_key, cert;
212 EXPECT_EQ(0, store.GetCertCount());
213 store.SetServerBoundCert(
214 "verisign.com",
215 CLIENT_CERT_RSA_SIGN,
216 base::Time::FromInternalValue(123),
217 base::Time::FromInternalValue(1234),
218 "a", "b");
219 store.SetServerBoundCert(
220 "verisign.com",
221 CLIENT_CERT_ECDSA_SIGN,
222 base::Time::FromInternalValue(456),
223 base::Time::FromInternalValue(4567),
224 "c", "d");
225
226 // Wait for load & queued set tasks.
227 MessageLoop::current()->RunUntilIdle();
228 EXPECT_EQ(1, store.GetCertCount());
229 EXPECT_TRUE(store.GetServerBoundCert("verisign.com",
230 &type,
231 &expiration_time,
232 &private_key,
233 &cert,
234 base::Bind(&GetCertCallbackNotCalled)));
235 EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type);
236 EXPECT_EQ(4567, expiration_time.ToInternalValue());
237 EXPECT_EQ("c", private_key);
238 EXPECT_EQ("d", cert);
239 }
240
241 TEST(DefaultServerBoundCertStoreTest, TestAsyncGet) {
242 scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
243 persistent_store->AddServerBoundCert(ServerBoundCertStore::ServerBoundCert(
244 "verisign.com",
245 CLIENT_CERT_RSA_SIGN,
246 base::Time::FromInternalValue(123),
247 base::Time::FromInternalValue(1234),
248 "a", "b"));
249
250 DefaultServerBoundCertStore store(persistent_store.get());
251 AsyncGetCertHelper helper;
252 SSLClientCertType type;
253 base::Time expiration_time;
254 std::string private_key;
255 std::string cert = "not set";
256 EXPECT_EQ(0, store.GetCertCount());
257 EXPECT_FALSE(store.GetServerBoundCert(
258 "verisign.com", &type, &expiration_time, &private_key, &cert,
259 base::Bind(&AsyncGetCertHelper::Callback, base::Unretained(&helper))));
260
261 // Wait for load & queued get tasks.
262 MessageLoop::current()->RunUntilIdle();
263 EXPECT_EQ(1, store.GetCertCount());
264 EXPECT_EQ("not set", cert);
265 EXPECT_TRUE(helper.called_);
266 EXPECT_EQ("verisign.com", helper.server_identifier_);
267 EXPECT_EQ(CLIENT_CERT_RSA_SIGN, helper.type_);
268 EXPECT_EQ(1234, helper.expiration_time_.ToInternalValue());
269 EXPECT_EQ("a", helper.private_key_);
270 EXPECT_EQ("b", helper.cert_);
271 }
272
273 TEST(DefaultServerBoundCertStoreTest, TestDeleteAll) {
274 scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
275 DefaultServerBoundCertStore store(persistent_store.get());
276
277 store.SetServerBoundCert(
278 "verisign.com",
279 CLIENT_CERT_RSA_SIGN,
280 base::Time(),
281 base::Time(),
282 "a", "b");
283 store.SetServerBoundCert(
284 "google.com",
285 CLIENT_CERT_RSA_SIGN,
286 base::Time(),
287 base::Time(),
288 "c", "d");
289 store.SetServerBoundCert(
290 "harvard.com",
291 CLIENT_CERT_RSA_SIGN,
292 base::Time(),
293 base::Time(),
294 "e", "f");
295 // Wait for load & queued set tasks.
296 MessageLoop::current()->RunUntilIdle();
297
298 EXPECT_EQ(3, store.GetCertCount());
299 int delete_finished = 0;
300 store.DeleteAll(base::Bind(&CallCounter, &delete_finished));
301 ASSERT_EQ(1, delete_finished);
302 EXPECT_EQ(0, store.GetCertCount());
303 }
304
305 TEST(DefaultServerBoundCertStoreTest, TestAsyncGetAndDeleteAll) {
306 scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
307 persistent_store->AddServerBoundCert(ServerBoundCertStore::ServerBoundCert(
308 "verisign.com",
309 CLIENT_CERT_RSA_SIGN,
310 base::Time(),
311 base::Time(),
312 "a", "b"));
313 persistent_store->AddServerBoundCert(ServerBoundCertStore::ServerBoundCert(
314 "google.com",
315 CLIENT_CERT_RSA_SIGN,
316 base::Time(),
317 base::Time(),
318 "c", "d"));
319
320 ServerBoundCertStore::ServerBoundCertList pre_certs;
321 ServerBoundCertStore::ServerBoundCertList post_certs;
322 int delete_finished = 0;
323 DefaultServerBoundCertStore store(persistent_store.get());
324
325 store.GetAllServerBoundCerts(base::Bind(GetAllCallback, &pre_certs));
326 store.DeleteAll(base::Bind(&CallCounter, &delete_finished));
327 store.GetAllServerBoundCerts(base::Bind(GetAllCallback, &post_certs));
328 // Tasks have not run yet.
329 EXPECT_EQ(0u, pre_certs.size());
330 // Wait for load & queued tasks.
331 MessageLoop::current()->RunUntilIdle();
332 EXPECT_EQ(0, store.GetCertCount());
333 EXPECT_EQ(2u, pre_certs.size());
334 EXPECT_EQ(0u, post_certs.size());
335 }
336
337 TEST(DefaultServerBoundCertStoreTest, TestDelete) {
338 scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
339 DefaultServerBoundCertStore store(persistent_store.get());
340
341 SSLClientCertType type;
342 base::Time expiration_time;
343 std::string private_key, cert;
344 EXPECT_EQ(0, store.GetCertCount());
345 store.SetServerBoundCert(
346 "verisign.com",
347 CLIENT_CERT_RSA_SIGN,
348 base::Time(),
349 base::Time(),
350 "a", "b");
351 // Wait for load & queued set task.
352 MessageLoop::current()->RunUntilIdle();
353
354 store.SetServerBoundCert(
355 "google.com",
356 CLIENT_CERT_ECDSA_SIGN,
357 base::Time(),
358 base::Time(),
359 "c", "d");
360
361 EXPECT_EQ(2, store.GetCertCount());
362 int delete_finished = 0;
363 store.DeleteServerBoundCert("verisign.com",
364 base::Bind(&CallCounter, &delete_finished));
365 ASSERT_EQ(1, delete_finished);
366 EXPECT_EQ(1, store.GetCertCount());
367 EXPECT_TRUE(store.GetServerBoundCert("verisign.com",
368 &type,
369 &expiration_time,
370 &private_key,
371 &cert,
372 base::Bind(&GetCertCallbackNotCalled)));
373 EXPECT_EQ(CLIENT_CERT_INVALID_TYPE, type);
374 EXPECT_TRUE(store.GetServerBoundCert("google.com",
375 &type,
376 &expiration_time,
377 &private_key,
378 &cert,
379 base::Bind(&GetCertCallbackNotCalled)));
380 EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type);
381 int delete2_finished = 0;
382 store.DeleteServerBoundCert("google.com",
383 base::Bind(&CallCounter, &delete2_finished));
384 ASSERT_EQ(1, delete2_finished);
385 EXPECT_EQ(0, store.GetCertCount());
386 EXPECT_TRUE(store.GetServerBoundCert("google.com",
387 &type,
388 &expiration_time,
389 &private_key,
390 &cert,
391 base::Bind(&GetCertCallbackNotCalled)));
392 EXPECT_EQ(CLIENT_CERT_INVALID_TYPE, type);
393 }
394
395 TEST(DefaultServerBoundCertStoreTest, TestAsyncDelete) {
396 scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
397 persistent_store->AddServerBoundCert(ServerBoundCertStore::ServerBoundCert(
398 "a.com",
399 CLIENT_CERT_RSA_SIGN,
400 base::Time::FromInternalValue(1),
401 base::Time::FromInternalValue(2),
402 "a", "b"));
403 persistent_store->AddServerBoundCert(ServerBoundCertStore::ServerBoundCert(
404 "b.com",
405 CLIENT_CERT_RSA_SIGN,
406 base::Time::FromInternalValue(3),
407 base::Time::FromInternalValue(4),
408 "c", "d"));
409 DefaultServerBoundCertStore store(persistent_store.get());
410 int delete_finished = 0;
411 store.DeleteServerBoundCert("a.com",
412 base::Bind(&CallCounter, &delete_finished));
413
414 AsyncGetCertHelper a_helper;
415 AsyncGetCertHelper b_helper;
416 SSLClientCertType type;
417 base::Time expiration_time;
418 std::string private_key;
419 std::string cert = "not set";
420 EXPECT_EQ(0, store.GetCertCount());
421 EXPECT_FALSE(store.GetServerBoundCert(
422 "a.com", &type, &expiration_time, &private_key, &cert,
423 base::Bind(&AsyncGetCertHelper::Callback, base::Unretained(&a_helper))));
424 EXPECT_FALSE(store.GetServerBoundCert(
425 "b.com", &type, &expiration_time, &private_key, &cert,
426 base::Bind(&AsyncGetCertHelper::Callback, base::Unretained(&b_helper))));
427
428 EXPECT_EQ(0, delete_finished);
429 EXPECT_FALSE(a_helper.called_);
430 EXPECT_FALSE(b_helper.called_);
431 // Wait for load & queued tasks.
432 MessageLoop::current()->RunUntilIdle();
433 EXPECT_EQ(1, delete_finished);
434 EXPECT_EQ(1, store.GetCertCount());
435 EXPECT_EQ("not set", cert);
436 EXPECT_TRUE(a_helper.called_);
437 EXPECT_EQ("a.com", a_helper.server_identifier_);
438 EXPECT_EQ(CLIENT_CERT_INVALID_TYPE, a_helper.type_);
439 EXPECT_EQ(0, a_helper.expiration_time_.ToInternalValue());
440 EXPECT_EQ("", a_helper.private_key_);
441 EXPECT_EQ("", a_helper.cert_);
442 EXPECT_TRUE(b_helper.called_);
443 EXPECT_EQ("b.com", b_helper.server_identifier_);
444 EXPECT_EQ(CLIENT_CERT_RSA_SIGN, b_helper.type_);
445 EXPECT_EQ(4, b_helper.expiration_time_.ToInternalValue());
446 EXPECT_EQ("c", b_helper.private_key_);
447 EXPECT_EQ("d", b_helper.cert_);
448 }
449
450 TEST(DefaultServerBoundCertStoreTest, TestGetAll) {
451 scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
452 DefaultServerBoundCertStore store(persistent_store.get());
453
454 EXPECT_EQ(0, store.GetCertCount());
455 store.SetServerBoundCert(
456 "verisign.com",
457 CLIENT_CERT_RSA_SIGN,
458 base::Time(),
459 base::Time(),
460 "a", "b");
461 store.SetServerBoundCert(
462 "google.com",
463 CLIENT_CERT_ECDSA_SIGN,
464 base::Time(),
465 base::Time(),
466 "c", "d");
467 store.SetServerBoundCert(
468 "harvard.com",
469 CLIENT_CERT_RSA_SIGN,
470 base::Time(),
471 base::Time(),
472 "e", "f");
473 store.SetServerBoundCert(
474 "mit.com",
475 CLIENT_CERT_RSA_SIGN,
476 base::Time(),
477 base::Time(),
478 "g", "h");
479 // Wait for load & queued set tasks.
480 MessageLoop::current()->RunUntilIdle();
481
482 EXPECT_EQ(4, store.GetCertCount());
483 ServerBoundCertStore::ServerBoundCertList certs;
484 store.GetAllServerBoundCerts(base::Bind(GetAllCallback, &certs));
485 EXPECT_EQ(4u, certs.size());
486 }
487
488 TEST(DefaultServerBoundCertStoreTest, TestInitializeFrom) {
489 scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
490 DefaultServerBoundCertStore store(persistent_store.get());
491
492 store.SetServerBoundCert(
493 "preexisting.com",
494 CLIENT_CERT_RSA_SIGN,
495 base::Time(),
496 base::Time(),
497 "a", "b");
498 store.SetServerBoundCert(
499 "both.com",
500 CLIENT_CERT_ECDSA_SIGN,
501 base::Time(),
502 base::Time(),
503 "c", "d");
504 // Wait for load & queued set tasks.
505 MessageLoop::current()->RunUntilIdle();
506 EXPECT_EQ(2, store.GetCertCount());
507
508 ServerBoundCertStore::ServerBoundCertList source_certs;
509 source_certs.push_back(ServerBoundCertStore::ServerBoundCert(
510 "both.com",
511 CLIENT_CERT_RSA_SIGN,
512 base::Time(),
513 base::Time(),
514 // Key differs from above to test that existing entries are overwritten.
515 "e", "f"));
516 source_certs.push_back(ServerBoundCertStore::ServerBoundCert(
517 "copied.com",
518 CLIENT_CERT_RSA_SIGN,
519 base::Time(),
520 base::Time(),
521 "g", "h"));
522 store.InitializeFrom(source_certs);
523 EXPECT_EQ(3, store.GetCertCount());
524
525 ServerBoundCertStore::ServerBoundCertList certs;
526 store.GetAllServerBoundCerts(base::Bind(GetAllCallback, &certs));
527 ASSERT_EQ(3u, certs.size());
528
529 ServerBoundCertStore::ServerBoundCertList::iterator cert = certs.begin();
530 EXPECT_EQ("both.com", cert->server_identifier());
531 EXPECT_EQ("e", cert->private_key());
532
533 ++cert;
534 EXPECT_EQ("copied.com", cert->server_identifier());
535 EXPECT_EQ("g", cert->private_key());
536
537 ++cert;
538 EXPECT_EQ("preexisting.com", cert->server_identifier());
539 EXPECT_EQ("a", cert->private_key());
540 }
541
542 TEST(DefaultServerBoundCertStoreTest, TestAsyncInitializeFrom) {
543 scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
544 persistent_store->AddServerBoundCert(ServerBoundCertStore::ServerBoundCert(
545 "preexisting.com",
546 CLIENT_CERT_RSA_SIGN,
547 base::Time(),
548 base::Time(),
549 "a", "b"));
550 persistent_store->AddServerBoundCert(ServerBoundCertStore::ServerBoundCert(
551 "both.com",
552 CLIENT_CERT_RSA_SIGN,
553 base::Time(),
554 base::Time(),
555 "c", "d"));
556
557 DefaultServerBoundCertStore store(persistent_store.get());
558 ServerBoundCertStore::ServerBoundCertList source_certs;
559 source_certs.push_back(ServerBoundCertStore::ServerBoundCert(
560 "both.com",
561 CLIENT_CERT_RSA_SIGN,
562 base::Time(),
563 base::Time(),
564 // Key differs from above to test that existing entries are overwritten.
565 "e", "f"));
566 source_certs.push_back(ServerBoundCertStore::ServerBoundCert(
567 "copied.com",
568 CLIENT_CERT_RSA_SIGN,
569 base::Time(),
570 base::Time(),
571 "g", "h"));
572 store.InitializeFrom(source_certs);
573 EXPECT_EQ(0, store.GetCertCount());
574 // Wait for load & queued tasks.
575 MessageLoop::current()->RunUntilIdle();
576 EXPECT_EQ(3, store.GetCertCount());
577
578 ServerBoundCertStore::ServerBoundCertList certs;
579 store.GetAllServerBoundCerts(base::Bind(GetAllCallback, &certs));
580 ASSERT_EQ(3u, certs.size());
581
582 ServerBoundCertStore::ServerBoundCertList::iterator cert = certs.begin();
583 EXPECT_EQ("both.com", cert->server_identifier());
584 EXPECT_EQ("e", cert->private_key());
585
586 ++cert;
587 EXPECT_EQ("copied.com", cert->server_identifier());
588 EXPECT_EQ("g", cert->private_key());
589
590 ++cert;
591 EXPECT_EQ("preexisting.com", cert->server_identifier());
592 EXPECT_EQ("a", cert->private_key());
593 }
594
595 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698