1 // Copyright 2015 The Chromium Authors
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 "cronet_bidirectional_stream_adapter.h"
6
7 #include <string>
8 #include <utility>
9 #include <vector>
10
11 #include "base/functional/bind.h"
12 #include "base/location.h"
13 #include "base/logging.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "components/cronet/android/cronet_context_adapter.h"
16 #include "components/cronet/android/cronet_jni_headers/CronetBidirectionalStream_jni.h"
17 #include "components/cronet/android/io_buffer_with_byte_buffer.h"
18 #include "components/cronet/android/url_request_error.h"
19 #include "components/cronet/metrics_util.h"
20 #include "net/base/http_user_agent_settings.h"
21 #include "net/base/net_errors.h"
22 #include "net/base/request_priority.h"
23 #include "net/http/bidirectional_stream_request_info.h"
24 #include "net/http/http_network_session.h"
25 #include "net/http/http_response_headers.h"
26 #include "net/http/http_status_code.h"
27 #include "net/http/http_transaction_factory.h"
28 #include "net/http/http_util.h"
29 #include "net/ssl/ssl_info.h"
30 #include "net/third_party/quiche/src/quiche/quic/core/quic_packets.h"
31 #include "net/third_party/quiche/src/quiche/spdy/core/http2_header_block.h"
32 #include "net/url_request/url_request_context.h"
33 #include "url/gurl.h"
34
35 using base::android::ConvertUTF8ToJavaString;
36 using base::android::ConvertJavaStringToUTF8;
37 using base::android::JavaRef;
38 using base::android::ScopedJavaLocalRef;
39
40 namespace cronet {
41
42 namespace {
43
44 // As |GetArrayLength| makes no guarantees about the returned value (e.g., it
45 // may be -1 if |array| is not a valid Java array), provide a safe wrapper
46 // that always returns a valid, non-negative size.
47 template <typename JavaArrayType>
SafeGetArrayLength(JNIEnv * env,JavaArrayType jarray)48 size_t SafeGetArrayLength(JNIEnv* env, JavaArrayType jarray) {
49 DCHECK(jarray);
50 jsize length = env->GetArrayLength(jarray);
51 DCHECK_GE(length, 0) << "Invalid array length: " << length;
52 return static_cast<size_t>(std::max(0, length));
53 }
54
55 } // namespace
56
PendingWriteData(JNIEnv * env,const JavaRef<jobjectArray> & jwrite_buffer_list,const JavaRef<jintArray> & jwrite_buffer_pos_list,const JavaRef<jintArray> & jwrite_buffer_limit_list,jboolean jwrite_end_of_stream)57 PendingWriteData::PendingWriteData(
58 JNIEnv* env,
59 const JavaRef<jobjectArray>& jwrite_buffer_list,
60 const JavaRef<jintArray>& jwrite_buffer_pos_list,
61 const JavaRef<jintArray>& jwrite_buffer_limit_list,
62 jboolean jwrite_end_of_stream) {
63 this->jwrite_buffer_list.Reset(jwrite_buffer_list);
64 this->jwrite_buffer_pos_list.Reset(jwrite_buffer_pos_list);
65 this->jwrite_buffer_limit_list.Reset(jwrite_buffer_limit_list);
66 this->jwrite_end_of_stream = jwrite_end_of_stream;
67 }
68
~PendingWriteData()69 PendingWriteData::~PendingWriteData() {
70 // Reset global references.
71 jwrite_buffer_list.Reset();
72 jwrite_buffer_pos_list.Reset();
73 jwrite_buffer_limit_list.Reset();
74 }
75
JNI_CronetBidirectionalStream_CreateBidirectionalStream(JNIEnv * env,const base::android::JavaParamRef<jobject> & jbidi_stream,jlong jurl_request_context_adapter,jboolean jsend_request_headers_automatically,jboolean jtraffic_stats_tag_set,jint jtraffic_stats_tag,jboolean jtraffic_stats_uid_set,jint jtraffic_stats_uid,jlong jnetwork_handle)76 static jlong JNI_CronetBidirectionalStream_CreateBidirectionalStream(
77 JNIEnv* env,
78 const base::android::JavaParamRef<jobject>& jbidi_stream,
79 jlong jurl_request_context_adapter,
80 jboolean jsend_request_headers_automatically,
81 jboolean jtraffic_stats_tag_set,
82 jint jtraffic_stats_tag,
83 jboolean jtraffic_stats_uid_set,
84 jint jtraffic_stats_uid,
85 jlong jnetwork_handle) {
86 CronetContextAdapter* context_adapter =
87 reinterpret_cast<CronetContextAdapter*>(jurl_request_context_adapter);
88 DCHECK(context_adapter);
89
90 CronetBidirectionalStreamAdapter* adapter =
91 new CronetBidirectionalStreamAdapter(
92 context_adapter, env, jbidi_stream,
93 jsend_request_headers_automatically, jtraffic_stats_tag_set,
94 jtraffic_stats_tag, jtraffic_stats_uid_set, jtraffic_stats_uid,
95 jnetwork_handle);
96
97 return reinterpret_cast<jlong>(adapter);
98 }
99
CronetBidirectionalStreamAdapter(CronetContextAdapter * context,JNIEnv * env,const base::android::JavaParamRef<jobject> & jbidi_stream,bool send_request_headers_automatically,bool traffic_stats_tag_set,int32_t traffic_stats_tag,bool traffic_stats_uid_set,int32_t traffic_stats_uid,net::handles::NetworkHandle network)100 CronetBidirectionalStreamAdapter::CronetBidirectionalStreamAdapter(
101 CronetContextAdapter* context,
102 JNIEnv* env,
103 const base::android::JavaParamRef<jobject>& jbidi_stream,
104 bool send_request_headers_automatically,
105 bool traffic_stats_tag_set,
106 int32_t traffic_stats_tag,
107 bool traffic_stats_uid_set,
108 int32_t traffic_stats_uid,
109 net::handles::NetworkHandle network)
110 : context_(context),
111 owner_(env, jbidi_stream),
112 send_request_headers_automatically_(send_request_headers_automatically),
113 traffic_stats_tag_set_(traffic_stats_tag_set),
114 traffic_stats_tag_(traffic_stats_tag),
115 traffic_stats_uid_set_(traffic_stats_uid_set),
116 traffic_stats_uid_(traffic_stats_uid),
117 network_(network),
118 stream_failed_(false) {}
119
~CronetBidirectionalStreamAdapter()120 CronetBidirectionalStreamAdapter::~CronetBidirectionalStreamAdapter() {
121 DCHECK(context_->IsOnNetworkThread());
122 }
123
SendRequestHeaders(JNIEnv * env,const base::android::JavaParamRef<jobject> & jcaller)124 void CronetBidirectionalStreamAdapter::SendRequestHeaders(
125 JNIEnv* env,
126 const base::android::JavaParamRef<jobject>& jcaller) {
127 context_->PostTaskToNetworkThread(
128 FROM_HERE,
129 base::BindOnce(
130 &CronetBidirectionalStreamAdapter::SendRequestHeadersOnNetworkThread,
131 base::Unretained(this)));
132 }
133
Start(JNIEnv * env,const base::android::JavaParamRef<jobject> & jcaller,const base::android::JavaParamRef<jstring> & jurl,jint jpriority,const base::android::JavaParamRef<jstring> & jmethod,const base::android::JavaParamRef<jobjectArray> & jheaders,jboolean jend_of_stream)134 jint CronetBidirectionalStreamAdapter::Start(
135 JNIEnv* env,
136 const base::android::JavaParamRef<jobject>& jcaller,
137 const base::android::JavaParamRef<jstring>& jurl,
138 jint jpriority,
139 const base::android::JavaParamRef<jstring>& jmethod,
140 const base::android::JavaParamRef<jobjectArray>& jheaders,
141 jboolean jend_of_stream) {
142 // Prepare request info here to be able to return the error.
143 std::unique_ptr<net::BidirectionalStreamRequestInfo> request_info(
144 new net::BidirectionalStreamRequestInfo());
145 request_info->url = GURL(ConvertJavaStringToUTF8(env, jurl));
146 request_info->priority = static_cast<net::RequestPriority>(jpriority);
147 // Http method is a token, just as header name.
148 request_info->method = ConvertJavaStringToUTF8(env, jmethod);
149 if (!net::HttpUtil::IsValidHeaderName(request_info->method))
150 return -1;
151
152 std::vector<std::string> headers;
153 base::android::AppendJavaStringArrayToStringVector(env, jheaders, &headers);
154 for (size_t i = 0; i < headers.size(); i += 2) {
155 std::string name(headers[i]);
156 std::string value(headers[i + 1]);
157 if (!net::HttpUtil::IsValidHeaderName(name) ||
158 !net::HttpUtil::IsValidHeaderValue(value)) {
159 return i + 1;
160 }
161 request_info->extra_headers.SetHeader(name, value);
162 }
163 request_info->end_stream_on_headers = jend_of_stream;
164 if (traffic_stats_tag_set_ || traffic_stats_uid_set_) {
165 request_info->socket_tag = net::SocketTag(
166 traffic_stats_uid_set_ ? traffic_stats_uid_ : net::SocketTag::UNSET_UID,
167 traffic_stats_tag_set_ ? traffic_stats_tag_
168 : net::SocketTag::UNSET_TAG);
169 }
170
171 context_->PostTaskToNetworkThread(
172 FROM_HERE,
173 base::BindOnce(&CronetBidirectionalStreamAdapter::StartOnNetworkThread,
174 base::Unretained(this), std::move(request_info)));
175 return 0;
176 }
177
ReadData(JNIEnv * env,const base::android::JavaParamRef<jobject> & jcaller,const base::android::JavaParamRef<jobject> & jbyte_buffer,jint jposition,jint jlimit)178 jboolean CronetBidirectionalStreamAdapter::ReadData(
179 JNIEnv* env,
180 const base::android::JavaParamRef<jobject>& jcaller,
181 const base::android::JavaParamRef<jobject>& jbyte_buffer,
182 jint jposition,
183 jint jlimit) {
184 DCHECK_LT(jposition, jlimit);
185
186 void* data = env->GetDirectBufferAddress(jbyte_buffer);
187 if (!data)
188 return JNI_FALSE;
189
190 scoped_refptr<IOBufferWithByteBuffer> read_buffer(
191 new IOBufferWithByteBuffer(env, jbyte_buffer, data, jposition, jlimit));
192
193 int remaining_capacity = jlimit - jposition;
194
195 context_->PostTaskToNetworkThread(
196 FROM_HERE,
197 base::BindOnce(&CronetBidirectionalStreamAdapter::ReadDataOnNetworkThread,
198 base::Unretained(this), read_buffer, remaining_capacity));
199 return JNI_TRUE;
200 }
201
WritevData(JNIEnv * env,const base::android::JavaParamRef<jobject> & jcaller,const base::android::JavaParamRef<jobjectArray> & jbyte_buffers,const base::android::JavaParamRef<jintArray> & jbyte_buffers_pos,const base::android::JavaParamRef<jintArray> & jbyte_buffers_limit,jboolean jend_of_stream)202 jboolean CronetBidirectionalStreamAdapter::WritevData(
203 JNIEnv* env,
204 const base::android::JavaParamRef<jobject>& jcaller,
205 const base::android::JavaParamRef<jobjectArray>& jbyte_buffers,
206 const base::android::JavaParamRef<jintArray>& jbyte_buffers_pos,
207 const base::android::JavaParamRef<jintArray>& jbyte_buffers_limit,
208 jboolean jend_of_stream) {
209 size_t buffers_array_size = SafeGetArrayLength(env, jbyte_buffers.obj());
210 size_t pos_array_size = SafeGetArrayLength(env, jbyte_buffers.obj());
211 size_t limit_array_size = SafeGetArrayLength(env, jbyte_buffers.obj());
212 if (buffers_array_size != pos_array_size ||
213 buffers_array_size != limit_array_size) {
214 DLOG(ERROR) << "Illegal arguments.";
215 return JNI_FALSE;
216 }
217
218 std::unique_ptr<PendingWriteData> pending_write_data;
219 pending_write_data.reset(
220 new PendingWriteData(env, jbyte_buffers, jbyte_buffers_pos,
221 jbyte_buffers_limit, jend_of_stream));
222 for (size_t i = 0; i < buffers_array_size; ++i) {
223 ScopedJavaLocalRef<jobject> jbuffer(
224 env, env->GetObjectArrayElement(
225 pending_write_data->jwrite_buffer_list.obj(), i));
226 void* data = env->GetDirectBufferAddress(jbuffer.obj());
227 if (!data)
228 return JNI_FALSE;
229 jint pos;
230 env->GetIntArrayRegion(pending_write_data->jwrite_buffer_pos_list.obj(), i,
231 1, &pos);
232 jint limit;
233 env->GetIntArrayRegion(pending_write_data->jwrite_buffer_limit_list.obj(),
234 i, 1, &limit);
235 auto write_buffer = base::MakeRefCounted<net::WrappedIOBuffer>(
236 base::make_span(static_cast<char*>(data), static_cast<size_t>(limit))
237 .subspan(pos));
238 pending_write_data->write_buffer_list.push_back(write_buffer);
239 pending_write_data->write_buffer_len_list.push_back(write_buffer->size());
240 }
241
242 context_->PostTaskToNetworkThread(
243 FROM_HERE,
244 base::BindOnce(
245 &CronetBidirectionalStreamAdapter::WritevDataOnNetworkThread,
246 base::Unretained(this), std::move(pending_write_data)));
247 return JNI_TRUE;
248 }
249
Destroy(JNIEnv * env,const base::android::JavaParamRef<jobject> & jcaller,jboolean jsend_on_canceled)250 void CronetBidirectionalStreamAdapter::Destroy(
251 JNIEnv* env,
252 const base::android::JavaParamRef<jobject>& jcaller,
253 jboolean jsend_on_canceled) {
254 // Destroy could be called from any thread, including network thread (if
255 // posting task to executor throws an exception), but is posted, so |this|
256 // is valid until calling task is complete. Destroy() is always called from
257 // within a synchronized java block that guarantees no future posts to the
258 // network thread with the adapter pointer.
259 context_->PostTaskToNetworkThread(
260 FROM_HERE,
261 base::BindOnce(&CronetBidirectionalStreamAdapter::DestroyOnNetworkThread,
262 base::Unretained(this), jsend_on_canceled));
263 }
264
OnStreamReady(bool request_headers_sent)265 void CronetBidirectionalStreamAdapter::OnStreamReady(
266 bool request_headers_sent) {
267 DCHECK(context_->IsOnNetworkThread());
268 JNIEnv* env = base::android::AttachCurrentThread();
269 cronet::Java_CronetBidirectionalStream_onStreamReady(
270 env, owner_, request_headers_sent ? JNI_TRUE : JNI_FALSE);
271 }
272
OnHeadersReceived(const spdy::Http2HeaderBlock & response_headers)273 void CronetBidirectionalStreamAdapter::OnHeadersReceived(
274 const spdy::Http2HeaderBlock& response_headers) {
275 DCHECK(context_->IsOnNetworkThread());
276 JNIEnv* env = base::android::AttachCurrentThread();
277 // Get http status code from response headers.
278 jint http_status_code = 0;
279 const auto http_status_header = response_headers.find(":status");
280 if (http_status_header != response_headers.end()) {
281 base::StringToInt(http_status_header->second, &http_status_code);
282 }
283
284 std::string protocol;
285 switch (bidi_stream_->GetProtocol()) {
286 case net::kProtoHTTP2:
287 protocol = "h2";
288 break;
289 case net::kProtoQUIC:
290 protocol = "quic/1+spdy/3";
291 break;
292 default:
293 break;
294 }
295
296 cronet::Java_CronetBidirectionalStream_onResponseHeadersReceived(
297 env, owner_, http_status_code, ConvertUTF8ToJavaString(env, protocol),
298 GetHeadersArray(env, response_headers),
299 bidi_stream_->GetTotalReceivedBytes());
300 }
301
OnDataRead(int bytes_read)302 void CronetBidirectionalStreamAdapter::OnDataRead(int bytes_read) {
303 DCHECK(context_->IsOnNetworkThread());
304 JNIEnv* env = base::android::AttachCurrentThread();
305 cronet::Java_CronetBidirectionalStream_onReadCompleted(
306 env, owner_, read_buffer_->byte_buffer(), bytes_read,
307 read_buffer_->initial_position(), read_buffer_->initial_limit(),
308 bidi_stream_->GetTotalReceivedBytes());
309 // Free the read buffer. This lets the Java ByteBuffer be freed, if the
310 // embedder releases it, too.
311 read_buffer_ = nullptr;
312 }
313
OnDataSent()314 void CronetBidirectionalStreamAdapter::OnDataSent() {
315 DCHECK(context_->IsOnNetworkThread());
316 DCHECK(pending_write_data_);
317
318 JNIEnv* env = base::android::AttachCurrentThread();
319 // Call into Java.
320 cronet::Java_CronetBidirectionalStream_onWritevCompleted(
321 env, owner_, pending_write_data_->jwrite_buffer_list,
322 pending_write_data_->jwrite_buffer_pos_list,
323 pending_write_data_->jwrite_buffer_limit_list,
324 pending_write_data_->jwrite_end_of_stream);
325 // Free the java objects. This lets the Java ByteBuffers be freed, if the
326 // embedder releases it, too.
327 pending_write_data_.reset();
328 }
329
OnTrailersReceived(const spdy::Http2HeaderBlock & response_trailers)330 void CronetBidirectionalStreamAdapter::OnTrailersReceived(
331 const spdy::Http2HeaderBlock& response_trailers) {
332 DCHECK(context_->IsOnNetworkThread());
333 JNIEnv* env = base::android::AttachCurrentThread();
334 cronet::Java_CronetBidirectionalStream_onResponseTrailersReceived(
335 env, owner_, GetHeadersArray(env, response_trailers));
336 }
337
OnFailed(int error)338 void CronetBidirectionalStreamAdapter::OnFailed(int error) {
339 DCHECK(context_->IsOnNetworkThread());
340 stream_failed_ = true;
341 JNIEnv* env = base::android::AttachCurrentThread();
342 net::NetErrorDetails net_error_details;
343 bidi_stream_->PopulateNetErrorDetails(&net_error_details);
344 cronet::Java_CronetBidirectionalStream_onError(
345 env, owner_, NetErrorToUrlRequestError(error), error,
346 net_error_details.quic_connection_error,
347 ConvertUTF8ToJavaString(env, net::ErrorToString(error)),
348 bidi_stream_->GetTotalReceivedBytes());
349 }
350
StartOnNetworkThread(std::unique_ptr<net::BidirectionalStreamRequestInfo> request_info)351 void CronetBidirectionalStreamAdapter::StartOnNetworkThread(
352 std::unique_ptr<net::BidirectionalStreamRequestInfo> request_info) {
353 DCHECK(context_->IsOnNetworkThread());
354 DCHECK(!bidi_stream_);
355
356 request_info->detect_broken_connection =
357 context_->cronet_url_request_context()
358 ->bidi_stream_detect_broken_connection();
359 request_info->heartbeat_interval =
360 context_->cronet_url_request_context()->heartbeat_interval();
361 request_info->extra_headers.SetHeaderIfMissing(
362 net::HttpRequestHeaders::kUserAgent,
363 context_->GetURLRequestContext(network_)
364 ->http_user_agent_settings()
365 ->GetUserAgent());
366 bidi_stream_.reset(
367 new net::BidirectionalStream(std::move(request_info),
368 context_->GetURLRequestContext(network_)
369 ->http_transaction_factory()
370 ->GetSession(),
371 send_request_headers_automatically_, this));
372 }
373
SendRequestHeadersOnNetworkThread()374 void CronetBidirectionalStreamAdapter::SendRequestHeadersOnNetworkThread() {
375 DCHECK(context_->IsOnNetworkThread());
376 DCHECK(!send_request_headers_automatically_);
377
378 if (stream_failed_) {
379 // If stream failed between the time when SendRequestHeaders is invoked and
380 // SendRequestHeadersOnNetworkThread is executed, do not call into
381 // |bidi_stream_| since the underlying stream might have been destroyed.
382 // Do not invoke Java callback either, since onError is posted when
383 // |stream_failed_| is set to true.
384 return;
385 }
386 bidi_stream_->SendRequestHeaders();
387 }
388
ReadDataOnNetworkThread(scoped_refptr<IOBufferWithByteBuffer> read_buffer,int buffer_size)389 void CronetBidirectionalStreamAdapter::ReadDataOnNetworkThread(
390 scoped_refptr<IOBufferWithByteBuffer> read_buffer,
391 int buffer_size) {
392 DCHECK(context_->IsOnNetworkThread());
393 DCHECK(read_buffer);
394 DCHECK(!read_buffer_);
395
396 read_buffer_ = read_buffer;
397
398 int bytes_read = bidi_stream_->ReadData(read_buffer_.get(), buffer_size);
399 // If IO is pending, wait for the BidirectionalStream to call OnDataRead.
400 if (bytes_read == net::ERR_IO_PENDING)
401 return;
402
403 if (bytes_read < 0) {
404 OnFailed(bytes_read);
405 return;
406 }
407 OnDataRead(bytes_read);
408 }
409
WritevDataOnNetworkThread(std::unique_ptr<PendingWriteData> pending_write_data)410 void CronetBidirectionalStreamAdapter::WritevDataOnNetworkThread(
411 std::unique_ptr<PendingWriteData> pending_write_data) {
412 DCHECK(context_->IsOnNetworkThread());
413 DCHECK(pending_write_data);
414 DCHECK(!pending_write_data_);
415
416 if (stream_failed_) {
417 // If stream failed between the time when WritevData is invoked and
418 // WritevDataOnNetworkThread is executed, do not call into |bidi_stream_|
419 // since the underlying stream might have been destroyed. Do not invoke
420 // Java callback either, since onError is posted when |stream_failed_| is
421 // set to true.
422 return;
423 }
424
425 pending_write_data_ = std::move(pending_write_data);
426 bool end_of_stream = pending_write_data_->jwrite_end_of_stream == JNI_TRUE;
427 bidi_stream_->SendvData(pending_write_data_->write_buffer_list,
428 pending_write_data_->write_buffer_len_list,
429 end_of_stream);
430 }
431
DestroyOnNetworkThread(bool send_on_canceled)432 void CronetBidirectionalStreamAdapter::DestroyOnNetworkThread(
433 bool send_on_canceled) {
434 DCHECK(context_->IsOnNetworkThread());
435 if (send_on_canceled) {
436 JNIEnv* env = base::android::AttachCurrentThread();
437 cronet::Java_CronetBidirectionalStream_onCanceled(env, owner_);
438 }
439 MaybeReportMetrics();
440 delete this;
441 }
442
443 base::android::ScopedJavaLocalRef<jobjectArray>
GetHeadersArray(JNIEnv * env,const spdy::Http2HeaderBlock & header_block)444 CronetBidirectionalStreamAdapter::GetHeadersArray(
445 JNIEnv* env,
446 const spdy::Http2HeaderBlock& header_block) {
447 DCHECK(context_->IsOnNetworkThread());
448
449 std::vector<std::string> headers;
450 for (const auto& header : header_block) {
451 auto value = std::string(header.second);
452 size_t start = 0;
453 size_t end = 0;
454 // The do loop will split headers by '\0' so that applications can skip it.
455 do {
456 end = value.find('\0', start);
457 std::string split_value;
458 if (end != value.npos) {
459 split_value = value.substr(start, end - start);
460 } else {
461 split_value = value.substr(start);
462 }
463 headers.push_back(std::string(header.first));
464 headers.push_back(split_value);
465 start = end + 1;
466 } while (end != value.npos);
467 }
468 return base::android::ToJavaArrayOfStrings(env, headers);
469 }
470
MaybeReportMetrics()471 void CronetBidirectionalStreamAdapter::MaybeReportMetrics() {
472 if (!bidi_stream_)
473 return;
474 net::LoadTimingInfo load_timing_info;
475 bidi_stream_->GetLoadTimingInfo(&load_timing_info);
476 JNIEnv* env = base::android::AttachCurrentThread();
477 base::Time start_time = load_timing_info.request_start_time;
478 base::TimeTicks start_ticks = load_timing_info.request_start;
479 cronet::Java_CronetBidirectionalStream_onMetricsCollected(
480 env, owner_,
481 metrics_util::ConvertTime(start_ticks, start_ticks, start_time),
482 metrics_util::ConvertTime(
483 load_timing_info.connect_timing.domain_lookup_start, start_ticks,
484 start_time),
485 metrics_util::ConvertTime(
486 load_timing_info.connect_timing.domain_lookup_end, start_ticks,
487 start_time),
488 metrics_util::ConvertTime(load_timing_info.connect_timing.connect_start,
489 start_ticks, start_time),
490 metrics_util::ConvertTime(load_timing_info.connect_timing.connect_end,
491 start_ticks, start_time),
492 metrics_util::ConvertTime(load_timing_info.connect_timing.ssl_start,
493 start_ticks, start_time),
494 metrics_util::ConvertTime(load_timing_info.connect_timing.ssl_end,
495 start_ticks, start_time),
496 metrics_util::ConvertTime(load_timing_info.send_start, start_ticks,
497 start_time),
498 metrics_util::ConvertTime(load_timing_info.send_end, start_ticks,
499 start_time),
500 metrics_util::ConvertTime(load_timing_info.push_start, start_ticks,
501 start_time),
502 metrics_util::ConvertTime(load_timing_info.push_end, start_ticks,
503 start_time),
504 metrics_util::ConvertTime(load_timing_info.receive_headers_end,
505 start_ticks, start_time),
506 metrics_util::ConvertTime(base::TimeTicks::Now(), start_ticks,
507 start_time),
508 load_timing_info.socket_reused, bidi_stream_->GetTotalSentBytes(),
509 bidi_stream_->GetTotalReceivedBytes());
510 }
511
512 } // namespace cronet
513