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

Side by Side Diff: net/tools/flip_server/spdy_interface.cc

Issue 2169503002: Remove flip_server. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 5 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 | « net/tools/flip_server/spdy_interface.h ('k') | net/tools/flip_server/spdy_interface_test.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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/tools/flip_server/spdy_interface.h"
6
7 #include <algorithm>
8 #include <string>
9 #include <utility>
10
11 #include "net/spdy/spdy_framer.h"
12 #include "net/spdy/spdy_protocol.h"
13 #include "net/tools/flip_server/constants.h"
14 #include "net/tools/flip_server/flip_config.h"
15 #include "net/tools/flip_server/http_interface.h"
16 #include "net/tools/flip_server/spdy_util.h"
17 #include "net/tools/flip_server/url_utilities.h"
18
19 namespace net {
20
21 // static
22 std::string SpdySM::forward_ip_header_;
23
24 class SpdyFrameDataFrame : public DataFrame {
25 public:
26 explicit SpdyFrameDataFrame(SpdySerializedFrame* spdy_frame)
27 : frame(spdy_frame) {
28 data = spdy_frame->data();
29 size = spdy_frame->size();
30 }
31
32 ~SpdyFrameDataFrame() override { delete frame; }
33
34 const SpdySerializedFrame* frame;
35 };
36
37 SpdySM::SpdySM(SMConnection* connection,
38 SMInterface* sm_http_interface,
39 EpollServer* epoll_server,
40 MemoryCache* memory_cache,
41 FlipAcceptor* acceptor)
42 : buffered_spdy_framer_(new BufferedSpdyFramer()),
43 valid_spdy_session_(false),
44 connection_(connection),
45 client_output_list_(connection->output_list()),
46 client_output_ordering_(connection),
47 next_outgoing_stream_id_(2),
48 epoll_server_(epoll_server),
49 acceptor_(acceptor),
50 memory_cache_(memory_cache),
51 close_on_error_(false) {
52 buffered_spdy_framer_->set_visitor(this);
53 }
54
55 SpdySM::~SpdySM() { }
56
57 void SpdySM::InitSMConnection(SMConnectionPoolInterface* connection_pool,
58 SMInterface* sm_interface,
59 EpollServer* epoll_server,
60 int fd,
61 std::string server_ip,
62 std::string server_port,
63 std::string remote_ip,
64 bool use_ssl) {
65 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Initializing server connection.";
66 connection_->InitSMConnection(connection_pool,
67 sm_interface,
68 epoll_server,
69 fd,
70 server_ip,
71 server_port,
72 remote_ip,
73 use_ssl);
74 }
75
76 SMInterface* SpdySM::NewConnectionInterface() {
77 SMConnection* server_connection =
78 SMConnection::NewSMConnection(epoll_server_,
79 NULL,
80 memory_cache_,
81 acceptor_,
82 "http_conn: ");
83 if (server_connection == NULL) {
84 LOG(ERROR) << "SpdySM: Could not create server connection";
85 return NULL;
86 }
87 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Creating new HTTP interface";
88 SMInterface* sm_http_interface =
89 new HttpSM(server_connection, this, memory_cache_, acceptor_);
90 return sm_http_interface;
91 }
92
93 SMInterface* SpdySM::FindOrMakeNewSMConnectionInterface(
94 const std::string& server_ip,
95 const std::string& server_port) {
96 SMInterface* sm_http_interface;
97 int32_t server_idx;
98 if (unused_server_interface_list.empty()) {
99 sm_http_interface = NewConnectionInterface();
100 server_idx = server_interface_list.size();
101 server_interface_list.push_back(sm_http_interface);
102 VLOG(2) << ACCEPTOR_CLIENT_IDENT
103 << "SpdySM: Making new server connection on index: " << server_idx;
104 } else {
105 server_idx = unused_server_interface_list.back();
106 unused_server_interface_list.pop_back();
107 sm_http_interface = server_interface_list.at(server_idx);
108 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Reusing connection on "
109 << "index: " << server_idx;
110 }
111
112 sm_http_interface->InitSMInterface(this, server_idx);
113 sm_http_interface->InitSMConnection(NULL,
114 sm_http_interface,
115 epoll_server_,
116 -1,
117 server_ip,
118 server_port,
119 std::string(),
120 false);
121
122 return sm_http_interface;
123 }
124
125 int SpdySM::SpdyHandleNewStream(SpdyStreamId stream_id,
126 SpdyPriority priority,
127 const SpdyHeaderBlock& headers,
128 std::string& http_data,
129 bool* is_https_scheme) {
130 *is_https_scheme = false;
131 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnSyn(" << stream_id << ")";
132 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: # headers: " << headers.size();
133
134 SpdyHeaderBlock::const_iterator method = headers.end();
135 SpdyHeaderBlock::const_iterator host = headers.end();
136 SpdyHeaderBlock::const_iterator path = headers.end();
137 SpdyHeaderBlock::const_iterator scheme = headers.end();
138 SpdyHeaderBlock::const_iterator version = headers.end();
139 SpdyHeaderBlock::const_iterator url = headers.end();
140
141 std::string path_string, host_string, version_string;
142
143 method = headers.find(":method");
144 host = headers.find(":host");
145 path = headers.find(":path");
146 scheme = headers.find(":scheme");
147 if (method == headers.end() || host == headers.end() ||
148 path == headers.end() || scheme == headers.end()) {
149 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: A mandatory header is "
150 << "missing. Not creating stream";
151 return 0;
152 }
153 host_string = host->second.as_string();
154 path_string = path->second.as_string();
155 version_string = "HTTP/1.1";
156
157 if (scheme->second.compare("https") == 0) {
158 *is_https_scheme = true;
159 }
160
161 if (acceptor_->flip_handler_type_ == FLIP_HANDLER_SPDY_SERVER) {
162 VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Request: " << method->second
163 << " " << path_string;
164 std::string filename =
165 EncodeURL(path_string, host_string, method->second.as_string());
166 NewStream(stream_id, priority, filename);
167 } else {
168 http_data += method->second.as_string() + " " + path_string + " " +
169 version_string + "\r\n";
170 VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Request: " << method->second << " "
171 << path_string << " " << version_string;
172 http_data += "Host: " + (*is_https_scheme ?
173 acceptor_->https_server_ip_ :
174 acceptor_->http_server_ip_) + "\r\n";
175 for (SpdyHeaderBlock::const_iterator i = headers.begin();
176 i != headers.end(); ++i) {
177 if ((i->first.size() > 0 && i->first[0] == ':') ||
178 i->first == "host" ||
179 i == method ||
180 i == host ||
181 i == path ||
182 i == scheme ||
183 i == version ||
184 i == url) {
185 // Ignore the entry.
186 } else {
187 http_data +=
188 i->first.as_string() + ": " + i->second.as_string() + "\r\n";
189 VLOG(2) << ACCEPTOR_CLIENT_IDENT << i->first << ":" << i->second;
190 }
191 }
192 if (forward_ip_header_.length()) {
193 // X-Client-Cluster-IP header
194 http_data += forward_ip_header_ + ": " +
195 connection_->client_ip() + "\r\n";
196 }
197 http_data += "\r\n";
198 }
199
200 VLOG(3) << ACCEPTOR_CLIENT_IDENT << "SpdySM: HTTP Request:\n" << http_data;
201 return 1;
202 }
203
204 void SpdySM::OnStreamFrameData(SpdyStreamId stream_id,
205 const char* data,
206 size_t len) {
207 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: StreamData(" << stream_id
208 << ", [" << len << "])";
209 StreamToSmif::iterator it = stream_to_smif_.find(stream_id);
210 if (it == stream_to_smif_.end()) {
211 VLOG(2) << "Dropping frame from unknown stream " << stream_id;
212 if (!valid_spdy_session_)
213 close_on_error_ = true;
214 return;
215 }
216
217 SMInterface* interface = it->second;
218 if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY)
219 interface->ProcessWriteInput(data, len);
220 }
221
222 void SpdySM::OnStreamEnd(SpdyStreamId stream_id) {
223 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: StreamEnd(" << stream_id << ")";
224 StreamToSmif::iterator it = stream_to_smif_.find(stream_id);
225 if (it == stream_to_smif_.end()) {
226 VLOG(2) << "Dropping frame from unknown stream " << stream_id;
227 if (!valid_spdy_session_)
228 close_on_error_ = true;
229 return;
230 }
231
232 SMInterface* interface = it->second;
233 if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY)
234 interface->ProcessWriteInput(nullptr, 0);
235 }
236
237 void SpdySM::OnStreamPadding(SpdyStreamId stream_id, size_t len) {
238 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: StreamPadding(" << stream_id
239 << ", [" << len << "])";
240 }
241
242 void SpdySM::OnHeaders(SpdyStreamId stream_id,
243 bool has_priority,
244 int weight,
245 SpdyStreamId parent_stream_id,
246 bool exclusive,
247 bool fin,
248 const SpdyHeaderBlock& headers) {
249 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnHeaders(" << stream_id << ")";
250 }
251
252 void SpdySM::OnRstStream(SpdyStreamId stream_id, SpdyRstStreamStatus status) {
253 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnRstStream(" << stream_id
254 << ")";
255 client_output_ordering_.RemoveStreamId(stream_id);
256 }
257
258 bool SpdySM::OnUnknownFrame(SpdyStreamId stream_id, int frame_type) {
259 return false;
260 }
261
262 size_t SpdySM::ProcessReadInput(const char* data, size_t len) {
263 DCHECK(buffered_spdy_framer_);
264 return buffered_spdy_framer_->ProcessInput(data, len);
265 }
266
267 size_t SpdySM::ProcessWriteInput(const char* data, size_t len) { return 0; }
268
269 bool SpdySM::MessageFullyRead() const {
270 DCHECK(buffered_spdy_framer_);
271 return buffered_spdy_framer_->MessageFullyRead();
272 }
273
274 bool SpdySM::Error() const {
275 DCHECK(buffered_spdy_framer_);
276 return close_on_error_ || buffered_spdy_framer_->HasError();
277 }
278
279 const char* SpdySM::ErrorAsString() const {
280 DCHECK(Error());
281 DCHECK(buffered_spdy_framer_);
282 return SpdyFramer::ErrorCodeToString(buffered_spdy_framer_->error_code());
283 }
284
285 void SpdySM::ResetForNewInterface(int32_t server_idx) {
286 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Reset for new interface: "
287 << "server_idx: " << server_idx;
288 unused_server_interface_list.push_back(server_idx);
289 }
290
291 void SpdySM::ResetForNewConnection() {
292 // seq_num is not cleared, intentionally.
293 buffered_spdy_framer_.reset();
294 valid_spdy_session_ = false;
295 client_output_ordering_.Reset();
296 next_outgoing_stream_id_ = 2;
297 }
298
299 // Send a settings frame
300 int SpdySM::PostAcceptHook() {
301 // We should have buffered_spdy_framer_ set after reuse
302 DCHECK(buffered_spdy_framer_);
303 SettingsMap settings;
304 settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
305 SettingsFlagsAndValue(SETTINGS_FLAG_NONE, 100);
306 SpdySerializedFrame* settings_frame =
307 buffered_spdy_framer_->CreateSettings(settings);
308
309 VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Sending Settings Frame";
310 EnqueueDataFrame(new SpdyFrameDataFrame(settings_frame));
311 return 1;
312 }
313
314 void SpdySM::NewStream(uint32_t stream_id,
315 uint32_t priority,
316 const std::string& filename) {
317 MemCacheIter mci;
318 mci.stream_id = stream_id;
319 mci.priority = priority;
320 // TODO(yhirano): The program will crash when
321 // acceptor_->flip_handler_type_ != FLIP_HANDLER_SPDY_SERVER.
322 // It should be fixed or an assertion should be placed.
323 if (acceptor_->flip_handler_type_ == FLIP_HANDLER_SPDY_SERVER) {
324 if (!memory_cache_->AssignFileData(filename, &mci)) {
325 // error creating new stream.
326 VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Sending ErrorNotFound";
327 SendErrorNotFound(stream_id);
328 } else {
329 AddToOutputOrder(mci);
330 }
331 } else {
332 AddToOutputOrder(mci);
333 }
334 }
335
336 void SpdySM::AddToOutputOrder(const MemCacheIter& mci) {
337 client_output_ordering_.AddToOutputOrder(mci);
338 }
339
340 void SpdySM::SendEOF(uint32_t stream_id) {
341 SendEOFImpl(stream_id);
342 }
343
344 void SpdySM::SendErrorNotFound(uint32_t stream_id) {
345 SendErrorNotFoundImpl(stream_id);
346 }
347
348 size_t SpdySM::SendSynStream(uint32_t stream_id, const BalsaHeaders& headers) {
349 return SendSynStreamImpl(stream_id, headers);
350 }
351
352 size_t SpdySM::SendSynReply(uint32_t stream_id, const BalsaHeaders& headers) {
353 return SendSynReplyImpl(stream_id, headers);
354 }
355
356 void SpdySM::SendDataFrame(uint32_t stream_id,
357 const char* data,
358 int64_t len,
359 uint32_t flags,
360 bool compress) {
361 SpdyDataFlags spdy_flags = static_cast<SpdyDataFlags>(flags);
362 SendDataFrameImpl(stream_id, data, len, spdy_flags, compress);
363 }
364
365 void SpdySM::SendEOFImpl(uint32_t stream_id) {
366 SendDataFrame(stream_id, NULL, 0, DATA_FLAG_FIN, false);
367 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending EOF: " << stream_id;
368 KillStream(stream_id);
369 stream_to_smif_.erase(stream_id);
370 }
371
372 void SpdySM::SendErrorNotFoundImpl(uint32_t stream_id) {
373 BalsaHeaders my_headers;
374 my_headers.SetFirstlineFromStringPieces("HTTP/1.1", "404", "Not Found");
375 SendSynReplyImpl(stream_id, my_headers);
376 SendDataFrame(stream_id, "wtf?", 4, DATA_FLAG_FIN, false);
377 client_output_ordering_.RemoveStreamId(stream_id);
378 }
379
380 void SpdySM::KillStream(uint32_t stream_id) {
381 client_output_ordering_.RemoveStreamId(stream_id);
382 }
383
384 void SpdySM::CopyHeaders(SpdyHeaderBlock& dest, const BalsaHeaders& headers) {
385 for (BalsaHeaders::const_header_lines_iterator hi =
386 headers.header_lines_begin();
387 hi != headers.header_lines_end();
388 ++hi) {
389 // It is illegal to send SPDY headers with empty value or header
390 // names.
391 if (!hi->first.length() || !hi->second.length())
392 continue;
393
394 // Key must be all lower case in SPDY headers.
395 std::string key = hi->first.as_string();
396 std::transform(key.begin(), key.end(), key.begin(), ::tolower);
397 SpdyHeaderBlock::iterator fhi = dest.find(key);
398 if (fhi == dest.end()) {
399 dest[key] = hi->second.as_string();
400 } else {
401 dest[key] = (std::string(fhi->second.data(), fhi->second.size()) + "\0" +
402 std::string(hi->second.data(), hi->second.size()));
403 }
404 }
405
406 // These headers have no value
407 dest.erase("X-Associated-Content"); // TODO(mbelshe): case-sensitive
408 dest.erase("X-Original-Url"); // TODO(mbelshe): case-sensitive
409 }
410
411 size_t SpdySM::SendSynStreamImpl(uint32_t stream_id,
412 const BalsaHeaders& headers) {
413 SpdyHeaderBlock block;
414 CopyHeaders(block, headers);
415 block[":method"] = headers.request_method().as_string();
416 block[":version"] = headers.request_version().as_string();
417 if (headers.HasHeader("X-Original-Url")) {
418 std::string original_url = headers.GetHeader("X-Original-Url").as_string();
419 block[":path"] = UrlUtilities::GetUrlPath(original_url);
420 block[":host"] = UrlUtilities::GetUrlPath(original_url);
421 } else {
422 block[":path"] = headers.request_uri().as_string();
423 if (block.find("host") != block.end()) {
424 block[":host"] = headers.GetHeader("Host").as_string();
425 block.erase("host");
426 }
427 }
428
429 DCHECK(buffered_spdy_framer_);
430 SpdySerializedFrame* fsrcf = buffered_spdy_framer_->CreateSynStream(
431 stream_id, 0, 0, CONTROL_FLAG_NONE, std::move(block));
432 size_t df_size = fsrcf->size();
433 EnqueueDataFrame(new SpdyFrameDataFrame(fsrcf));
434
435 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending SynStreamheader "
436 << stream_id;
437 return df_size;
438 }
439
440 size_t SpdySM::SendSynReplyImpl(uint32_t stream_id,
441 const BalsaHeaders& headers) {
442 SpdyHeaderBlock block;
443 CopyHeaders(block, headers);
444 block[":status"] = headers.response_code().as_string() + " " +
445 headers.response_reason_phrase().as_string();
446 block[":version"] = headers.response_version().as_string();
447
448 DCHECK(buffered_spdy_framer_);
449 SpdySerializedFrame* fsrcf = buffered_spdy_framer_->CreateSynReply(
450 stream_id, CONTROL_FLAG_NONE, std::move(block));
451 size_t df_size = fsrcf->size();
452 EnqueueDataFrame(new SpdyFrameDataFrame(fsrcf));
453
454 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending SynReplyheader "
455 << stream_id;
456 return df_size;
457 }
458
459 void SpdySM::SendDataFrameImpl(uint32_t stream_id,
460 const char* data,
461 int64_t len,
462 SpdyDataFlags flags,
463 bool compress) {
464 DCHECK(buffered_spdy_framer_);
465 // TODO(mbelshe): We can't compress here - before going into the
466 // priority queue. Compression needs to be done
467 // with late binding.
468 if (len == 0) {
469 SpdySerializedFrame* fdf =
470 buffered_spdy_framer_->CreateDataFrame(stream_id, data, len, flags);
471 EnqueueDataFrame(new SpdyFrameDataFrame(fdf));
472 return;
473 }
474
475 // Chop data frames into chunks so that one stream can't monopolize the
476 // output channel.
477 while (len > 0) {
478 int64_t size = std::min(len, static_cast<int64_t>(kSpdySegmentSize));
479 SpdyDataFlags chunk_flags = flags;
480
481 // If we chunked this block, and the FIN flag was set, there is more
482 // data coming. So, remove the flag.
483 if ((size < len) && (flags & DATA_FLAG_FIN))
484 chunk_flags = static_cast<SpdyDataFlags>(chunk_flags & ~DATA_FLAG_FIN);
485
486 SpdySerializedFrame* fdf = buffered_spdy_framer_->CreateDataFrame(
487 stream_id, data, size, chunk_flags);
488 EnqueueDataFrame(new SpdyFrameDataFrame(fdf));
489
490 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending data frame "
491 << stream_id << " [" << size << "] shrunk to "
492 << (fdf->size() - kSpdyOverhead) << ", flags=" << flags;
493
494 data += size;
495 len -= size;
496 }
497 }
498
499 void SpdySM::EnqueueDataFrame(DataFrame* df) {
500 connection_->EnqueueDataFrame(df);
501 }
502
503 void SpdySM::GetOutput() {
504 while (client_output_list_->size() < 2) {
505 MemCacheIter* mci = client_output_ordering_.GetIter();
506 if (mci == NULL) {
507 VLOG(2) << ACCEPTOR_CLIENT_IDENT
508 << "SpdySM: GetOutput: nothing to output!?";
509 return;
510 }
511 if (!mci->transformed_header) {
512 mci->transformed_header = true;
513 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput transformed "
514 << "header stream_id: [" << mci->stream_id << "]";
515 if ((mci->stream_id % 2) == 0) {
516 // this is a server initiated stream.
517 // Ideally, we'd do a 'syn-push' here, instead of a syn-reply.
518 BalsaHeaders headers;
519 headers.CopyFrom(*(mci->file_data->headers()));
520 headers.ReplaceOrAppendHeader("status", "200");
521 headers.ReplaceOrAppendHeader("version", "http/1.1");
522 headers.SetRequestFirstlineFromStringPieces(
523 "PUSH", mci->file_data->filename(), "");
524 mci->bytes_sent = SendSynStream(mci->stream_id, headers);
525 } else {
526 BalsaHeaders headers;
527 headers.CopyFrom(*(mci->file_data->headers()));
528 mci->bytes_sent = SendSynReply(mci->stream_id, headers);
529 }
530 return;
531 }
532 if (mci->body_bytes_consumed >= mci->file_data->body().size()) {
533 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput "
534 << "remove_stream_id: [" << mci->stream_id << "]";
535 SendEOF(mci->stream_id);
536 return;
537 }
538 size_t num_to_write =
539 mci->file_data->body().size() - mci->body_bytes_consumed;
540 if (num_to_write > mci->max_segment_size)
541 num_to_write = mci->max_segment_size;
542
543 bool should_compress = false;
544 if (!mci->file_data->headers()->HasHeader("content-encoding")) {
545 if (mci->file_data->headers()->HasHeader("content-type")) {
546 std::string content_type =
547 mci->file_data->headers()->GetHeader("content-type").as_string();
548 if (content_type.find("image") == content_type.npos)
549 should_compress = true;
550 }
551 }
552
553 SendDataFrame(mci->stream_id,
554 mci->file_data->body().data() + mci->body_bytes_consumed,
555 num_to_write,
556 0,
557 should_compress);
558 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput SendDataFrame["
559 << mci->stream_id << "]: " << num_to_write;
560 mci->body_bytes_consumed += num_to_write;
561 mci->bytes_sent += num_to_write;
562 }
563 }
564
565 void SpdySM::CreateFramer() {
566 DCHECK(!buffered_spdy_framer_);
567 buffered_spdy_framer_.reset(new BufferedSpdyFramer());
568 buffered_spdy_framer_->set_visitor(this);
569 }
570
571 } // namespace net
OLDNEW
« no previous file with comments | « net/tools/flip_server/spdy_interface.h ('k') | net/tools/flip_server/spdy_interface_test.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698